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')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
42if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
43require_once(DOKU_PLUGIN.'syntax.php');
44
45/*
46 * All DokuWiki plugins to extend the parser/rendering mechanism
47 * need to inherit from this class
48 */
49class syntax_plugin_rack extends DokuWiki_Syntax_Plugin {
50
51	/*
52	 * return some info
53	 */
54	function getInfo(){
55		return array(
56			'author' => 'Tyler Bletsch',
57			'email'  => 'Tyler.Bletsch@netapp.com',
58			'date'   => '2010-01-18',
59			'name'   => 'Rack Elevation Plugin',
60			'desc'   => 'Displays an elevation of a datacenter rack.',
61			'url'    => 'http://www.dokuwiki.org/plugin:rack',
62		);
63	}
64
65	/*
66	 * What kind of syntax are we?
67	 */
68	function getType(){
69		return 'substition';
70	}
71
72	/*
73	 * Where to sort in?
74	 */
75	function getSort(){
76		return 155;
77	}
78
79	/*
80	 * Paragraph Type
81	 */
82	function getPType(){
83		return 'block';
84	}
85
86	/*
87	 * Connect pattern to lexer
88	 */
89	function connectTo($mode) {
90		$this->Lexer->addSpecialPattern("<rack[^>]*>.*?(?:<\/rack>)",$mode,'plugin_rack');
91	}
92
93
94	/*
95	 * Handle the matches
96	 */
97	function handle($match, $state, $pos, &$handler){
98		$match = substr($match,5,-7);
99
100		//default options
101		$opt = array(
102			'name' => 'Rack',
103			'height' => 42,
104			'content' => ''
105		);
106
107		list($optstr,$opt['content']) = explode('>',$match,2);
108		unset($match);
109
110		// parse options
111		$optsin = explode(' ',$optstr);
112		foreach($optsin as $o){
113			$o = trim($o);
114			if (preg_match("/^name=(.+)/",$o,$matches)) {
115				$opt['name'] = $matches[1];
116			} elseif (preg_match("/^height=(\d+)/",$o,$matches)) {
117				$opt['height'] = $matches[1];
118			} elseif (preg_match("/^descending/",$o,$matches)) {
119				$opt['descending'] = 1;
120			}
121		}
122
123		return $opt;
124	}
125
126	function autoselect_color($item) {
127		$color = '#888';
128		if (preg_match('/(wire|cable)\s*guide|pdu|patch|term server|lcd/i',$item['model'])) { $color = '#bba'; }
129		if (preg_match('/blank/i',                                         $item['model'])) { $color = '#fff'; }
130		if (preg_match('/netapp|fas\d/i',                                  $item['model'])) { $color = '#07c'; }
131		if (preg_match('/^Sh(elf)?\s/i',                                   $item['model'])) { $color = '#0AE'; }
132		if (preg_match('/cisco|catalyst|nexus/i',                          $item['model'])) { $color = '#F80'; }
133		if (preg_match('/brocade|mds/i',                                   $item['model'])) { $color = '#8F0'; }
134		if (preg_match('/ucs/i',                                           $item['model'])) { $color = '#c00'; }
135		if (preg_match('/ibm/i',                                           $item['model'])) { $color = '#67A'; }
136		if (preg_match('/hp/i',                                            $item['model'])) { $color = '#A67'; }
137		if (!$item['model']) { $color = '#FFF'; }
138		return $color;
139	}
140
141	/*
142	 * Create output
143	 */
144	function render($mode, &$renderer, $opt) {
145		if($mode == 'metadata') return false;
146
147		$content = $opt['content'];
148
149		// clear any trailing or leading empty lines from the data set
150		$content = preg_replace("/[\r\n]*$/","",$content);
151		$content = preg_replace("/^\s*[\r\n]*/","",$content);
152
153		if(!trim($content)){
154			$renderer->cdata('No data found');
155		}
156
157		$items = array();
158
159		$csv_id = uniqid("csv_");
160		$csv = "Model,Name,Rack,U,Height,Comment\n";
161
162		foreach (explode("\n",$content) as $line) {
163			$item = array();
164			if (preg_match("/^\s*#/",$line) || !trim($line)) { continue; } # skip comments & blanks
165			#                     Ustart    Usize     Model                    Name?                                             Color?       Link?               Comment
166			if (preg_match('/^\s* (\d+) \s+ (\d+) \s+ ((?:"[^"]*")|\S+) \s* ((?:"[^"]*")|(?!(?:(?:\#)|(?:link:)))\S*)? \s* (\#\w+)? \s* ( link: (?: (?:\[\[[^]|]+(?:\|[^]]*)?]]) | \S* ) )? \s* (.*?)? \s* $/x',$line,$matches)) {
167				$item['u_bottom'] = $matches[1];
168				$item['u_size'] = $matches[2];
169				$item['model'] = preg_replace('/^"/','',preg_replace('/"$/','',$matches[3]));
170				$item['name'] = preg_replace('/^"/','',preg_replace('/"$/','',$matches[4]));
171				$item['color'] = $matches[5] ? $matches[5] : $this->autoselect_color($item);
172				$item['linktitle'] = '';
173				$item['link'] = substr($matches[6], 5);
174				if( '[' == substr($item['link'], 0, 1)) {
175					if(preg_match( '/^\[\[[^|]+\|([^]]+)]]$/', $item['link'], $titlematch )) {
176						$item['linktitle'] = ' title="'.hsc($titlematch[1]). '"';
177					}
178					$item['link']=wl(cleanID(preg_replace( '/^\[\[([^]|]+).*/', '$1', $item['link'] )));
179				}
180				$item['comment'] = $matches[7];
181				$item['u_top'] = $item['u_bottom'] + ($opt['descending']?-1:1)*($item['u_size'] - 1);
182				$items[$item['u_top']] = $item;
183				$csv .= "\"$item[model]\",\"$item[name]\",$opt[name],$item[u_bottom],$item[u_size],\"$item[comment]\"\n";
184			} else {
185				#$renderer->doc .= "Syntax error on the following line: <pre style='color:red'>$line</pre>\n";
186				$renderer->doc .= 'Syntax error on the following line: <pre style="color:red">'.hsc($line)."</pre>\n";
187			}
188		}
189
190		#$renderer->doc .= "<pre>ALL\n".print_r($items,true)."</pre>";
191
192		$u_first = $opt['descending'] ? 1 : $opt['height'];
193		$u_last  = $opt['descending'] ? $opt['height'] : 1;
194		$u_delta = $opt['descending'] ? +1 : -1;
195		$renderer->doc .= "<table class='rack'><tr><th colspan='2' class='title'>$opt[name]</th></tr>\n";
196		#for ($u=$opt['height']; $u>=1; $u--) {
197		#foreach (range($u_first,$u_last,$u_delta) as $u) {
198		for ($u=$u_first;  ($opt['descending'] ? $u<=$u_last : $u>=$u_last);  $u += $u_delta) {
199			if ($items[$u] && $items[$u]['model']) {
200				$item = $items[$u];
201				$renderer->doc .=
202					"<tr><th>$u</th>".
203					"<td class='item' rowspan='$item[u_size]' style='background-color: $item[color];' title=\"".htmlspecialchars($item['comment'])."\">".
204					($item['link'] ? '<a href="'.$item['link'].'"'.$item['linktitle'].'>' : '').
205					"<div style='float: left; font-weight:bold;'>".
206						"$item[model]" .
207						($item['comment'] ? ' *' : '').
208					"</div>".
209					"<div style='float: right; margin-left: 3em; '>$item[name]".
210					"</div>".
211					($item['link'] ? '</a>' : '').
212					"</td></tr>\n";
213				for ($d = 1; $d < $item['u_size']; $d++) {
214					$u += $u_delta;
215					$renderer->doc .= "<tr><th>$u</th></tr>\n";
216				}
217			} else {
218				$renderer->doc .= "<Tr><Th>$u</th><td class='empty'></td></tr>\n";
219			}
220		}
221		# we use a whole row as a bottom border to sidestep an asinine rendering bug in firefox 3.0.10
222		$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";
223		$renderer->doc .= "</table>&nbsp;";
224
225		# this javascript hack sets the CSS "display" property of the tables to "inline",
226		# since IE is too dumb to have heard of the "inline-table" mode.
227		$renderer->doc .= "<script type='text/javascript'>rack_ie6fix();</script>\n";
228
229		$renderer->doc .= "<pre style='display:none;' id='$csv_id'>$csv</pre>\n";
230
231		return true;
232	}
233
234}
235