1<?php
2/*
3 * Rack Plugin: display a rack elevation from a plaintext source
4 *
5 * Each rack is enclosed in <rack>...</rack> tags. The rack tag can have the
6 * following parameters (all optional):
7 *   name=<name>     The name of the rack (default: 'Rack')
8 *   height=<U>      The height of the rack, in U (default: 42)
9 *   descending      Indicates that the rack is numbered top-to-bottom
10 * Between these tags is a series of lines, each describing a piece of equipment:
11 *
12 *   <u_bottom> <u_size> <model> [name] [#color] [link:<URL>] [comment]
13 *
14 * The fields:
15 *  - <u_bottom>: The starting (bottom-most) U of the equipment.
16 *  - <u_size>: The height of the equipment in U.
17 *  - <model>: The model name or other description of the item (e.g.
18 *    "Cisco 4948" or "Patch panel"). If it has spaces, enclose it in quotes.
19 *  - [name]: Optional. The hostname or other designator of this specific item
20 *    (e.g. �rtpserver1�). If it has spaces, enclose it in quotes. If you want
21 *    to specify a comment but no name, use �� as the name.
22 *  - [#color]: Optional. The color of the item is normally automatically
23 *    picked based on the model, but you can override it by specifying a
24 *    #RRGGBB HTML color after the model/name.
25 *  - [link:http://url]: Optional. The model name will now link to the url given.
26 *  - [comment]: Optional. After the name (and possibly color), and remaining
27 *    text on the line is treated as free-form comment. Comments are visible
28 *    by hovering the mouse over the equipment in the rack. Items with comments
29 *    are designated with an asterisk * after the model name.
30 *
31 * You can also include comment lines starting with a pound sign #.
32 *
33 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
34 * @author     Tyler Bletsch <Tyler.Bletsch@netapp.com>
35 * @version    20120816.1
36 *
37 * Modded for links usage by Sylvain Bigonneau <s.bigonneau@moka-works.com>
38 * Improved link syntax contributed by Dokuwiki user "schplurtz".
39 */
40
41if(!defined('DOKU_INC')) die(); // DokuWiki needed
42
43/*
44 * All DokuWiki plugins to extend the parser/rendering mechanism
45 * need to inherit from this class
46 */
47class syntax_plugin_rack extends DokuWiki_Syntax_Plugin {
48
49	/*
50	 * return some info
51	 */
52	function getInfo(){
53		return confToHash(dirname(__FILE__).'/plugin.info.txt');
54	}
55
56	/*
57	 * What kind of syntax are we?
58	 */
59	function getType(){
60		return 'substition';
61	}
62
63	/*
64	 * Where to sort in?
65	 */
66	function getSort(){
67		return 155;
68	}
69
70	/*
71	 * Paragraph Type
72	 */
73	function getPType(){
74		return 'block';
75	}
76
77	/*
78	 * Connect pattern to lexer
79	 */
80	function connectTo($mode) {
81		$this->Lexer->addSpecialPattern("<rack[^>]*>.*?(?:<\/rack>)",$mode,'plugin_rack');
82	}
83
84
85	/*
86	 * Handle the matches
87	 */
88	function handle($match, $state, $pos, Doku_Handler $handler){
89		$match = substr($match,5,-7);
90
91		//default options
92		$opt = array(
93			'name' => 'Rack',
94			'height' => 42,
95			'content' => ''
96		);
97
98		list($optstr,$opt['content']) = explode('>',$match,2);
99		unset($match);
100
101		// parse options
102		$optsin = explode(' ',$optstr);
103		foreach($optsin as $o){
104			$o = trim($o);
105			if (preg_match("/^name=(.+)/",$o,$matches)) {
106				$opt['name'] = $matches[1];
107			} elseif (preg_match("/^height=(\d+)/",$o,$matches)) {
108				$opt['height'] = $matches[1];
109			} elseif (preg_match("/^descending/",$o,$matches)) {
110				$opt['descending'] = 1;
111			}
112		}
113		return $opt;
114	}
115
116	function autoselect_color($item) {
117		$color = '#888';
118		if (preg_match('/(wire|cable)\s*guide|pdu|patch|term server|lcd/i',$item['model'])) { $color = '#bba'; }
119		if (preg_match('/blank/i',                                         $item['model'])) { $color = '#fff'; }
120		if (preg_match('/netapp|fas\d/i',                                  $item['model'])) { $color = '#07c'; }
121		if (preg_match('/^Sh(elf)?\s/i',                                   $item['model'])) { $color = '#0AE'; }
122		if (preg_match('/cisco|catalyst|nexus/i',                          $item['model'])) { $color = '#F80'; }
123		if (preg_match('/brocade|mds/i',                                   $item['model'])) { $color = '#8F0'; }
124		if (preg_match('/ucs/i',                                           $item['model'])) { $color = '#c00'; }
125		if (preg_match('/ibm/i',                                           $item['model'])) { $color = '#67A'; }
126		if (preg_match('/hp/i',                                            $item['model'])) { $color = '#A67'; }
127		if (!$item['model']) { $color = '#FFF'; }
128		return $color;
129	}
130
131	/*
132	 * Create output
133	 */
134	function render($mode, Doku_Renderer $renderer, $opt) {
135		if($mode == 'metadata') return false;
136
137		$content = $opt['content'];
138
139		// clear any trailing or leading empty lines from the data set
140		$content = preg_replace("/[\r\n]*$/","",$content);
141		$content = preg_replace("/^\s*[\r\n]*/","",$content);
142
143		if(!trim($content)){
144			$renderer->cdata('No data found');
145		}
146
147		$items = array();
148
149		$csv_id = uniqid("csv_");
150		$csv = "Model,Name,Rack,U,Height,Comment\n";
151
152		foreach (explode("\n",$content) as $line) {
153			$item = array();
154			if (preg_match("/^\s*#/",$line) || !trim($line)) { continue; } # skip comments & blanks
155			#                     Ustart    Usize     Model                    Name?                                             Color?       Link?               Comment
156			if (preg_match('/^\s* (\d+) \s+ (\d+) \s+ ((?:"[^"]*")|\S+) \s* ((?:"[^"]*")|(?!(?:(?:\#)|(?:link:)))\S*)? \s* (\#\w+)? \s* ( link: (?: (?:\[\[[^]|]+(?:\|[^]]*)?]]) | \S* ) )? \s* (.*?)? \s* $/x',$line,$matches)) {
157				$item['u_bottom'] = $matches[1];
158				$item['u_size'] = $matches[2];
159				$item['model'] = preg_replace('/^"/','',preg_replace('/"$/','',$matches[3]));
160				$item['name'] = preg_replace('/^"/','',preg_replace('/"$/','',$matches[4]));
161				$item['color'] = $matches[5] ? $matches[5] : $this->autoselect_color($item);
162				$item['linktitle'] = '';
163				$item['link'] = substr($matches[6], 5);
164				if( '[' == substr($item['link'], 0, 1)) {
165					if(preg_match( '/^\[\[[^|]+\|([^]]+)]]$/', $item['link'], $titlematch )) {
166						$item['linktitle'] = ' title="'.hsc($titlematch[1]). '"';
167					}
168					$item['link']=wl(cleanID(preg_replace( '/^\[\[([^]|]+).*/', '$1', $item['link'] )));
169				}
170				$item['comment'] = $matches[7];
171				$item['u_top'] = $item['u_bottom'] + ($opt['descending']?-1:1)*($item['u_size'] - 1);
172				$items[$item['u_top']] = $item;
173				$csv .= "\"$item[model]\",\"$item[name]\",$opt[name],$item[u_bottom],$item[u_size],\"$item[comment]\"\n";
174			} else {
175				#$renderer->doc .= "Syntax error on the following line: <pre style='color:red'>$line</pre>\n";
176				$renderer->doc .= 'Syntax error on the following line: <pre style="color:red">'.hsc($line)."</pre>\n";
177			}
178		}
179
180		#$renderer->doc .= "<pre>ALL\n".print_r($items,true)."</pre>";
181
182		$u_first = $opt['descending'] ? 1 : $opt['height'];
183		$u_last  = $opt['descending'] ? $opt['height'] : 1;
184		$u_delta = $opt['descending'] ? +1 : -1;
185		$renderer->doc .= "<table class='rack'><tr><th colspan='2' class='title'>$opt[name]</th></tr>\n";
186		#for ($u=$opt['height']; $u>=1; $u--) {
187		#foreach (range($u_first,$u_last,$u_delta) as $u) {
188		for ($u=$u_first;  ($opt['descending'] ? $u<=$u_last : $u>=$u_last);  $u += $u_delta) {
189			if ($items[$u] && $items[$u]['model']) {
190				$item = $items[$u];
191				$renderer->doc .=
192					"<tr><th>$u</th>".
193					"<td class='item' rowspan='$item[u_size]' style='background-color: $item[color];' title=\"".htmlspecialchars($item['comment'])."\">".
194					($item['link'] ? '<a href="'.$item['link'].'"'.$item['linktitle'].'>' : '').
195					"<div style='float: left; font-weight:bold;'>".
196						"$item[model]" .
197						($item['comment'] ? ' *' : '').
198					"</div>".
199					"<div style='float: right; margin-left: 3em; '>$item[name]".
200					"</div>".
201					($item['link'] ? '</a>' : '').
202					"</td></tr>\n";
203				for ($d = 1; $d < $item['u_size']; $d++) {
204					$u += $u_delta;
205					$renderer->doc .= "<tr><th>$u</th></tr>\n";
206				}
207			} else {
208				$renderer->doc .= "<Tr><Th>$u</th><td class='empty'></td></tr>\n";
209			}
210		}
211		# we use a whole row as a bottom border to sidestep an asinine rendering bug in firefox 3.0.10
212		$renderer->doc .= "<tr><th colspan='2' class='bottom'><span style='cursor: pointer;' onclick=\"this.innerHTML = rack_toggle_vis(document.getElementById('$csv_id'),'block')?'Hide CSV &uarr;':'Show CSV &darr;';\">Show CSV &darr;</span></th></tr>\n";
213		$renderer->doc .= "</table>&nbsp;";
214
215		# this javascript hack sets the CSS "display" property of the tables to "inline",
216		# since IE is too dumb to have heard of the "inline-table" mode.
217		$renderer->doc .= "<script type='text/javascript'>rack_ie6fix();</script>\n";
218
219		$renderer->doc .= "<pre style='display:none;' id='$csv_id'>$csv</pre>\n";
220
221		return true;
222	}
223
224}
225