1<?php
2// PHP Weathermap 0.97a
3// Copyright Howard Jones, 2005-2010 howie@thingy.com
4// http://www.network-weathermap.com/
5// Released under the GNU Public License
6
7require_once "HTML_ImageMap.class.php";
8
9require_once "WeatherMap.functions.php";
10require_once "WeatherMapNode.class.php";
11require_once "WeatherMapLink.class.php";
12
13$WEATHERMAP_VERSION="0.97a";
14$weathermap_debugging=FALSE;
15$weathermap_map="";
16$weathermap_warncount=0;
17$weathermap_debug_suppress = array("processstring","mysprintf");
18$weathemap_lazycounter=0;
19
20// Turn on ALL error reporting for now.
21// error_reporting (E_ALL|E_STRICT);
22
23// parameterise the in/out stuff a bit
24define("IN",0);
25define("OUT",1);
26define("WMCHANNELS",2);
27
28define('CONFIG_TYPE_LITERAL',0);
29define('CONFIG_TYPE_COLOR',1);
30
31// some strings that are used in more than one place
32define('FMT_BITS_IN',"{link:this:bandwidth_in:%2k}");
33define('FMT_BITS_OUT',"{link:this:bandwidth_out:%2k}");
34define('FMT_UNFORM_IN',"{link:this:bandwidth_in}");
35define('FMT_UNFORM_OUT',"{link:this:bandwidth_out}");
36define('FMT_PERC_IN',"{link:this:inpercent:%.2f}%");
37define('FMT_PERC_OUT',"{link:this:outpercent:%.2f}%");
38
39// the fields within a spine triple
40define("X",0);
41define("Y",1);
42define("DISTANCE",2);
43
44// ***********************************************
45
46// template class for data sources. All data sources extend this class.
47// I really wish PHP4 would just die overnight
48class WeatherMapDataSource
49{
50	// Initialize - called after config has been read (so SETs are processed)
51	// but just before ReadData. Used to allow plugins to verify their dependencies
52	// (if any) and bow out gracefully. Return FALSE to signal that the plugin is not
53	// in a fit state to run at the moment.
54	function Init(&$map) { return TRUE; }
55
56	// called with the TARGET string. Returns TRUE or FALSE, depending on whether it wants to handle this TARGET
57	// called by map->ReadData()
58	function Recognise( $targetstring ) { return FALSE; }
59
60	// the actual ReadData
61	//   returns an array of two values (in,out). -1,-1 if it couldn't get valid data
62	//   configline is passed in, to allow for better error messages
63	//   itemtype and itemname may be used as part of the target (e.g. for TSV source line)
64	// function ReadData($targetstring, $configline, $itemtype, $itemname, $map) { return (array(-1,-1)); }
65	function ReadData($targetstring, &$map, &$item)
66	{
67		return(array(-1,-1));
68	}
69
70	// pre-register a target + context, to allow a plugin to batch up queries to a slow database, or snmp for example
71	function Register($targetstring, &$map, &$item)
72	{
73
74	}
75
76	// called before ReadData, to allow plugins to DO the prefetch of targets known from Register
77	function Prefetch()
78	{
79
80	}
81}
82
83// template classes for the pre- and post-processor plugins
84class WeatherMapPreProcessor
85{
86	function run($map) { return FALSE; }
87}
88
89class WeatherMapPostProcessor
90{
91	function run($map) { return FALSE; }
92}
93
94// ***********************************************
95
96// Links, Nodes and the Map object inherit from this class ultimately.
97// Just to make some common code common.
98class WeatherMapBase
99{
100	var $notes = array();
101	var $hints = array();
102	var $inherit_fieldlist;
103
104	function add_note($name,$value)
105	{
106		debug("Adding note $name='$value' to ".$this->name."\n");
107		$this->notes[$name] = $value;
108	}
109
110	function get_note($name)
111	{
112		if(isset($this->notes[$name]))
113		{
114		//	debug("Found note $name in ".$this->name." with value of ".$this->notes[$name].".\n");
115			return($this->notes[$name]);
116		}
117		else
118		{
119		//	debug("Looked for note $name in ".$this->name." which doesn't exist.\n");
120			return(NULL);
121		}
122	}
123
124	function add_hint($name,$value)
125	{
126		debug("Adding hint $name='$value' to ".$this->name."\n");
127		$this->hints[$name] = $value;
128		# warn("Adding hint $name to ".$this->my_type()."/".$this->name."\n");
129	}
130
131
132	function get_hint($name)
133	{
134		if(isset($this->hints[$name]))
135		{
136		//	debug("Found hint $name in ".$this->name." with value of ".$this->hints[$name].".\n");
137			return($this->hints[$name]);
138		}
139		else
140		{
141		//	debug("Looked for hint $name in ".$this->name." which doesn't exist.\n");
142			return(NULL);
143		}
144	}
145}
146
147class WeatherMapConfigItem
148{
149	var $defined_in;
150	var $name;
151	var $value;
152	var $type;
153}
154
155// The 'things on the map' class. More common code (mainly variables, actually)
156class WeatherMapItem extends WeatherMapBase
157{
158	var $owner;
159
160	var $configline;
161	var $infourl;
162	var $overliburl;
163	var $overlibwidth, $overlibheight;
164	var $overlibcaption;
165	var $my_default;
166	var $defined_in;
167	var $config_override;	# used by the editor to allow text-editing
168
169	function my_type() {  return "ITEM"; }
170}
171
172class WeatherMap extends WeatherMapBase
173{
174	var $nodes = array(); // an array of WeatherMapNodes
175	var $links = array(); // an array of WeatherMapLinks
176	var $texts = array(); // an array containing all the extraneous text bits
177	var $used_images = array(); // an array of image filenames referred to (used by editor)
178	var $seen_zlayers = array(0=>array(),1000=>array()); // 0 is the background, 1000 is the legends, title, etc
179
180	var $config;
181	var $next_id;
182	var $min_ds_time;
183	var $max_ds_time;
184	var $background;
185	var $htmlstyle;
186	var $imap;
187	var $colours;
188	var $configfile;
189	var $imagefile,
190		$imageuri;
191	var $rrdtool;
192	var $title,
193		$titlefont;
194	var $kilo;
195	var $sizedebug,
196		$widthmod,
197		$debugging;
198	var $linkfont,
199		$nodefont,
200		$keyfont,
201		$timefont;
202	// var $bg_r, $bg_g, $bg_b;
203	var $timex,
204		$timey;
205	var $width,
206		$height;
207	var $keyx,
208		$keyy, $keyimage;
209	var $titlex,
210		$titley;
211	var $keytext,
212		$stamptext, $datestamp;
213	var $min_data_time, $max_data_time;
214	var $htmloutputfile,
215		$imageoutputfile;
216	var $htmlstylesheet;
217	var $defaultlink,
218		$defaultnode;
219	var $need_size_precalc;
220	var $keystyle,$keysize;
221	var $rrdtool_check;
222	var $inherit_fieldlist;
223	var $mintimex, $maxtimex;
224	var $mintimey, $maxtimey;
225	var $minstamptext, $maxstamptext;
226	var $context;
227	var $cachefolder,$mapcache,$cachefile_version;
228	var $name;
229	var $black,
230		$white,
231		$grey,
232		$selected;
233
234	var $datasourceclasses;
235	var $preprocessclasses;
236	var $postprocessclasses;
237	var $activedatasourceclasses;
238	var $thumb_width, $thumb_height;
239	var $has_includes;
240	var $has_overlibs;
241	var $node_template_tree;
242	var $link_template_tree;
243
244	var $plugins = array();
245	var $included_files = array();
246	var $usage_stats = array();
247
248	function WeatherMap()
249	{
250		$this->inherit_fieldlist=array
251			(
252				'width' => 800,
253				'height' => 600,
254				'kilo' => 1000,
255				'numscales' => array('DEFAULT' => 0),
256				'datasourceclasses' => array(),
257				'preprocessclasses' => array(),
258				'postprocessclasses' => array(),
259				'included_files' => array(),
260				'context' => '',
261				'dumpconfig' => FALSE,
262				'rrdtool_check' => '',
263				'background' => '',
264				'imageoutputfile' => '',
265				'imageuri' => '',
266				'htmloutputfile' => '',
267				'htmlstylesheet' => '',
268				'labelstyle' => 'percent', // redundant?
269				'htmlstyle' => 'static',
270				'keystyle' => array('DEFAULT' => 'classic'),
271				'title' => 'Network Weathermap',
272				'keytext' => array('DEFAULT' => 'Traffic Load'),
273				'keyx' => array('DEFAULT' => -1),
274				'keyy' => array('DEFAULT' => -1),
275				'keyimage' => array(),
276				'keysize' => array('DEFAULT' => 400),
277				'stamptext' => 'Created: %b %d %Y %H:%M:%S',
278				'keyfont' => 4,
279				'titlefont' => 2,
280				'timefont' => 2,
281				'timex' => 0,
282				'timey' => 0,
283
284				'mintimex' => -10000,
285				'mintimey' => -10000,
286				'maxtimex' => -10000,
287				'maxtimey' => -10000,
288				'minstamptext' => 'Oldest Data: %b %d %Y %H:%M:%S',
289				'maxstamptext' => 'Newest Data: %b %d %Y %H:%M:%S',
290
291				'thumb_width' => 0,
292				'thumb_height' => 0,
293				'titlex' => -1,
294				'titley' => -1,
295				'cachefolder' => 'cached',
296				'mapcache' => '',
297				'sizedebug' => FALSE,
298				'debugging' => FALSE,
299				'widthmod' => FALSE,
300				'has_includes' => FALSE,
301				'has_overlibs' => FALSE,
302				'name' => 'MAP'
303			);
304
305		$this->Reset();
306	}
307
308	function my_type() {  return "MAP"; }
309
310	function Reset()
311	{
312		$this->next_id = 100;
313		foreach (array_keys($this->inherit_fieldlist)as $fld) { $this->$fld=$this->inherit_fieldlist[$fld]; }
314
315		$this->min_ds_time = NULL;
316		$this->max_ds_time = NULL;
317
318		$this->need_size_precalc=FALSE;
319
320		$this->nodes=array(); // an array of WeatherMapNodes
321		$this->links=array(); // an array of WeatherMapLinks
322
323		// these are the default defaults
324                // by putting them into a normal object, we can use the
325                // same code for writing out LINK DEFAULT as any other link.
326                debug("Creating ':: DEFAULT ::' DEFAULT LINK\n");
327                // these two are used for default settings
328                $deflink = new WeatherMapLink;
329                $deflink->name=":: DEFAULT ::";
330                $deflink->template=":: DEFAULT ::";
331                $deflink->Reset($this);
332
333                $this->links[':: DEFAULT ::'] = &$deflink;
334
335                debug("Creating ':: DEFAULT ::' DEFAULT NODE\n");
336                $defnode = new WeatherMapNode;
337                $defnode->name=":: DEFAULT ::";
338                $defnode->template=":: DEFAULT ::";
339                $defnode->Reset($this);
340
341                $this->nodes[':: DEFAULT ::'] = &$defnode;
342
343       	$this->node_template_tree = array();
344       	$this->link_template_tree = array();
345
346		$this->node_template_tree['DEFAULT'] = array();
347		$this->link_template_tree['DEFAULT'] = array();
348
349
350// ************************************
351		// now create the DEFAULT link and node, based on those.
352		// these can be modified by the user, but their template (and therefore comparison in WriteConfig) is ':: DEFAULT ::'
353		debug("Creating actual DEFAULT NODE from :: DEFAULT ::\n");
354                $defnode2 = new WeatherMapNode;
355                $defnode2->name = "DEFAULT";
356                $defnode2->template = ":: DEFAULT ::";
357                $defnode2->Reset($this);
358
359                $this->nodes['DEFAULT'] = &$defnode2;
360
361		debug("Creating actual DEFAULT LINK from :: DEFAULT ::\n");
362                $deflink2 = new WeatherMapLink;
363                $deflink2->name = "DEFAULT";
364                $deflink2->template = ":: DEFAULT ::";
365                $deflink2->Reset($this);
366
367                $this->links['DEFAULT'] = &$deflink2;
368
369// for now, make the old defaultlink and defaultnode work too.
370//                $this->defaultlink = $this->links['DEFAULT'];
371//                $this->defaultnode = $this->nodes['DEFAULT'];
372
373                assert('is_object($this->nodes[":: DEFAULT ::"])');
374                assert('is_object($this->links[":: DEFAULT ::"])');
375				assert('is_object($this->nodes["DEFAULT"])');
376                assert('is_object($this->links["DEFAULT"])');
377
378// ************************************
379
380
381		$this->imap=new HTML_ImageMap('weathermap');
382		$this->colours=array
383			(
384				);
385
386		debug ("Adding default map colour set.\n");
387		$defaults=array
388			(
389				'KEYTEXT' => array('bottom' => -2, 'top' => -1, 'red1' => 0, 'green1' => 0, 'blue1' => 0, 'special' => 1),
390				'KEYOUTLINE' => array('bottom' => -2, 'top' => -1, 'red1' => 0, 'green1' => 0, 'blue1' => 0, 'special' => 1),
391				'KEYBG' => array('bottom' => -2, 'top' => -1, 'red1' => 255, 'green1' => 255, 'blue1' => 255, 'special' => 1),
392				'BG' => array('bottom' => -2, 'top' => -1, 'red1' => 255, 'green1' => 255, 'blue1' => 255, 'special' => 1),
393				'TITLE' => array('bottom' => -2, 'top' => -1, 'red1' => 0, 'green1' => 0, 'blue1' => 0, 'special' => 1),
394				'TIME' => array('bottom' => -2, 'top' => -1, 'red1' => 0, 'green1' => 0, 'blue1' => 0, 'special' => 1)
395			);
396
397		foreach ($defaults as $key => $def) { $this->colours['DEFAULT'][$key]=$def; }
398
399		$this->configfile='';
400		$this->imagefile='';
401		$this->imageuri='';
402
403		$this->fonts=array();
404
405		// Adding these makes the editor's job a little easier, mainly
406		for($i=1; $i<=5; $i++)
407		{
408			$this->fonts[$i]->type="GD builtin";
409			$this->fonts[$i]->file='';
410			$this->fonts[$i]->size=0;
411		}
412
413		$this->LoadPlugins('data', 'lib' . DIRECTORY_SEPARATOR . 'datasources');
414		$this->LoadPlugins('pre', 'lib' . DIRECTORY_SEPARATOR . 'pre');
415		$this->LoadPlugins('post', 'lib' . DIRECTORY_SEPARATOR . 'post');
416
417		debug("WeatherMap class Reset() complete\n");
418	}
419
420	function myimagestring($image, $fontnumber, $x, $y, $string, $colour, $angle=0)
421	{
422		// if it's supposed to be a special font, and it hasn't been defined, then fall through
423		if ($fontnumber > 5 && !isset($this->fonts[$fontnumber]))
424		{
425			warn ("Using a non-existent special font ($fontnumber) - falling back to internal GD fonts [WMWARN03]\n");
426			if($angle != 0) warn("Angled text doesn't work with non-FreeType fonts [WMWARN02]\n");
427			$fontnumber=5;
428		}
429
430		if (($fontnumber > 0) && ($fontnumber < 6))
431		{
432			imagestring($image, $fontnumber, $x, $y - imagefontheight($fontnumber), $string, $colour);
433			if($angle != 0) warn("Angled text doesn't work with non-FreeType fonts [WMWARN02]\n");
434		}
435		else
436		{
437			// look up what font is defined for this slot number
438			if ($this->fonts[$fontnumber]->type == 'truetype')
439			{
440				wimagettftext($image, $this->fonts[$fontnumber]->size, $angle, $x, $y,
441					$colour, $this->fonts[$fontnumber]->file, $string);
442			}
443
444			if ($this->fonts[$fontnumber]->type == 'gd')
445			{
446				imagestring($image, $this->fonts[$fontnumber]->gdnumber,
447					$x,      $y - imagefontheight($this->fonts[$fontnumber]->gdnumber),
448					$string, $colour);
449				if($angle != 0) warn("Angled text doesn't work with non-FreeType fonts [WMWARN04]\n");
450			}
451		}
452	}
453
454	function myimagestringsize($fontnumber, $string)
455	{
456		$linecount = 1;
457
458		$lines = split("\n",$string);
459		$linecount = sizeof($lines);
460		$maxlinelength=0;
461		foreach($lines as $line)
462		{
463			$l = strlen($line);
464			if($l > $maxlinelength) $maxlinelength = $l;
465		}
466
467		if (($fontnumber > 0) && ($fontnumber < 6))
468		{ return array(imagefontwidth($fontnumber) * $maxlinelength, $linecount * imagefontheight($fontnumber)); }
469		else
470		{
471			// look up what font is defined for this slot number
472			if (!isset($this->fonts[$fontnumber]))
473			{
474				warn ("Using a non-existent special font ($fontnumber) - falling back to internal GD fonts [WMWARN36]\n");
475				$fontnumber=5;
476				return array(imagefontwidth($fontnumber) * $maxlinelength, $linecount * imagefontheight($fontnumber));
477			}
478			else
479			{
480				if ($this->fonts[$fontnumber]->type == 'truetype')
481				{
482					$ysize = 0;
483					$xsize = 0;
484					foreach($lines as $line)
485					{
486						$bounds=imagettfbbox($this->fonts[$fontnumber]->size, 0, $this->fonts[$fontnumber]->file, $line);
487						$cx = $bounds[4] - $bounds[0];
488						$cy = $bounds[1] - $bounds[5];
489						if($cx > $xsize) $xsize = $cx;
490						$ysize += ($cy*1.2);
491						# warn("Adding $cy (x was $cx)\n");
492					}
493					#$bounds=imagettfbbox($this->fonts[$fontnumber]->size, 0, $this->fonts[$fontnumber]->file,
494					#	$string);
495					# return (array($bounds[4] - $bounds[0], $bounds[1] - $bounds[5]));
496					# warn("Size of $string is $xsize x $ysize over $linecount lines\n");
497
498					return(array($xsize,$ysize));
499				}
500
501				if ($this->fonts[$fontnumber]->type == 'gd')
502				{ return array(imagefontwidth($this->fonts[$fontnumber]->gdnumber) * $maxlinelength,
503					$linecount * imagefontheight($this->fonts[$fontnumber]->gdnumber)); }
504			}
505		}
506	}
507
508	function ProcessString($input,&$context, $include_notes=TRUE,$multiline=FALSE)
509	{
510		# debug("ProcessString: input is $input\n");
511
512		assert('is_scalar($input)');
513
514		$context_description = strtolower( $context->my_type() );
515		if($context_description != "map") $context_description .= ":" . $context->name;
516
517		debug("Trace: ProcessString($input, $context_description)\n");
518
519		if($multiline==TRUE)
520		{
521			$i = $input;
522			$input = str_replace("\\n","\n",$i);
523			# if($i != $input)  warn("$i into $input\n");
524		}
525
526		$output = $input;
527
528		# while( preg_match("/(\{[^}]+\})/",$input,$matches) )
529		while( preg_match("/(\{(?:node|map|link)[^}]+\})/",$input,$matches) )
530		{
531			$value = "[UNKNOWN]";
532			$format = "";
533			$key = $matches[1];
534			debug("ProcessString: working on ".$key."\n");
535
536			if ( preg_match("/\{(node|map|link):([^}]+)\}/",$key,$matches) )
537			{
538				$type = $matches[1];
539				$args = $matches[2];
540				# debug("ProcessString: type is ".$type.", arguments are ".$args."\n");
541
542				if($type == 'map')
543				{
544					$the_item = $this;
545					if(preg_match("/map:([^:]+):*([^:]*)/",$args,$matches))
546					{
547						$args = $matches[1];
548						$format = $matches[2];
549					}
550				}
551
552				if(($type == 'link') || ($type == 'node'))
553				{
554					if(preg_match("/([^:]+):([^:]+):*([^:]*)/",$args,$matches))
555					{
556						$itemname = $matches[1];
557						$args = $matches[2];
558						$format = $matches[3];
559
560		#				debug("ProcessString: item is $itemname, and args are now $args\n");
561
562						$the_item = NULL;
563						if( ($itemname == "this") && ($type == strtolower($context->my_type())) )
564						{
565							$the_item = $context;
566						}
567						elseif( strtolower($context->my_type()) == "link" && $type == 'node' && ($itemname == '_linkstart_' || $itemname == '_linkend_') )
568						{
569							// this refers to the two nodes at either end of this link
570							if($itemname == '_linkstart_')
571							{
572								$the_item = $context->a;
573							}
574
575							if($itemname == '_linkend_')
576							{
577								$the_item = $context->b;
578							}
579						}
580						elseif( ($itemname == "parent") && ($type == strtolower($context->my_type())) && ($type=='node') && ($context->relative_to != '') )
581						{
582							$the_item = $this->nodes[$context->relative_to];
583						}
584						else
585						{
586							if( ($type == 'link') && isset($this->links[$itemname]) )
587							{
588								$the_item = $this->links[$itemname];
589							}
590							if( ($type == 'node') && isset($this->nodes[$itemname]) )
591							{
592								$the_item = $this->nodes[$itemname];
593							}
594						}
595					}
596				}
597
598				if(is_null($the_item))
599				{
600					warn("ProcessString: $key refers to unknown item (context is $context_description) [WMWARN05]\n");
601				}
602				else
603				{
604				#	warn($the_item->name.": ".var_dump($the_item->hints)."\n");
605					debug("ProcessString: Found appropriate item: ".get_class($the_item)." ".$the_item->name."\n");
606
607					# warn($the_item->name."/hints: ".var_dump($the_item->hints)."\n");
608					# warn($the_item->name."/notes: ".var_dump($the_item->notes)."\n");
609
610					// SET and notes have precedent over internal properties
611					// this is my laziness - it saves me having a list of reserved words
612					// which are currently used for internal props. You can just 'overwrite' any of them.
613					if(isset($the_item->hints[$args]))
614					{
615						$value = $the_item->hints[$args];
616						debug("ProcessString: used hint\n");
617					}
618					// for some things, we don't want to allow notes to be considered.
619					// mainly - TARGET (which can define command-lines), shouldn't be
620					// able to get data from uncontrolled sources (i.e. data sources rather than SET in config files).
621					elseif($include_notes && isset($the_item->notes[$args]))
622					{
623						$value = $the_item->notes[$args];
624						debug("ProcessString: used note\n");
625
626					}
627					elseif(isset($the_item->$args))
628					{
629						$value = $the_item->$args;
630						debug("ProcessString: used internal property\n");
631					}
632				}
633			}
634
635			// format, and sanitise the value string here, before returning it
636
637			if($value===NULL) $value='NULL';
638			debug("ProcessString: replacing ".$key." with $value\n");
639
640			# if($format != '') $value = sprintf($format,$value);
641			if($format != '')
642			{
643
644		#		debug("Formatting with mysprintf($format,$value)\n");
645				$value = mysprintf($format,$value);
646			}
647
648		#	debug("ProcessString: formatted to $value\n");
649			$input = str_replace($key,'',$input);
650			$output = str_replace($key,$value,$output);
651		}
652		#debug("ProcessString: output is $output\n");
653		return ($output);
654}
655
656function RandomData()
657{
658	foreach ($this->links as $link)
659	{
660		$this->links[$link->name]->bandwidth_in=rand(0, $link->max_bandwidth_in);
661		$this->links[$link->name]->bandwidth_out=rand(0, $link->max_bandwidth_out);
662	}
663}
664
665function LoadPlugins( $type="data", $dir="lib/datasources" )
666{
667	debug("Beginning to load $type plugins from $dir\n");
668
669    if ( ! file_exists($dir)) {
670        $dir = dirname(__FILE__) . DIRECTORY_SEPARATOR . $dir;
671        debug("Relative path didn't exist. Trying $dir\n");
672    }
673	# $this->datasourceclasses = array();
674	$dh=@opendir($dir);
675
676	if(!$dh) {	// try to find it with the script, if the relative path fails
677		$srcdir = substr($_SERVER['argv'][0], 0, strrpos($_SERVER['argv'][0], DIRECTORY_SEPARATOR));
678		$dh = opendir($srcdir.DIRECTORY_SEPARATOR.$dir);
679		if ($dh) $dir = $srcdir.DIRECTORY_SEPARATOR.$dir;
680	}
681
682	if ($dh)
683	{
684		while ($file=readdir($dh))
685		{
686			$realfile = $dir . DIRECTORY_SEPARATOR . $file;
687
688			if( is_file($realfile) && preg_match( '/\.php$/', $realfile ) )
689			{
690				debug("Loading $type Plugin class from $file\n");
691
692				include_once( $realfile );
693				$class = preg_replace( "/\.php$/", "", $file );
694				if($type == 'data')
695				{
696					$this->datasourceclasses [$class]= $class;
697					$this->activedatasourceclasses[$class]=1;
698				}
699				if($type == 'pre') $this->preprocessclasses [$class]= $class;
700				if($type == 'post') $this->postprocessclasses [$class]= $class;
701
702				debug("Loaded $type Plugin class $class from $file\n");
703				$this->plugins[$type][$class] = new $class;
704				if(! isset($this->plugins[$type][$class]))
705				{
706					debug("** Failed to create an object for plugin $type/$class\n");
707				}
708				else
709				{
710					debug("Instantiated $class.\n");
711				}
712			}
713			else
714			{
715				debug("Skipping $file\n");
716			}
717		}
718	}
719	else
720	{
721		warn("Couldn't open $type Plugin directory ($dir). Things will probably go wrong. [WMWARN06]\n");
722	}
723}
724
725function DatasourceInit()
726{
727	debug("Running Init() for Data Source Plugins...\n");
728	foreach ($this->datasourceclasses as $ds_class)
729	{
730		// make an instance of the class
731		$dsplugins[$ds_class] = new $ds_class;
732		debug("Running $ds_class"."->Init()\n");
733		# $ret = call_user_func(array($ds_class, 'Init'), $this);
734		assert('isset($this->plugins["data"][$ds_class])');
735
736		$ret = $this->plugins['data'][$ds_class]->Init($this);
737
738		if(! $ret)
739		{
740			debug("Removing $ds_class from Data Source list, since Init() failed\n");
741			$this->activedatasourceclasses[$ds_class]=0;
742			# unset($this->datasourceclasses[$ds_class]);
743		}
744	}
745	debug("Finished Initialising Plugins...\n");
746}
747
748function ProcessTargets()
749{
750	debug("Preprocessing targets\n");
751
752	$allitems = array(&$this->links, &$this->nodes);
753	reset($allitems);
754
755	debug("Preprocessing targets\n");
756
757	while( list($kk,) = each($allitems))
758	{
759		unset($objects);
760		$objects = &$allitems[$kk];
761
762		reset($objects);
763		while (list($k,) = each($objects))
764		{
765			unset($myobj);
766			$myobj = &$objects[$k];
767
768			$type = $myobj->my_type();
769			$name=$myobj->name;
770
771
772			if( ($type=='LINK' && isset($myobj->a)) || ($type=='NODE' && !is_null($myobj->x) ) )
773			{
774				if (count($myobj->targets)>0)
775				{
776					$tindex = 0;
777					foreach ($myobj->targets as $target)
778					{
779						debug ("ProcessTargets: New Target: $target[4]\n");
780						// processstring won't use notes (only hints) for this string
781
782						$targetstring = $this->ProcessString($target[4], $myobj, FALSE, FALSE);
783						if($target[4] != $targetstring) debug("Targetstring is now $targetstring\n");
784
785						// if the targetstring starts with a -, then we're taking this value OFF the aggregate
786						$multiply = 1;
787						if(preg_match("/^-(.*)/",$targetstring,$matches))
788						{
789							$targetstring = $matches[1];
790							$multiply = -1 * $multiply;
791						}
792
793						// if the remaining targetstring starts with a number and a *-, then this is a scale factor
794						if(preg_match("/^(\d+\.?\d*)\*(.*)/",$targetstring,$matches))
795						{
796							$targetstring = $matches[2];
797							$multiply = $multiply * floatval($matches[1]);
798						}
799
800						$matched = FALSE;
801						$matched_by = '';
802						foreach ($this->datasourceclasses as $ds_class)
803						{
804							if(!$matched)
805							{
806								// $recognised = call_user_func(array($ds_class, 'Recognise'), $targetstring);
807								$recognised = $this->plugins['data'][$ds_class]->Recognise($targetstring);
808
809								if( $recognised )
810								{
811									$matched = TRUE;
812									$matched_by = $ds_class;
813
814									if($this->activedatasourceclasses[$ds_class])
815									{
816										$this->plugins['data'][$ds_class]->Register($targetstring, $this, $myobj);
817										if($type == 'NODE')
818										{
819											$this->nodes[$name]->targets[$tindex][1] = $multiply;
820											$this->nodes[$name]->targets[$tindex][0] = $targetstring;
821											$this->nodes[$name]->targets[$tindex][5] = $matched_by;
822										}
823										if($type == 'LINK')
824										{
825											$this->links[$name]->targets[$tindex][1] = $multiply;
826											$this->links[$name]->targets[$tindex][0] = $targetstring;
827											$this->links[$name]->targets[$tindex][5] = $matched_by;
828										}
829									}
830									else
831									{
832										warn("ProcessTargets: $type $name, target: $targetstring on config line $target[3] of $target[2] was recognised as a valid TARGET by a plugin that is unable to run ($ds_class) [WMWARN07]\n");
833									}
834								}
835							}
836						}
837						if(! $matched)
838						{
839							warn("ProcessTargets: $type $name, target: $target[4] on config line $target[3] of $target[2] was not recognised as a valid TARGET [WMWARN08]\n");
840						}
841
842						$tindex++;
843					}
844				}
845			}
846		}
847	}
848}
849
850function ReadData()
851{
852	$this->DatasourceInit();
853
854	debug ("======================================\n");
855	debug("ReadData: Updating link data for all links and nodes\n");
856
857	// we skip readdata completely in sizedebug mode
858	if ($this->sizedebug == 0)
859	{
860		$this->ProcessTargets();
861
862		debug ("======================================\n");
863		debug("Starting prefetch\n");
864		foreach ($this->datasourceclasses as $ds_class)
865		{
866			$this->plugins['data'][$ds_class]->Prefetch();
867		}
868
869		debug ("======================================\n");
870		debug("Starting main collection loop\n");
871
872		$allitems = array(&$this->links, &$this->nodes);
873		reset($allitems);
874
875		while( list($kk,) = each($allitems))
876		{
877			unset($objects);
878			$objects = &$allitems[$kk];
879
880			reset($objects);
881			while (list($k,) = each($objects))
882			{
883				unset($myobj);
884				$myobj = &$objects[$k];
885
886				$type = $myobj->my_type();
887
888				$total_in=0;
889				$total_out=0;
890				$name=$myobj->name;
891				debug ("\n");
892				debug ("ReadData for $type $name: \n");
893
894				if( ($type=='LINK' && isset($myobj->a)) || ($type=='NODE' && !is_null($myobj->x) ) )
895				{
896					if (count($myobj->targets)>0)
897					{
898						$tindex = 0;
899						foreach ($myobj->targets as $target)
900						{
901							debug ("ReadData: New Target: $target[4]\n");
902	#						debug ( var_dump($target));
903
904							$targetstring = $target[0];
905							$multiply = $target[1];
906
907	#						exit();
908
909							$in = 0;
910							$out = 0;
911							$datatime = 0;
912							if ($target[4] != '')
913							{
914								// processstring won't use notes (only hints) for this string
915
916								$targetstring = $this->ProcessString($target[0], $myobj, FALSE, FALSE);
917								if($target[0] != $targetstring) debug("Targetstring is now $targetstring\n");
918								if($multiply != 1) debug("Will multiply result by $multiply\n");
919
920								if($target[0] != "")
921								{
922									$matched_by = $target[5];
923									list($in,$out,$datatime) =  $this->plugins['data'][ $target[5] ]->ReadData($targetstring, $this, $myobj);
924								}
925
926								if (($in === NULL) && ($out === NULL))
927								{
928									$in=0;
929									$out=0;
930									warn
931										("ReadData: $type $name, target: $targetstring on config line $target[3] of $target[2] had no valid data, according to $matched_by\n");
932								}
933								else
934								{
935									if($in === NULL) $in = 0;
936									if($out === NULL) $out = 0;
937								}
938
939								if($multiply != 1) {
940									debug("Pre-multiply: $in $out\n");
941
942									$in = $multiply*$in;
943									$out = $multiply*$out;
944
945									debug("Post-multiply: $in $out\n");
946								}
947
948								$total_in=$total_in + $in;
949								$total_out=$total_out + $out;
950								debug("Aggregate so far: $total_in $total_out\n");
951								# keep a track of the range of dates for data sources (mainly for MRTG/textfile based DS)
952								if($datatime > 0)
953								{
954									if($this->max_data_time==NULL || $datatime > $this->max_data_time) $this->max_data_time = $datatime;
955									if($this->min_data_time==NULL || $datatime < $this->min_data_time) $this->min_data_time = $datatime;
956
957									debug("DataTime MINMAX: ".$this->min_data_time." -> ".$this->max_data_time."\n");
958								}
959
960							}
961							$tindex++;
962						}
963
964						debug ("ReadData complete for $type $name: $total_in $total_out\n");
965					}
966					else
967					{
968						debug("ReadData: No targets for $type $name\n");
969					}
970				}
971				else
972				{
973					debug("ReadData: Skipping $type $name that looks like a template\n.");
974				}
975
976				# $this->links[$name]->bandwidth_in=$total_in;
977				# $this->links[$name]->bandwidth_out=$total_out;
978				$myobj->bandwidth_in = $total_in;
979				$myobj->bandwidth_out = $total_out;
980
981				if($type == 'LINK' && $myobj->duplex=='half')
982				{
983					// in a half duplex link, in and out share a common bandwidth pool, so percentages need to include both
984					debug("Calculating percentage using half-duplex\n");
985					$myobj->outpercent = (($total_in + $total_out) / ($myobj->max_bandwidth_out)) * 100;
986					$myobj->inpercent = (($total_out + $total_in) / ($myobj->max_bandwidth_in)) * 100;
987					if($myobj->max_bandwidth_out != $myobj->max_bandwidth_in)
988					{
989						warn("ReadData: $type $name: You're using asymmetric bandwidth AND half-duplex in the same link. That makes no sense. [WMWARN44]\n");
990					}
991				}
992				else
993				{
994					$myobj->outpercent = (($total_out) / ($myobj->max_bandwidth_out)) * 100;
995					$myobj->inpercent = (($total_in) / ($myobj->max_bandwidth_in)) * 100;
996				}
997
998				# print $myobj->name."=>".$myobj->inpercent."%/".$myobj->outpercent."\n";
999
1000                                $warn_in = true;
1001                                $warn_out = true;
1002                                if($type=='NODE' && $myobj->scalevar =='in') $warn_out = false;
1003                                if($type=='NODE' && $myobj->scalevar =='out') $warn_in = false;
1004
1005				if($myobj->scaletype == 'percent')
1006				{
1007					list($incol,$inscalekey,$inscaletag) = $this->NewColourFromPercent($myobj->inpercent,$myobj->usescale,$myobj->name, TRUE, $warn_in);
1008					list($outcol,$outscalekey, $outscaletag) = $this->NewColourFromPercent($myobj->outpercent,$myobj->usescale,$myobj->name, TRUE, $warn_out);
1009				}
1010				else
1011				{
1012					// use absolute values, if that's what is requested
1013					list($incol,$inscalekey,$inscaletag) = $this->NewColourFromPercent($myobj->bandwidth_in,$myobj->usescale,$myobj->name, FALSE, $warn_in);
1014					list($outcol,$outscalekey, $outscaletag) = $this->NewColourFromPercent($myobj->bandwidth_out,$myobj->usescale,$myobj->name, FALSE, $warn_out);
1015				}
1016
1017				$myobj->add_note("inscalekey",$inscalekey);
1018				$myobj->add_note("outscalekey",$outscalekey);
1019
1020				$myobj->add_note("inscaletag",$inscaletag);
1021				$myobj->add_note("outscaletag",$outscaletag);
1022
1023				$myobj->add_note("inscalecolor",$incol->as_html());
1024				$myobj->add_note("outscalecolor",$outcol->as_html());
1025
1026				$myobj->colours[IN] = $incol;
1027				$myobj->colours[OUT] = $outcol;
1028
1029				### warn("TAGS (setting) |$inscaletag| |$outscaletag| \n");
1030
1031				debug ("ReadData: Setting $total_in,$total_out\n");
1032				unset($myobj);
1033			}
1034		}
1035		debug ("ReadData Completed.\n");
1036		debug("------------------------------\n");
1037	}
1038}
1039
1040// nodename is a vestigal parameter, from the days when nodes were just big labels
1041function DrawLabelRotated($im, $x, $y, $angle, $text, $font, $padding, $linkname, $textcolour, $bgcolour, $outlinecolour, &$map, $direction)
1042{
1043	list($strwidth, $strheight)=$this->myimagestringsize($font, $text);
1044
1045	if(abs($angle)>90)  $angle -= 180;
1046	if($angle < -180) $angle +=360;
1047
1048	$rangle = -deg2rad($angle);
1049
1050	$extra=3;
1051
1052	$x1= $x - ($strwidth / 2) - $padding - $extra;
1053	$x2= $x + ($strwidth / 2) + $padding + $extra;
1054	$y1= $y - ($strheight / 2) - $padding - $extra;
1055	$y2= $y + ($strheight / 2) + $padding + $extra;
1056
1057	// a box. the last point is the start point for the text.
1058	$points = array($x1,$y1, $x1,$y2, $x2,$y2, $x2,$y1,   $x-$strwidth/2, $y+$strheight/2 + 1);
1059	$npoints = count($points)/2;
1060
1061	RotateAboutPoint($points, $x,$y, $rangle);
1062
1063	if ($bgcolour != array
1064		(
1065			-1,
1066			-1,
1067			-1
1068		))
1069	{
1070		$bgcol=myimagecolorallocate($im, $bgcolour[0], $bgcolour[1], $bgcolour[2]);
1071		# imagefilledrectangle($im, $x1, $y1, $x2, $y2, $bgcol);
1072		wimagefilledpolygon($im,$points,4,$bgcol);
1073	}
1074
1075	if ($outlinecolour != array
1076		(
1077			-1,
1078			-1,
1079			-1
1080		))
1081	{
1082		$outlinecol=myimagecolorallocate($im, $outlinecolour[0], $outlinecolour[1], $outlinecolour[2]);
1083		# imagerectangle($im, $x1, $y1, $x2, $y2, $outlinecol);
1084		wimagepolygon($im,$points,4,$outlinecol);
1085	}
1086
1087	$textcol=myimagecolorallocate($im, $textcolour[0], $textcolour[1], $textcolour[2]);
1088	$this->myimagestring($im, $font, $points[8], $points[9], $text, $textcol,$angle);
1089
1090	$areaname = "LINK:L".$map->links[$linkname]->id.':'.($direction+2);
1091
1092	// the rectangle is about half the size in the HTML, and easier to optimise/detect in the browser
1093	if($angle==0)
1094	{
1095		$map->imap->addArea("Rectangle", $areaname, '', array($x1, $y1, $x2, $y2));
1096		debug ("Adding Rectangle imagemap for $areaname\n");
1097	}
1098	else
1099	{
1100		$map->imap->addArea("Polygon", $areaname, '', $points);
1101		debug ("Adding Poly imagemap for $areaname\n");
1102	}
1103
1104}
1105
1106function ColourFromPercent($image, $percent,$scalename="DEFAULT",$name="")
1107{
1108	$col = NULL;
1109	$tag = '';
1110
1111	$nowarn_clipping = intval($this->get_hint("nowarn_clipping"));
1112	$nowarn_scalemisses = intval($this->get_hint("nowarn_scalemisses"));
1113
1114	$bt = debug_backtrace();
1115	$function = (isset($bt[1]['function']) ? $bt[1]['function'] : '');
1116	print "$function calls ColourFromPercent\n";
1117
1118	exit();
1119
1120	if(isset($this->colours[$scalename]))
1121	{
1122		$colours=$this->colours[$scalename];
1123
1124		if ($percent > 100)
1125		{
1126			if($nowarn_clipping==0) warn ("ColourFromPercent: Clipped $name $percent% to 100% [WMWARN33]\n");
1127			$percent=100;
1128		}
1129
1130		foreach ($colours as $key => $colour)
1131		{
1132			if (($percent >= $colour['bottom']) and ($percent <= $colour['top']))
1133			{
1134				if(isset($colour['tag'])) $tag = $colour['tag'];
1135
1136				// we get called early now, so might not need to actually allocate a colour
1137				if(isset($image))
1138				{
1139					if (isset($colour['red2']))
1140					{
1141						if($colour["bottom"] == $colour["top"])
1142						{
1143							$ratio = 0;
1144						}
1145						else
1146						{
1147							$ratio=($percent - $colour["bottom"]) / ($colour["top"] - $colour["bottom"]);
1148						}
1149
1150						$r=$colour["red1"] + ($colour["red2"] - $colour["red1"]) * $ratio;
1151						$g=$colour["green1"] + ($colour["green2"] - $colour["green1"]) * $ratio;
1152						$b=$colour["blue1"] + ($colour["blue2"] - $colour["blue1"]) * $ratio;
1153
1154						$col = myimagecolorallocate($image, $r, $g, $b);
1155					}
1156					else {
1157						$r=$colour["red1"];
1158						$g=$colour["green1"];
1159						$b=$colour["blue1"];
1160
1161						$col = myimagecolorallocate($image, $r, $g, $b);
1162						# $col = $colour['gdref1'];
1163					}
1164					debug("CFPC $name $tag $key $r $g $b\n");
1165				}
1166
1167				### warn(">>TAGS CFPC $tag\n");
1168
1169				return(array($col,$key,$tag));
1170			}
1171		}
1172	}
1173	else
1174	{
1175		if($scalename != 'none')
1176		{
1177			warn("ColourFromPercent: Attempted to use non-existent scale: $scalename for $name [WMWARN09]\n");
1178		}
1179		else
1180		{
1181			return array($this->white,'','');
1182		}
1183	}
1184
1185	// you'll only get grey for a COMPLETELY quiet link if there's no 0 in the SCALE lines
1186	if ($percent == 0) { return array($this->grey,'',''); }
1187
1188	// and you'll only get white for a link with no colour assigned
1189	if($nowarn_scalemisses==0) warn("ColourFromPercent: Scale $scalename doesn't cover $percent% for $name [WMWARN29]\n");
1190	return array($this->white,'','');
1191}
1192
1193function NewColourFromPercent($value,$scalename="DEFAULT",$name="",$is_percent=TRUE, $scale_warning=TRUE)
1194{
1195	$col = new Colour(0,0,0);
1196	$tag = '';
1197	$matchsize = NULL;
1198
1199	$nowarn_clipping = intval($this->get_hint("nowarn_clipping"));
1200	$nowarn_scalemisses = (!$scale_warning) || intval($this->get_hint("nowarn_scalemisses"));
1201
1202	if(isset($this->colours[$scalename]))
1203	{
1204		$colours=$this->colours[$scalename];
1205
1206		if ($is_percent && $value > 100)
1207		{
1208			if($nowarn_clipping==0) warn ("NewColourFromPercent: Clipped $value% to 100% for item $name [WMWARN33]\n");
1209			$value = 100;
1210		}
1211
1212		if ($is_percent && $value < 0)
1213		{
1214			if($nowarn_clipping==0) warn ("NewColourFromPercent: Clipped $value% to 0% for item $name [WMWARN34]\n");
1215			$value = 0;
1216		}
1217
1218		foreach ($colours as $key => $colour)
1219		{
1220			if ( (!isset($colour['special']) || $colour['special'] == 0) and ($value >= $colour['bottom']) and ($value <= $colour['top']))
1221			{
1222				$range = $colour['top'] - $colour['bottom'];
1223				if (isset($colour['red2']))
1224				{
1225					if($colour["bottom"] == $colour["top"])
1226					{
1227						$ratio = 0;
1228					}
1229					else
1230					{
1231						$ratio=($value - $colour["bottom"]) / ($colour["top"] - $colour["bottom"]);
1232					}
1233
1234					$r=$colour["red1"] + ($colour["red2"] - $colour["red1"]) * $ratio;
1235					$g=$colour["green1"] + ($colour["green2"] - $colour["green1"]) * $ratio;
1236					$b=$colour["blue1"] + ($colour["blue2"] - $colour["blue1"]) * $ratio;
1237				}
1238				else {
1239					$r=$colour["red1"];
1240					$g=$colour["green1"];
1241					$b=$colour["blue1"];
1242
1243					# $col = new Colour($r, $g, $b);
1244					# $col = $colour['gdref1'];
1245				}
1246
1247				// change in behaviour - with multiple matching ranges for a value, the smallest range wins
1248				if( is_null($matchsize) || ($range < $matchsize) )
1249				{
1250					$col = new Colour($r, $g, $b);
1251					$matchsize = $range;
1252				}
1253
1254				if(isset($colour['tag'])) $tag = $colour['tag'];
1255				#### warn(">>NCFPC TAGS $tag\n");
1256				debug("NCFPC $name $scalename $value '$tag' $key $r $g $b\n");
1257
1258				return(array($col,$key,$tag));
1259			}
1260		}
1261	}
1262	else
1263	{
1264		if($scalename != 'none')
1265		{
1266			warn("ColourFromPercent: Attempted to use non-existent scale: $scalename for item $name [WMWARN09]\n");
1267		}
1268		else
1269		{
1270			return array(new Colour(255,255,255),'','');
1271		}
1272	}
1273
1274	// shouldn't really get down to here if there's a complete SCALE
1275
1276	// you'll only get grey for a COMPLETELY quiet link if there's no 0 in the SCALE lines
1277	if ($value == 0) { return array(new Colour(192,192,192),'',''); }
1278
1279	if($nowarn_scalemisses==0) warn("NewColourFromPercent: Scale $scalename doesn't include a line for $value".($is_percent ? "%" : "")." while drawing item $name [WMWARN29]\n");
1280
1281	// and you'll only get white for a link with no colour assigned
1282	return array(new Colour(255,255,255),'','');
1283}
1284
1285
1286function coloursort($a, $b)
1287{
1288	if ($a['bottom'] == $b['bottom'])
1289	{
1290		if($a['top'] < $b['top']) { return -1; };
1291		if($a['top'] > $b['top']) { return 1; };
1292		return 0;
1293	}
1294
1295	if ($a['bottom'] < $b['bottom']) { return -1; }
1296
1297	return 1;
1298}
1299
1300function FindScaleExtent($scalename="DEFAULT")
1301{
1302	$max = -999999999999999999999;
1303	$min = - $max;
1304
1305	if(isset($this->colours[$scalename]))
1306	{
1307		$colours=$this->colours[$scalename];
1308
1309		foreach ($colours as $key => $colour)
1310		{
1311			if(! $colour['special'])
1312			{
1313				$min = min($colour['bottom'], $min);
1314				$max = max($colour['top'],  $max);
1315			}
1316		}
1317	}
1318	else
1319	{
1320		warn("FindScaleExtent: non-existent SCALE $scalename [WMWARN43]\n");
1321	}
1322	return array($min, $max);
1323}
1324
1325function DrawLegend_Horizontal($im,$scalename="DEFAULT",$width=400)
1326{
1327	$title=$this->keytext[$scalename];
1328
1329	$colours=$this->colours[$scalename];
1330	$nscales=$this->numscales[$scalename];
1331
1332	debug("Drawing $nscales colours into SCALE\n");
1333
1334	$font=$this->keyfont;
1335
1336	# $x=$this->keyx[$scalename];
1337	# $y=$this->keyy[$scalename];
1338	$x = 0;
1339	$y = 0;
1340
1341	# $width = 400;
1342	$scalefactor = $width/100;
1343
1344	list($tilewidth, $tileheight)=$this->myimagestringsize($font, "100%");
1345	$box_left = $x;
1346	# $box_left = 0;
1347	$scale_left = $box_left + 4 + $scalefactor/2;
1348	$box_right = $scale_left + $width + $tilewidth + 4 + $scalefactor/2;
1349	$scale_right = $scale_left + $width;
1350
1351	$box_top = $y;
1352	# $box_top = 0;
1353	$scale_top = $box_top + $tileheight + 6;
1354	$scale_bottom = $scale_top + $tileheight * 1.5;
1355	$box_bottom = $scale_bottom + $tileheight * 2 + 6;
1356
1357	$scale_im = imagecreatetruecolor($box_right+1, $box_bottom+1);
1358	$scale_ref = 'gdref_legend_'.$scalename;
1359	$this->AllocateScaleColours($scale_im,$scale_ref);
1360
1361	wimagefilledrectangle($scale_im, $box_left, $box_top, $box_right, $box_bottom,
1362		$this->colours['DEFAULT']['KEYBG'][$scale_ref]);
1363	wimagerectangle($scale_im, $box_left, $box_top, $box_right, $box_bottom,
1364		$this->colours['DEFAULT']['KEYOUTLINE'][$scale_ref]);
1365
1366	$this->myimagestring($scale_im, $font, $scale_left, $scale_bottom + $tileheight * 2 + 2 , $title,
1367		$this->colours['DEFAULT']['KEYTEXT'][$scale_ref]);
1368
1369	for($p=0;$p<=100;$p++)
1370	{
1371		$dx = $p*$scalefactor;
1372
1373		if( ($p % 25) == 0)
1374		{
1375			imageline($scale_im, $scale_left + $dx, $scale_top - $tileheight,
1376				$scale_left + $dx, $scale_bottom + $tileheight,
1377				$this->colours['DEFAULT']['KEYTEXT'][$scale_ref]);
1378			$labelstring=sprintf("%d%%", $p);
1379			$this->myimagestring($scale_im, $font, $scale_left + $dx + 2, $scale_top - 2, $labelstring,
1380				$this->colours['DEFAULT']['KEYTEXT'][$scale_ref]);
1381		}
1382
1383		list($col,$junk) = $this->NewColourFromPercent($p,$scalename);
1384		if($col->is_real())
1385		{
1386			$cc = $col->gdallocate($scale_im);
1387			wimagefilledrectangle($scale_im, $scale_left + $dx - $scalefactor/2, $scale_top,
1388				$scale_left + $dx + $scalefactor/2, $scale_bottom,
1389				$cc);
1390		}
1391	}
1392
1393	imagecopy($im,$scale_im,$this->keyx[$scalename],$this->keyy[$scalename],0,0,imagesx($scale_im),imagesy($scale_im));
1394	$this->keyimage[$scalename] = $scale_im;
1395
1396    $rx = $this->keyx[$scalename];
1397    $ry = $this->keyy[$scalename];
1398
1399	$this->imap->addArea("Rectangle", "LEGEND:$scalename", '',
1400		array($rx+$box_left, $ry+$box_top, $rx+$box_right, $ry+$box_bottom));
1401}
1402
1403function DrawLegend_Vertical($im,$scalename="DEFAULT",$height=400,$inverted=false)
1404{
1405	$title=$this->keytext[$scalename];
1406
1407	$colours=$this->colours[$scalename];
1408	$nscales=$this->numscales[$scalename];
1409
1410	debug("Drawing $nscales colours into SCALE\n");
1411
1412	$font=$this->keyfont;
1413
1414	$x=$this->keyx[$scalename];
1415	$y=$this->keyy[$scalename];
1416
1417	# $height = 400;
1418	$scalefactor = $height/100;
1419
1420	list($tilewidth, $tileheight)=$this->myimagestringsize($font, "100%");
1421
1422	# $box_left = $x;
1423	# $box_top = $y;
1424	$box_left = 0;
1425	$box_top = 0;
1426
1427	$scale_left = $box_left+$scalefactor*2 +4 ;
1428	$scale_right = $scale_left + $tileheight*2;
1429	$box_right = $scale_right + $tilewidth + $scalefactor*2 + 4;
1430
1431	list($titlewidth,$titleheight) = $this->myimagestringsize($font,$title);
1432	if( ($box_left + $titlewidth + $scalefactor*3) > $box_right)
1433	{
1434		$box_right = $box_left + $scalefactor*4 + $titlewidth;
1435	}
1436
1437	$scale_top = $box_top + 4 + $scalefactor + $tileheight*2;
1438	$scale_bottom = $scale_top + $height;
1439	$box_bottom = $scale_bottom + $scalefactor + $tileheight/2 + 4;
1440
1441	$scale_im = imagecreatetruecolor($box_right+1, $box_bottom+1);
1442	$scale_ref = 'gdref_legend_'.$scalename;
1443	$this->AllocateScaleColours($scale_im,$scale_ref);
1444
1445	wimagefilledrectangle($scale_im, $box_left, $box_top, $box_right, $box_bottom,
1446		$this->colours['DEFAULT']['KEYBG']['gdref1']);
1447	wimagerectangle($scale_im, $box_left, $box_top, $box_right, $box_bottom,
1448		$this->colours['DEFAULT']['KEYOUTLINE']['gdref1']);
1449
1450	$this->myimagestring($scale_im, $font, $scale_left-$scalefactor, $scale_top - $tileheight , $title,
1451		$this->colours['DEFAULT']['KEYTEXT']['gdref1']);
1452
1453	$updown = 1;
1454	if($inverted) $updown = -1;
1455
1456
1457	for($p=0;$p<=100;$p++)
1458	{
1459		if($inverted)
1460		{
1461			$dy = (100-$p) * $scalefactor;
1462		}
1463		else
1464		{
1465			$dy = $p*$scalefactor;
1466		}
1467
1468		if( ($p % 25) == 0)
1469		{
1470			imageline($scale_im, $scale_left - $scalefactor, $scale_top + $dy,
1471				$scale_right + $scalefactor, $scale_top + $dy,
1472				$this->colours['DEFAULT']['KEYTEXT'][$scale_ref]);
1473			$labelstring=sprintf("%d%%", $p);
1474			$this->myimagestring($scale_im, $font, $scale_right + $scalefactor*2 , $scale_top + $dy + $tileheight/2,
1475				$labelstring,  $this->colours['DEFAULT']['KEYTEXT'][$scale_ref]);
1476		}
1477
1478		list($col,$junk) = $this->NewColourFromPercent($p,$scalename);
1479		if( $col->is_real())
1480		{
1481			$cc = $col->gdallocate($scale_im);
1482			wimagefilledrectangle($scale_im, $scale_left, $scale_top + $dy - $scalefactor/2,
1483				$scale_right, $scale_top + $dy + $scalefactor/2,
1484				$cc);
1485		}
1486	}
1487
1488	imagecopy($im,$scale_im,$this->keyx[$scalename],$this->keyy[$scalename],0,0,imagesx($scale_im),imagesy($scale_im));
1489	$this->keyimage[$scalename] = $scale_im;
1490
1491	$rx = $this->keyx[$scalename];
1492	$ry = $this->keyy[$scalename];
1493	$this->imap->addArea("Rectangle", "LEGEND:$scalename", '',
1494		array($rx+$box_left, $ry+$box_top, $rx+$box_right, $ry+$box_bottom));
1495}
1496
1497function DrawLegend_Classic($im,$scalename="DEFAULT",$use_tags=FALSE)
1498{
1499	$title=$this->keytext[$scalename];
1500
1501	$colours=$this->colours[$scalename];
1502	usort($colours, array("Weathermap", "coloursort"));
1503
1504	$nscales=$this->numscales[$scalename];
1505
1506	debug("Drawing $nscales colours into SCALE\n");
1507
1508	$hide_zero = intval($this->get_hint("key_hidezero_".$scalename));
1509	$hide_percent = intval($this->get_hint("key_hidepercent_".$scalename));
1510
1511	// did we actually hide anything?
1512	$hid_zero = FALSE;
1513	if( ($hide_zero == 1) && isset($colours['0_0']) )
1514	{
1515		$nscales--;
1516		$hid_zero = TRUE;
1517	}
1518
1519	$font=$this->keyfont;
1520
1521	$x=$this->keyx[$scalename];
1522	$y=$this->keyy[$scalename];
1523
1524	list($tilewidth, $tileheight)=$this->myimagestringsize($font, "MMMM");
1525	$tileheight=$tileheight * 1.1;
1526	$tilespacing=$tileheight + 2;
1527
1528	if (($this->keyx[$scalename] >= 0) && ($this->keyy[$scalename] >= 0))
1529	{
1530
1531		# $minwidth = imagefontwidth($font) * strlen('XX 100%-100%')+10;
1532		# $boxwidth = imagefontwidth($font) * strlen($title) + 10;
1533		list($minwidth, $junk)=$this->myimagestringsize($font, 'MMMM 100%-100%');
1534		list($minminwidth, $junk)=$this->myimagestringsize($font, 'MMMM ');
1535		list($boxwidth, $junk)=$this->myimagestringsize($font, $title);
1536
1537		if($use_tags)
1538		{
1539			$max_tag = 0;
1540			foreach ($colours as $colour)
1541			{
1542				if ( isset($colour['tag']) )
1543				{
1544					list($w, $junk)=$this->myimagestringsize($font, $colour['tag']);
1545					# print $colour['tag']." $w \n";
1546					if($w > $max_tag) $max_tag = $w;
1547				}
1548			}
1549
1550			// now we can tweak the widths, appropriately to allow for the tag strings
1551			# print "$max_tag > $minwidth?\n";
1552			if( ($max_tag + $minminwidth) > $minwidth) $minwidth = $minminwidth + $max_tag;
1553			# print "minwidth is now $minwidth\n";
1554		}
1555
1556		$minwidth+=10;
1557		$boxwidth+=10;
1558
1559		if ($boxwidth < $minwidth) { $boxwidth=$minwidth; }
1560
1561		$boxheight=$tilespacing * ($nscales + 1) + 10;
1562
1563		$boxx=$x; $boxy=$y;
1564		$boxx=0;
1565		$boxy=0;
1566
1567		// allow for X11-style negative positioning
1568		if ($boxx < 0) { $boxx+=$this->width; }
1569
1570		if ($boxy < 0) { $boxy+=$this->height; }
1571
1572		$scale_im = imagecreatetruecolor($boxwidth+1, $boxheight+1);
1573		$scale_ref = 'gdref_legend_'.$scalename;
1574		$this->AllocateScaleColours($scale_im,$scale_ref);
1575
1576		wimagefilledrectangle($scale_im, $boxx, $boxy, $boxx + $boxwidth, $boxy + $boxheight,
1577			$this->colours['DEFAULT']['KEYBG'][$scale_ref]);
1578		wimagerectangle($scale_im, $boxx, $boxy, $boxx + $boxwidth, $boxy + $boxheight,
1579			$this->colours['DEFAULT']['KEYOUTLINE'][$scale_ref]);
1580		$this->myimagestring($scale_im, $font, $boxx + 4, $boxy + 4 + $tileheight, $title,
1581			$this->colours['DEFAULT']['KEYTEXT'][$scale_ref]);
1582
1583		$i=1;
1584
1585		foreach ($colours as $colour)
1586		{
1587			if (!isset($colour['special']) || $colour['special'] == 0)
1588			// if ( 1==1 || $colour['bottom'] >= 0)
1589			{
1590				// pick a value in the middle...
1591				$value = ($colour['bottom'] + $colour['top']) / 2;
1592				debug(sprintf("%f-%f (%f)  %d %d %d\n", $colour['bottom'], $colour['top'], $value, $colour['red1'], $colour['green1'], $colour['blue1']));
1593
1594				#  debug("$i: drawing\n");
1595				if( ($hide_zero == 0) || $colour['key'] != '0_0')
1596				{
1597					$y=$boxy + $tilespacing * $i + 8;
1598					$x=$boxx + 6;
1599
1600					$fudgefactor = 0;
1601					if( $hid_zero && $colour['bottom']==0 )
1602					{
1603						// calculate a small offset that can be added, which will hide the zero-value in a
1604						// gradient, but not make the scale incorrect. A quarter of a pixel should do it.
1605						$fudgefactor = ($colour['top'] - $colour['bottom'])/($tilewidth*4);
1606						# warn("FUDGING $fudgefactor\n");
1607					}
1608
1609					// if it's a gradient, red2 is defined, and we need to sweep the values
1610					if (isset($colour['red2']))
1611					{
1612						for ($n=0; $n <= $tilewidth; $n++)
1613						{
1614							$value
1615								=  $fudgefactor + $colour['bottom'] + ($n / $tilewidth) * ($colour['top'] - $colour['bottom']);
1616							list($ccol,$junk) = $this->NewColourFromPercent($value, $scalename, "", FALSE);
1617							$col = $ccol->gdallocate($scale_im);
1618							wimagefilledrectangle($scale_im, $x + $n, $y, $x + $n, $y + $tileheight,
1619								$col);
1620						}
1621					}
1622					else
1623					{
1624						// pick a value in the middle...
1625						//$value = ($colour['bottom'] + $colour['top']) / 2;
1626						list($ccol,$junk) = $this->NewColourFromPercent($value, $scalename, "", FALSE);
1627						$col = $ccol->gdallocate($scale_im);
1628						wimagefilledrectangle($scale_im, $x, $y, $x + $tilewidth, $y + $tileheight,
1629							$col);
1630					}
1631
1632					if($use_tags)
1633					{
1634						$labelstring = "";
1635						if(isset($colour['tag'])) $labelstring = $colour['tag'];
1636					}
1637					else
1638					{
1639						$labelstring=sprintf("%s-%s", $colour['bottom'], $colour['top']);
1640						if($hide_percent==0) { $labelstring.="%"; }
1641					}
1642
1643					$this->myimagestring($scale_im, $font, $x + 4 + $tilewidth, $y + $tileheight, $labelstring,
1644						$this->colours['DEFAULT']['KEYTEXT'][$scale_ref]);
1645					$i++;
1646				}
1647				imagecopy($im,$scale_im,$this->keyx[$scalename],$this->keyy[$scalename],0,0,imagesx($scale_im),imagesy($scale_im));
1648				$this->keyimage[$scalename] = $scale_im;
1649
1650			}
1651		}
1652
1653		$this->imap->addArea("Rectangle", "LEGEND:$scalename", '',
1654			array($this->keyx[$scalename], $this->keyy[$scalename], $this->keyx[$scalename] + $boxwidth, $this->keyy[$scalename] + $boxheight));
1655		# $this->imap->setProp("href","#","LEGEND");
1656		# $this->imap->setProp("extrahtml","onclick=\"position_legend();\"","LEGEND");
1657
1658	}
1659}
1660
1661function DrawTimestamp($im, $font, $colour, $which="")
1662{
1663	// add a timestamp to the corner, so we can tell if it's all being updated
1664	# $datestring = "Created: ".date("M d Y H:i:s",time());
1665	# $this->datestamp=strftime($this->stamptext, time());
1666
1667	switch($which)
1668	{
1669		case "MIN":
1670			$stamp = strftime($this->minstamptext, $this->min_data_time);
1671			$pos_x = $this->mintimex;
1672			$pos_y = $this->mintimey;
1673			break;
1674		case "MAX":
1675			$stamp = strftime($this->maxstamptext, $this->max_data_time);
1676			$pos_x = $this->maxtimex;
1677			$pos_y = $this->maxtimey;
1678			break;
1679		default:
1680			$stamp = $this->datestamp;
1681			$pos_x = $this->timex;
1682			$pos_y = $this->timey;
1683			break;
1684	}
1685
1686	list($boxwidth, $boxheight)=$this->myimagestringsize($font, $stamp);
1687
1688	$x=$this->width - $boxwidth;
1689	$y=$boxheight;
1690
1691	if (($pos_x != 0) && ($pos_y != 0))
1692	{
1693		$x = $pos_x;
1694		$y = $pos_y;
1695	}
1696
1697	$this->myimagestring($im, $font, $x, $y, $stamp, $colour);
1698	$this->imap->addArea("Rectangle", $which."TIMESTAMP", '', array($x, $y, $x + $boxwidth, $y - $boxheight));
1699}
1700
1701function DrawTitle($im, $font, $colour)
1702{
1703	$string = $this->ProcessString($this->title,$this);
1704
1705	if($this->get_hint('screenshot_mode')==1)  $string= screenshotify($string);
1706
1707	list($boxwidth, $boxheight)=$this->myimagestringsize($font, $string);
1708
1709	$x=10;
1710	$y=$this->titley - $boxheight;
1711
1712	if (($this->titlex >= 0) && ($this->titley >= 0))
1713	{
1714		$x=$this->titlex;
1715		$y=$this->titley;
1716	}
1717
1718	$this->myimagestring($im, $font, $x, $y, $string, $colour);
1719
1720	$this->imap->addArea("Rectangle", "TITLE", '', array($x, $y, $x + $boxwidth, $y - $boxheight));
1721}
1722
1723function ReadConfigNG($input, $is_include=FALSE, $initial_context="GLOBAL")
1724{
1725	$valid_commands = array(
1726			"GLOBAL.set", "LINK.set", "NODE.set",
1727			"GLOBAL.#","LINK.#","NODE.#",
1728			"GLOBAL.include", "NODE.include", "LINK.include",
1729
1730			"GLOBAL.width", "GLOBAL.height", "GLOBAL.background",
1731			"GLOBAL.scale", "GLOBAL.title", "GLOBAL.titlepos",
1732			"GLOBAL.fontdefine",
1733			"GLOBAL.keystyle", "GLOBAL.titlecolor", "GLOBAL.timecolor",
1734			"GLOBAL.titlefont", "GLOBAL.timefont", "GLOBAL.htmloutputfile",
1735			"GLOBAL.htmlstyle", "GLOBAL.imageoutputfile", "GLOBAL.keyfont",
1736			"GLOBAL.keytextcolor", "GLOBAL.keyoutlinecolor", "GLOBAL.keybgcolor",
1737			"GLOBAL.bgcolor",
1738
1739			"SCALE.keypos", "SCALE.keystyle", "SCALE.scale",
1740
1741			"LINK.width", "LINK.link", "LINK.nodes",
1742			"LINK.target", "LINK.usescale", "LINK.infourl",
1743			"LINK.linkstyle", "LINK.overlibcaption", "LINK.inoverlibcaption", "LINK.outoverlibcaption",
1744			"LINK.inoverlibgraph", "LINK.outoverlibgraph",
1745			"LINK.overlibgraph", "LINK.overlibwidth", "LINK.overlibheight",
1746			"LINK.bwlabel", "LINK.via", "LINK.zorder", "LINK.outlinecolor",
1747			"LINK.notes", "LINK.innotes", "LINK.outnotes","LINK.ininfourl", "LINK.outinfourl",
1748			"LINK.bwstyle", "LINK.template", "LINK.splitpos", "LINK.bwlabelpos", "LINK.incomment", "LINK.outcomment",
1749			"LINK.viastyle", "LINK.bandwidth", "LINK.inbwformat", "LINK.outbwformat",
1750			"LINK.commentstyle", "LINK.commentfont", "LINK.commentfontcolor", "LINK.bwfont",
1751
1752			"NODE.icon", "NODE.target", "NODE.position", "NODE.infourl", "NODE.overlibgraph",
1753			"NODE.zorder", "NODE.label", "NODE.template", "NODE.labelbgcolor",
1754			"NODE.maxvalue",
1755			"NODE.labeloutlinecolor", "NODE.aiconoutlinecolor", "NODE.aiconfillcolor", "NODE.usescale",
1756			"NODE.labelfontcolor", "NODE.labelfont", "NODE.labelangle", "NODE.labelfontshadowcolor",
1757			"NODE.node", "NODE.overlibwidth", "NODE.overlibheight", "NODE.labeloffset"
1758		);
1759
1760
1761	if( (strchr($input,"\n")!=FALSE) || (strchr($input,"\r")!=FALSE ) )
1762	{
1763		 debug("ReadConfig Detected that this is a config fragment.\n");
1764			 // strip out any Windows line-endings that have gotten in here
1765			 $input=str_replace("\r", "", $input);
1766			 $lines = split("/n",$input);
1767			 $filename = "{text insert}";
1768	}
1769	else
1770	{
1771		debug("ReadConfig Detected that this is a config filename.\n");
1772		 $filename = $input;
1773
1774		$fd=fopen($filename, "r");
1775
1776		if ($fd)
1777		{
1778				while (!feof($fd))
1779				{
1780					$buffer=fgets($fd, 4096);
1781					// strip out any Windows line-endings that have gotten in here
1782					$buffer=str_replace("\r", "", $buffer);
1783					$lines[] = $buffer;
1784				}
1785				fclose($fd);
1786		}
1787	}
1788
1789	$linecount = 0;
1790	$context = $initial_context;
1791
1792	foreach($lines as $buffer)
1793	{
1794		$linematched=0;
1795		$linecount++;
1796		$nextcontext = "";
1797		$key = "";
1798
1799		$buffer = trim($buffer);
1800		// alternative for use later where quoted strings are more useful
1801		$args = ParseString($buffer);
1802
1803		if(sizeof($args) > 0)
1804		{
1805			$linematched++;
1806			$cmd = strtolower(array_shift($args));
1807
1808			if($cmd == 'include')
1809			{
1810				$this->ReadConfigNG($args[0],TRUE, $context);
1811			}
1812			elseif($cmd == 'node')
1813			{
1814				$context = "NODE.".$args[0];
1815			}
1816			elseif($cmd == 'link')
1817			{
1818				$context = "LINK.".$args[0];
1819				$vcount = 0;	# reset the via-number counter, it's a new link
1820			}
1821			elseif($cmd == 'scale' || $cmd == 'keystyle' || $cmd == 'keypos')
1822			{
1823				if( preg_match("/^[0-9\-]+/i",$args[0]) )
1824				{
1825					$scalename = "DEFAULT";
1826				}
1827				else
1828				{
1829					$scalename = array_shift($args);
1830				}
1831				if($cmd=="scale") $key = $args[0]."_".$args[1];
1832				$nextcontext = $context;
1833				$context = "SCALE.".$scalename;
1834			}
1835
1836			array_unshift($args,$cmd);
1837
1838			if($context == 'GLOBAL')
1839	 		{
1840				$ctype='GLOBAL';
1841			}
1842			else
1843			{
1844				list($ctype,$junk) = split("\\.", $context, 2);
1845			}
1846
1847			$lookup = $ctype.".".$cmd;
1848
1849			// Some things (scales, mainly) might define special keys
1850			// the key should be unique for that object
1851			// most (all?) things for a link or node are one-offs.
1852			if($key == "") $key = $cmd;
1853			if($cmd == 'set' || $cmd == 'fontdefine') $key .= "_".$args[1];
1854			if($cmd == 'via')
1855			{
1856				$key .= "_".$vcount;
1857				$vcount++;
1858			}
1859
1860			# everything else
1861			if( substr($cmd, 0, 1) != '#')
1862			{
1863				if(! in_array($lookup, $valid_commands))
1864				{
1865					print "INVALID COMMAND: $lookup\n";
1866				}
1867
1868				if(isset($config[$context][$key]))
1869				{
1870					print "REDEFINED $key in $context\n";
1871				}
1872				else
1873				{
1874					array_unshift($args,$linecount);
1875					array_unshift($args,$filename);
1876					$this->config[$context][$key] = $args;
1877				}
1878			}
1879			print "$context\\$key  $filename:$linecount ".join("|",$args)."\n";
1880
1881			if($nextcontext != "") $context = $nextcontext;
1882		}
1883
1884		if ($linematched == 0 && trim($buffer) != '') { warn ("Unrecognised config on line $linecount: $buffer\n"); }
1885
1886	}
1887
1888	if(! $is_include)
1889	{
1890		print_r($this->config);
1891
1892		foreach ($this->config as $context=>$values)
1893		{
1894			print "> $context\n";
1895		}
1896	}
1897
1898
1899}
1900
1901function ReadConfigNNG($input, $is_include=FALSE, $initial_context="GLOBAL")
1902{
1903	global $valid_commands;
1904
1905	if( (strchr($input,"\n")!=FALSE) || (strchr($input,"\r")!=FALSE ) )
1906	{
1907		 debug("ReadConfig Detected that this is a config fragment.\n");
1908			 // strip out any Windows line-endings that have gotten in here
1909			 $input=str_replace("\r", "", $input);
1910			 $lines = split("/n",$input);
1911			 $filename = "{text insert}";
1912	}
1913	else
1914	{
1915		debug("ReadConfig Detected that this is a config filename.\n");
1916		 $filename = $input;
1917
1918		$fd=fopen($filename, "r");
1919
1920		if ($fd)
1921		{
1922			while (!feof($fd))
1923			{
1924				$buffer=fgets($fd, 4096);
1925				// strip out any Windows line-endings that have gotten in here
1926				$buffer=str_replace("\r", "", $buffer);
1927				$lines[] = $buffer;
1928			}
1929			fclose($fd);
1930		}
1931	}
1932
1933	$linecount = 0;
1934	$context = $initial_context;
1935
1936	foreach($lines as $buffer)
1937	{
1938		$linematched=0;
1939		$linecount++;
1940		$nextcontext = "";
1941		$key = "";
1942
1943		$buffer = trim($buffer);
1944		// alternative for use later where quoted strings are more useful
1945		$args = ParseString($buffer);
1946
1947		if(sizeof($args) > 0)
1948		{
1949			$linematched++;
1950			$cmd = strtolower(array_shift($args));
1951
1952			if($cmd == 'include')
1953			{
1954				$context = $this->ReadConfigNNG($args[0],TRUE, $context);
1955			}
1956			elseif($cmd == 'node')
1957			{
1958				$context = "NODE.".$args[0];
1959			}
1960			elseif($cmd == 'link')
1961			{
1962				$context = "LINK.".$args[0];
1963				$vcount = 0;	# reset the via-number counter, it's a new link
1964			}
1965			elseif($cmd == 'scale' || $cmd == 'keystyle' || $cmd == 'keypos')
1966			{
1967				if( preg_match("/^[0-9\-]+/i",$args[0]) )
1968				{
1969					$scalename = "DEFAULT";
1970				}
1971				else
1972				{
1973					$scalename = array_shift($args);
1974				}
1975				if($cmd=="scale") $key = $args[0]."_".$args[1];
1976				$nextcontext = $context;
1977				$context = "SCALE.".$scalename;
1978			}
1979
1980			array_unshift($args,$cmd);
1981
1982			if($context == 'GLOBAL')
1983	 		{
1984				$ctype='GLOBAL';
1985			}
1986			else
1987			{
1988				list($ctype,$junk) = split("\\.", $context, 2);
1989			}
1990
1991			$lookup = $ctype.".".$cmd;
1992
1993			// Some things (scales, mainly) might define special keys
1994			// the key should be unique for that object
1995			// most (all?) things for a link or node are one-offs.
1996			if($key == "") $key = $cmd;
1997			if($cmd == 'set' || $cmd == 'fontdefine') $key .= "_".$args[1];
1998			if($cmd == 'via')
1999			{
2000				$key .= "_".$vcount;
2001				$vcount++;
2002			}
2003
2004			# everything else
2005			if( substr($cmd, 0, 1) != '#')
2006			{
2007				if(! array_key_exists($lookup, $valid_commands))
2008				{
2009					print "INVALID COMMAND: $lookup\n";
2010				}
2011
2012				if(isset($config[$context][$key]))
2013				{
2014					print "REDEFINED $key in $context\n";
2015				}
2016				else
2017				{
2018					array_unshift($args,$linecount);
2019					array_unshift($args,$filename);
2020					$this->config[$context][$key] = $args;
2021				}
2022			}
2023			print "$context\\$key  $filename:$linecount ".join("|",$args)."\n";
2024
2025			if($nextcontext != "") $context = $nextcontext;
2026		}
2027
2028		if ($linematched == 0 && trim($buffer) != '') { warn ("Unrecognised config on line $linecount: $buffer\n"); }
2029
2030	}
2031
2032	if(! $is_include)
2033	{
2034
2035		# print_r($this->config);
2036
2037		foreach ($this->config as $context=>$values)
2038		{
2039		#	print "> $context\n";
2040		}
2041	}
2042
2043	return($context);
2044}
2045
2046
2047function WriteConfigNG($filename)
2048{
2049	global $WEATHERMAP_VERSION;
2050
2051	$fd = fopen($filename);
2052
2053
2054
2055	fclose($fd);
2056}
2057
2058function ReadConfig($input, $is_include=FALSE)
2059{
2060	$curnode=null;
2061	$curlink=null;
2062	$matches=0;
2063	$nodesseen=0;
2064	$linksseen=0;
2065	$scalesseen=0;
2066	$last_seen="GLOBAL";
2067	$filename = "";
2068	$objectlinecount=0;
2069
2070	 // check if $input is more than one line. if it is, it's a text of a config file
2071	// if it isn't, it's the filename
2072
2073	$lines = array();
2074
2075	if( (strchr($input,"\n")!=FALSE) || (strchr($input,"\r")!=FALSE ) )
2076	{
2077		 debug("ReadConfig Detected that this is a config fragment.\n");
2078			 // strip out any Windows line-endings that have gotten in here
2079			 $input=str_replace("\r", "", $input);
2080			 $lines = split("/n",$input);
2081			 $filename = "{text insert}";
2082	}
2083	else
2084	{
2085		debug("ReadConfig Detected that this is a config filename.\n");
2086		 $filename = $input;
2087
2088		if($is_include){
2089			debug("ReadConfig Detected that this is an INCLUDED config filename.\n");
2090			if($is_include && in_array($filename, $this->included_files))
2091			{
2092				warn("Attempt to include '$filename' twice! Skipping it.\n");
2093				return(FALSE);
2094			}
2095			else
2096			{
2097				$this->included_files[] = $filename;
2098				$this->has_includes = TRUE;
2099			}
2100		}
2101
2102		$fd=fopen($filename, "r");
2103
2104		if ($fd)
2105		{
2106				while (!feof($fd))
2107				{
2108					$buffer=fgets($fd, 4096);
2109					// strip out any Windows line-endings that have gotten in here
2110					$buffer=str_replace("\r", "", $buffer);
2111					$lines[] = $buffer;
2112				}
2113				fclose($fd);
2114		}
2115	}
2116
2117	$linecount = 0;
2118	$objectlinecount = 0;
2119
2120	foreach($lines as $buffer)
2121	{
2122		$linematched=0;
2123		$linecount++;
2124
2125		if (preg_match("/^\s*#/", $buffer)) {
2126			// this is a comment line
2127		}
2128		else
2129		{
2130			$buffer = trim($buffer);
2131
2132			// for any other config elements that are shared between nodes and links, they can use this
2133			unset($curobj);
2134			$curobj = NULL;
2135			if($last_seen == "LINK") $curobj = &$curlink;
2136			if($last_seen == "NODE") $curobj = &$curnode;
2137			if($last_seen == "GLOBAL") $curobj = &$this;
2138
2139			$objectlinecount++;
2140
2141			#if (preg_match("/^\s*(LINK|NODE)\s+([A-Za-z][A-Za-z0-9_\.\-\:]*)\s*$/i", $buffer, $matches))
2142			if (preg_match("/^\s*(LINK|NODE)\s+(\S+)\s*$/i", $buffer, $matches))
2143			{
2144				$objectlinecount = 0;
2145				if(1==1)
2146				{
2147					$this->ReadConfig_Commit($curobj);
2148				}
2149				else
2150				{
2151					// first, save the previous item, before starting work on the new one
2152					if ($last_seen == "NODE")
2153					{
2154						$this->nodes[$curnode->name]=$curnode;
2155						if($curnode->template == 'DEFAULT') $this->node_template_tree[ "DEFAULT" ][]= $curnode->name;
2156
2157						debug ("Saving Node: " . $curnode->name . "\n");
2158					}
2159
2160					if ($last_seen == "LINK")
2161					{
2162						if (isset($curlink->a) && isset($curlink->b))
2163						{
2164							$this->links[$curlink->name]=$curlink;
2165							debug ("Saving Link: " . $curlink->name . "\n");
2166						}
2167						else
2168						{
2169							$this->links[$curlink->name]=$curlink;
2170							debug ("Saving Template-Only Link: " . $curlink->name . "\n");
2171						}
2172						if($curlink->template == 'DEFAULT') $this->link_template_tree[ "DEFAULT" ][]= $curlink->name;
2173					}
2174				}
2175
2176				if ($matches[1] == 'LINK')
2177				{
2178					if ($matches[2] == 'DEFAULT')
2179					{
2180						if ($linksseen > 0) { warn
2181							("LINK DEFAULT is not the first LINK. Defaults will not apply to earlier LINKs. [WMWARN26]\n");
2182						}
2183						unset($curlink);
2184						debug("Loaded LINK DEFAULT\n");
2185						$curlink = $this->links['DEFAULT'];
2186					}
2187					else
2188					{
2189						unset($curlink);
2190
2191						if(isset($this->links[$matches[2]]))
2192						{
2193							warn("Duplicate link name ".$matches[2]." at line $linecount - only the last one defined is used. [WMWARN25]\n");
2194						}
2195
2196						debug("New LINK ".$matches[2]."\n");
2197						$curlink=new WeatherMapLink;
2198						$curlink->name=$matches[2];
2199						$curlink->Reset($this);
2200
2201						$linksseen++;
2202					}
2203
2204					$last_seen="LINK";
2205					$curlink->configline = $linecount;
2206					$linematched++;
2207					$curobj = &$curlink;
2208				}
2209
2210				if ($matches[1] == 'NODE')
2211				{
2212					if ($matches[2] == 'DEFAULT')
2213					{
2214						if ($nodesseen > 0) { warn
2215							("NODE DEFAULT is not the first NODE. Defaults will not apply to earlier NODEs. [WMWARN27]\n");
2216						}
2217
2218						unset($curnode);
2219						debug("Loaded NODE DEFAULT\n");
2220						$curnode = $this->nodes['DEFAULT'];
2221					}
2222					else
2223					{
2224						unset($curnode);
2225
2226						if(isset($this->nodes[$matches[2]]))
2227						{
2228							warn("Duplicate node name ".$matches[2]." at line $linecount - only the last one defined is used. [WMWARN24]\n");
2229						}
2230
2231						$curnode=new WeatherMapNode;
2232						$curnode->name=$matches[2];
2233						$curnode->Reset($this);
2234
2235						$nodesseen++;
2236					}
2237
2238					$curnode->configline = $linecount;
2239					$last_seen="NODE";
2240					$linematched++;
2241					$curobj = &$curnode;
2242				}
2243
2244				# record where we first heard about this object
2245				$curobj->defined_in = $filename;
2246			}
2247
2248			// most of the config keywords just copy stuff into object properties.
2249			// these are all dealt with from this one array. The special-cases
2250			// follow on from that
2251			$config_keywords = array(
2252					array('LINK','/^\s*(MAXVALUE|BANDWIDTH)\s+(\d+\.?\d*[KMGT]?)\s+(\d+\.?\d*[KMGT]?)\s*$/i',array('max_bandwidth_in_cfg'=>2,'max_bandwidth_out_cfg'=>3)),
2253					array('LINK','/^\s*(MAXVALUE|BANDWIDTH)\s+(\d+\.?\d*[KMGT]?)\s*$/i',array('max_bandwidth_in_cfg'=>2,'max_bandwidth_out_cfg'=>2)),
2254					array('NODE','/^\s*(MAXVALUE)\s+(\d+\.?\d*[KMGT]?)\s+(\d+\.?\d*[KMGT]?)\s*$/i',array('max_bandwidth_in_cfg'=>2,'max_bandwidth_out_cfg'=>3)),
2255					array('NODE','/^\s*(MAXVALUE)\s+(\d+\.?\d*[KMGT]?)\s*$/i',array('max_bandwidth_in_cfg'=>2,'max_bandwidth_out_cfg'=>2)),
2256					array('GLOBAL','/^\s*BACKGROUND\s+(.*)\s*$/i',array('background'=>1)),
2257					array('GLOBAL','/^\s*HTMLOUTPUTFILE\s+(.*)\s*$/i',array('htmloutputfile'=>1)),
2258					array('GLOBAL','/^\s*HTMLSTYLESHEET\s+(.*)\s*$/i',array('htmlstylesheet'=>1)),
2259					array('GLOBAL','/^\s*IMAGEOUTPUTFILE\s+(.*)\s*$/i',array('imageoutputfile'=>1)),
2260					array('GLOBAL','/^\s*IMAGEURI\s+(.*)\s*$/i',array('imageuri'=>1)),
2261					array('GLOBAL','/^\s*TITLE\s+(.*)\s*$/i',array('title'=>1)),
2262					array('GLOBAL','/^\s*HTMLSTYLE\s+(static|overlib)\s*$/i',array('htmlstyle'=>1)),
2263					array('GLOBAL','/^\s*KEYFONT\s+(\d+)\s*$/i',array('keyfont'=>1)),
2264					array('GLOBAL','/^\s*TITLEFONT\s+(\d+)\s*$/i',array('titlefont'=>1)),
2265					array('GLOBAL','/^\s*TIMEFONT\s+(\d+)\s*$/i',array('timefont'=>1)),
2266					array('GLOBAL','/^\s*TITLEPOS\s+(-?\d+)\s+(-?\d+)\s*$/i',array('titlex'=>1, 'titley'=>2)),
2267					array('GLOBAL','/^\s*TITLEPOS\s+(-?\d+)\s+(-?\d+)\s+(.*)\s*$/i',array('titlex'=>1, 'titley'=>2, 'title'=>3)),
2268					array('GLOBAL','/^\s*TIMEPOS\s+(-?\d+)\s+(-?\d+)\s*$/i',array('timex'=>1, 'timey'=>2)),
2269					array('GLOBAL','/^\s*TIMEPOS\s+(-?\d+)\s+(-?\d+)\s+(.*)\s*$/i',array('timex'=>1, 'timey'=>2, 'stamptext'=>3)),
2270					array('GLOBAL','/^\s*MINTIMEPOS\s+(-?\d+)\s+(-?\d+)\s*$/i',array('mintimex'=>1, 'mintimey'=>2)),
2271					array('GLOBAL','/^\s*MINTIMEPOS\s+(-?\d+)\s+(-?\d+)\s+(.*)\s*$/i',array('mintimex'=>1, 'mintimey'=>2, 'minstamptext'=>3)),
2272					array('GLOBAL','/^\s*MAXTIMEPOS\s+(-?\d+)\s+(-?\d+)\s*$/i',array('maxtimex'=>1, 'maxtimey'=>2)),
2273					array('GLOBAL','/^\s*MAXTIMEPOS\s+(-?\d+)\s+(-?\d+)\s+(.*)\s*$/i',array('maxtimex'=>1, 'maxtimey'=>2, 'maxstamptext'=>3)),
2274					array('NODE', "/^\s*LABEL\s*$/i", array('label'=>'')),	# special case for blank labels
2275					array('NODE', "/^\s*LABEL\s+(.*)\s*$/i", array('label'=>1)),
2276					array('(LINK|GLOBAL)', "/^\s*WIDTH\s+(\d+)\s*$/i", array('width'=>1)),
2277					array('(LINK|GLOBAL)', "/^\s*HEIGHT\s+(\d+)\s*$/i", array('height'=>1)),
2278					array('LINK', "/^\s*WIDTH\s+(\d+\.\d+)\s*$/i", array('width'=>1)),
2279					array('LINK', '/^\s*ARROWSTYLE\s+(classic|compact)\s*$/i', array('arrowstyle'=>1)),
2280					array('LINK', '/^\s*VIASTYLE\s+(curved|angled)\s*$/i', array('viastyle'=>1)),
2281					array('LINK', '/^\s*INCOMMENT\s+(.*)\s*$/i', array('comments[IN]'=>1)),
2282					array('LINK', '/^\s*OUTCOMMENT\s+(.*)\s*$/i', array('comments[OUT]'=>1)),
2283					array('LINK', '/^\s*BWFONT\s+(\d+)\s*$/i', array('bwfont'=>1)),
2284					array('LINK', '/^\s*COMMENTFONT\s+(\d+)\s*$/i', array('commentfont'=>1)),
2285					array('LINK', '/^\s*COMMENTSTYLE\s+(edge|center)\s*$/i', array('commentstyle'=>1)),
2286					array('LINK', '/^\s*DUPLEX\s+(full|half)\s*$/i', array('duplex'=>1)),
2287					array('LINK', '/^\s*BWSTYLE\s+(classic|angled)\s*$/i', array('labelboxstyle'=>1)),
2288					array('LINK', '/^\s*LINKSTYLE\s+(twoway|oneway)\s*$/i', array('linkstyle'=>1)),
2289					array('LINK', '/^\s*BWLABELPOS\s+(\d+)\s(\d+)\s*$/i', array('labeloffset_in'=>1,'labeloffset_out'=>2)),
2290					array('LINK', '/^\s*COMMENTPOS\s+(\d+)\s(\d+)\s*$/i', array('commentoffset_in'=>1, 'commentoffset_out'=>2)),
2291					array('LINK', '/^\s*USESCALE\s+([A-Za-z][A-Za-z0-9_]*)\s*$/i', array('usescale'=>1)),
2292					array('LINK', '/^\s*USESCALE\s+([A-Za-z][A-Za-z0-9_]*)\s+(absolute|percent)\s*$/i', array('usescale'=>1,'scaletype'=>2)),
2293
2294					array('LINK', '/^\s*SPLITPOS\s+(\d+)\s*$/i', array('splitpos'=>1)),
2295
2296					array('NODE', '/^\s*LABELOFFSET\s+([-+]?\d+)\s+([-+]?\d+)\s*$/i', array('labeloffsetx'=>1,'labeloffsety'=>2)),
2297					array('NODE', '/^\s*LABELOFFSET\s+(C|NE|SE|NW|SW|N|S|E|W)\s*$/i', array('labeloffset'=>1)),
2298                                        array('NODE', '/^\s*LABELOFFSET\s+((C|NE|SE|NW|SW|N|S|E|W)\d+)\s*$/i', array('labeloffset'=>1)),
2299                                        array('NODE', '/^\s*LABELOFFSET\s+(-?\d+r\d+)\s*$/i', array('labeloffset'=>1)),
2300
2301					array('NODE', '/^\s*LABELFONT\s+(\d+)\s*$/i', array('labelfont'=>1)),
2302					array('NODE', '/^\s*LABELANGLE\s+(0|90|180|270)\s*$/i', array('labelangle'=>1)),
2303					# array('(NODE|LINK)', '/^\s*TEMPLATE\s+(\S+)\s*$/i', array('template'=>1)),
2304
2305					array('LINK', '/^\s*OUTBWFORMAT\s+(.*)\s*$/i', array('bwlabelformats[OUT]'=>1,'labelstyle'=>'--')),
2306					array('LINK', '/^\s*INBWFORMAT\s+(.*)\s*$/i', array('bwlabelformats[IN]'=>1,'labelstyle'=>'--')),
2307					# array('NODE','/^\s*ICON\s+none\s*$/i',array('iconfile'=>'')),
2308					array('NODE','/^\s*ICON\s+(\S+)\s*$/i',array('iconfile'=>1, 'iconscalew'=>'#0', 'iconscaleh'=>'#0')),
2309					array('NODE','/^\s*ICON\s+(\S+)\s*$/i',array('iconfile'=>1)),
2310					array('NODE','/^\s*ICON\s+(\d+)\s+(\d+)\s+(inpie|outpie|box|rbox|round|gauge|nink)\s*$/i',array('iconfile'=>3, 'iconscalew'=>1, 'iconscaleh'=>2)),
2311					array('NODE','/^\s*ICON\s+(\d+)\s+(\d+)\s+(\S+)\s*$/i',array('iconfile'=>3, 'iconscalew'=>1, 'iconscaleh'=>2)),
2312
2313					array('NODE','/^\s*NOTES\s+(.*)\s*$/i',array('notestext[IN]'=>1,'notestext[OUT]'=>1)),
2314					array('LINK','/^\s*NOTES\s+(.*)\s*$/i',array('notestext[IN]'=>1,'notestext[OUT]'=>1)),
2315					array('LINK','/^\s*INNOTES\s+(.*)\s*$/i',array('notestext[IN]'=>1)),
2316					array('LINK','/^\s*OUTNOTES\s+(.*)\s*$/i',array('notestext[OUT]'=>1)),
2317
2318					array('NODE','/^\s*INFOURL\s+(.*)\s*$/i',array('infourl[IN]'=>1,'infourl[OUT]'=>1)),
2319					array('LINK','/^\s*INFOURL\s+(.*)\s*$/i',array('infourl[IN]'=>1,'infourl[OUT]'=>1)),
2320					array('LINK','/^\s*ININFOURL\s+(.*)\s*$/i',array('infourl[IN]'=>1)),
2321					array('LINK','/^\s*OUTINFOURL\s+(.*)\s*$/i',array('infourl[OUT]'=>1)),
2322
2323					array('NODE','/^\s*OVERLIBCAPTION\s+(.*)\s*$/i',array('overlibcaption[IN]'=>1,'overlibcaption[OUT]'=>1)),
2324					array('LINK','/^\s*OVERLIBCAPTION\s+(.*)\s*$/i',array('overlibcaption[IN]'=>1,'overlibcaption[OUT]'=>1)),
2325					array('LINK','/^\s*INOVERLIBCAPTION\s+(.*)\s*$/i',array('overlibcaption[IN]'=>1)),
2326					array('LINK','/^\s*OUTOVERLIBCAPTION\s+(.*)\s*$/i',array('overlibcaption[OUT]'=>1)),
2327
2328					array('(NODE|LINK)', "/^\s*ZORDER\s+([-+]?\d+)\s*$/i", array('zorder'=>1)),
2329					array('(NODE|LINK)', "/^\s*OVERLIBWIDTH\s+(\d+)\s*$/i", array('overlibwidth'=>1)),
2330					array('(NODE|LINK)', "/^\s*OVERLIBHEIGHT\s+(\d+)\s*$/i", array('overlibheight'=>1)),
2331					array('NODE', "/^\s*POSITION\s+([-+]?\d+)\s+([-+]?\d+)\s*$/i", array('x'=>1,'y'=>2)),
2332					array('NODE', "/^\s*POSITION\s+(\S+)\s+([-+]?\d+)\s+([-+]?\d+)\s*$/i", array('x'=>2,'y'=>3,'original_x'=>2,'original_y'=>3,'relative_to'=>1,'relative_resolved'=>FALSE)),
2333					array('NODE', "/^\s*POSITION\s+(\S+)\s+([-+]?\d+)r(\d+)\s*$/i", array('x'=>2,'y'=>3,'original_x'=>2,'original_y'=>3,'relative_to'=>1,'polar'=>TRUE,'relative_resolved'=>FALSE))
2334					);
2335
2336			// alternative for use later where quoted strings are more useful
2337			$args = ParseString($buffer);
2338
2339			// this loop replaces a whole pile of duplicated ifs with something with consistent handling
2340			foreach ($config_keywords as $keyword)
2341			{
2342				if(preg_match("/".$keyword[0]."/",$last_seen))
2343				{
2344					$statskey = $last_seen."-".$keyword[1];
2345					$statskey = str_replace( array('/^\s*','\s*$/i'),array('',''), $statskey);
2346					if(!isset($this->usage_stats[$statskey])) $this->usage_stats[$statskey] = 0;
2347
2348					if(preg_match($keyword[1],$buffer,$matches))
2349					{
2350						# print "CONFIG MATCHED: ".$keyword[1]."\n";
2351
2352						$this->usage_stats[$statskey]++;
2353
2354						foreach ($keyword[2] as $key=>$val)
2355						{
2356							// so we can poke in numbers too, if the value starts with #
2357							// then take the # off, and treat the rest as a number literal
2358							if(preg_match("/^#(.*)/",$val,$m))
2359							{
2360								$val = $m[1];
2361							}
2362							elseif(is_numeric($val))
2363							{
2364								// if it's a number, then it;s a match number,
2365								// otherwise it's a literal to be put into a variable
2366								$val = $matches[$val];
2367							}
2368
2369							assert('is_object($curobj)');
2370
2371							if(preg_match('/^(.*)\[([^\]]+)\]$/',$key,$m))
2372							{
2373								$index = constant($m[2]);
2374								$key = $m[1];
2375								$curobj->{$key}[$index] = $val;
2376							}
2377							else
2378							{
2379								$curobj->$key = $val;
2380							}
2381						}
2382						$linematched++;
2383						# print "\n\n";
2384						break;
2385					}
2386				}
2387			}
2388
2389			if (preg_match("/^\s*NODES\s+(\S+)\s+(\S+)\s*$/i", $buffer, $matches))
2390			{
2391				if ($last_seen == 'LINK')
2392				{
2393					$valid_nodes=2;
2394
2395					foreach (array(1, 2)as $i)
2396					{
2397						$endoffset[$i]='C';
2398						$nodenames[$i]=$matches[$i];
2399
2400						// percentage of compass - must be first
2401						if (preg_match("/:(NE|SE|NW|SW|N|S|E|W|C)(\d+)$/i", $matches[$i], $submatches))
2402						{
2403							$endoffset[$i]=$submatches[1].$submatches[2];
2404							$nodenames[$i]=preg_replace("/:(NE|SE|NW|SW|N|S|E|W|C)\d+$/i", '', $matches[$i]);
2405							$this->need_size_precalc=TRUE;
2406						}
2407
2408						if (preg_match("/:(NE|SE|NW|SW|N|S|E|W|C)$/i", $matches[$i], $submatches))
2409						{
2410							$endoffset[$i]=$submatches[1];
2411							$nodenames[$i]=preg_replace("/:(NE|SE|NW|SW|N|S|E|W|C)$/i", '', $matches[$i]);
2412							$this->need_size_precalc=TRUE;
2413						}
2414
2415						if( preg_match("/:(-?\d+r\d+)$/i", $matches[$i], $submatches) )
2416						{
2417							$endoffset[$i]=$submatches[1];
2418							$nodenames[$i]=preg_replace("/:(-?\d+r\d+)$/i", '', $matches[$i]);
2419							$this->need_size_precalc=TRUE;
2420						}
2421
2422						if (preg_match("/:([-+]?\d+):([-+]?\d+)$/i", $matches[$i], $submatches))
2423						{
2424							$xoff = $submatches[1];
2425							$yoff = $submatches[2];
2426							$endoffset[$i]=$xoff.":".$yoff;
2427							$nodenames[$i]=preg_replace("/:$xoff:$yoff$/i", '', $matches[$i]);
2428							$this->need_size_precalc=TRUE;
2429						}
2430
2431						if (!array_key_exists($nodenames[$i], $this->nodes))
2432						{
2433							warn ("Unknown node '" . $nodenames[$i] . "' on line $linecount of config\n");
2434							$valid_nodes--;
2435						}
2436					}
2437
2438					// TODO - really, this should kill the whole link, and reset for the next one
2439					if ($valid_nodes == 2)
2440					{
2441						$curlink->a=$this->nodes[$nodenames[1]];
2442						$curlink->b=$this->nodes[$nodenames[2]];
2443						$curlink->a_offset=$endoffset[1];
2444						$curlink->b_offset=$endoffset[2];
2445					}
2446					else {
2447						// this'll stop the current link being added
2448						$last_seen="broken"; }
2449
2450						$linematched++;
2451				}
2452			}
2453
2454			if ( $last_seen=='GLOBAL' && preg_match("/^\s*INCLUDE\s+(.*)\s*$/i", $buffer, $matches))
2455			{
2456				if(file_exists($matches[1])){
2457					debug("Including '{$matches[1]}'\n");
2458					$this->ReadConfig($matches[1], TRUE);
2459					$last_seen = "GLOBAL";
2460				}else{
2461					warn("INCLUDE File '{$matches[1]}' not found!\n");
2462				}
2463				$linematched++;
2464			}
2465
2466			if ( ( $last_seen=='NODE' || $last_seen=='LINK' ) && preg_match("/^\s*TARGET\s+(.*)\s*$/i", $buffer, $matches))
2467			{
2468				$linematched++;
2469				# $targets=preg_split('/\s+/', $matches[1], -1, PREG_SPLIT_NO_EMPTY);
2470				$rawtargetlist = $matches[1]." ";
2471
2472				if($args[0]=='TARGET')
2473				{
2474					// wipe any existing targets, otherwise things in the DEFAULT accumulate with the new ones
2475					$curobj->targets = array();
2476					array_shift($args); // take off the actual TARGET keyword
2477
2478					foreach($args as $arg)
2479					{
2480						// we store the original TARGET string, and line number, along with the breakdown, to make nicer error messages later
2481						// array of 7 things:
2482						// - only 0,1,2,3,4 are used at the moment (more used to be before DS plugins)
2483						// 0 => final target string (filled in by ReadData)
2484						// 1 => multiplier (filled in by ReadData)
2485						// 2 => config filename where this line appears
2486						// 3 => linenumber in that file
2487						// 4 => the original target string
2488						// 5 => the plugin to use to pull data
2489						$newtarget=array('','',$filename,$linecount,$arg,"","");
2490						if ($curobj)
2491						{
2492							debug("  TARGET: $arg\n");
2493							$curobj->targets[]=$newtarget;
2494						}
2495					}
2496				}
2497			}
2498
2499			if ($last_seen == 'LINK' && preg_match(
2500				"/^\s*BWLABEL\s+(bits|percent|unformatted|none)\s*$/i", $buffer,
2501				$matches))
2502			{
2503				$format_in = '';
2504				$format_out = '';
2505				$style = strtolower($matches[1]);
2506				if($style=='percent')
2507				{
2508					$format_in = FMT_PERC_IN;
2509					$format_out = FMT_PERC_OUT;
2510				}
2511				if($style=='bits')
2512				{
2513					$format_in = FMT_BITS_IN;
2514					$format_out = FMT_BITS_OUT;
2515				}
2516				if($style=='unformatted')
2517				{
2518					$format_in = FMT_UNFORM_IN;
2519					$format_out = FMT_UNFORM_OUT;
2520				}
2521
2522				$curobj->labelstyle=$style;
2523				$curobj->bwlabelformats[IN] = $format_in;
2524				$curobj->bwlabelformats[OUT] = $format_out;
2525				$linematched++;
2526			}
2527
2528			if (preg_match("/^\s*SET\s+(\S+)\s+(.*)\s*$/i", $buffer, $matches))
2529			{
2530					$curobj->add_hint($matches[1],trim($matches[2]));
2531					$linematched++;
2532			}
2533
2534			// allow setting a variable to ""
2535			if (preg_match("/^\s*SET\s+(\S+)\s*$/i", $buffer, $matches))
2536			{
2537					$curobj->add_hint($matches[1],'');
2538					$linematched++;
2539			}
2540
2541			if (preg_match("/^\s*(IN|OUT)?OVERLIBGRAPH\s+(.+)$/i", $buffer, $matches))
2542			{
2543				$this->has_overlibs = TRUE;
2544				if($last_seen == 'NODE' && $matches[1] != '') {
2545						warn("IN/OUTOVERLIBGRAPH make no sense for a NODE! [WMWARN42]\n");
2546					} else if($last_seen == 'LINK' || $last_seen=='NODE' ) {
2547
2548						$urls = preg_split('/\s+/', $matches[2], -1, PREG_SPLIT_NO_EMPTY);
2549
2550						if($matches[1] == 'IN') $index = IN;
2551						if($matches[1] == 'OUT') $index = OUT;
2552						if($matches[1] == '') {
2553							$curobj->overliburl[IN]=$urls;
2554							$curobj->overliburl[OUT]=$urls;
2555						} else {
2556							$curobj->overliburl[$index]=$urls;
2557						}
2558						$linematched++;
2559					}
2560			}
2561
2562			// array('(NODE|LINK)', '/^\s*TEMPLATE\s+(\S+)\s*$/i', array('template'=>1)),
2563
2564			if ( ( $last_seen=='NODE' || $last_seen=='LINK' ) && preg_match("/^\s*TEMPLATE\s+(\S+)\s*$/i", $buffer, $matches))
2565			{
2566				$tname = $matches[1];
2567				if( ($last_seen=='NODE' && isset($this->nodes[$tname])) || ($last_seen=='LINK' && isset($this->links[$tname])) )
2568				{
2569					$curobj->template = $matches[1];
2570					debug("Resetting to template $last_seen ".$curobj->template."\n");
2571					$curobj->Reset($this);
2572					if( $objectlinecount > 1 ) warn("line $linecount: TEMPLATE is not first line of object. Some data may be lost. [WMWARN39]\n");
2573					// build up a list of templates - this will be useful later for the tree view
2574
2575					if($last_seen == 'NODE') $this->node_template_tree[ $tname ][]= $curobj->name;
2576					if($last_seen == 'LINK') $this->link_template_tree[ $tname ][]= $curobj->name;
2577				}
2578				else
2579				{
2580					warn("line $linecount: $last_seen TEMPLATE '$tname' doesn't exist! (if it does exist, check it's defined first) [WMWARN40]\n");
2581				}
2582				$linematched++;
2583
2584			}
2585
2586			if ($last_seen == 'LINK' && preg_match("/^\s*VIA\s+([-+]?\d+)\s+([-+]?\d+)\s*$/i", $buffer, $matches))
2587			{
2588				$curlink->vialist[]=array
2589					(
2590						$matches[1],
2591						$matches[2]
2592					);
2593
2594				$linematched++;
2595			}
2596
2597			if ($last_seen == 'LINK' && preg_match("/^\s*VIA\s+(\S+)\s+([-+]?\d+)\s+([-+]?\d+)\s*$/i", $buffer, $matches))
2598			{
2599				$curlink->vialist[]=array
2600					(
2601						$matches[2],
2602						$matches[3],
2603						$matches[1]
2604					);
2605
2606				$linematched++;
2607			}
2608
2609			if( ($last_seen == 'NODE') && preg_match("/^\s*USE(ICON)?SCALE\s+([A-Za-z][A-Za-z0-9_]*)(\s+(in|out))?(\s+(absolute|percent))?\s*$/i",$buffer,$matches))
2610			{
2611				$svar = '';
2612				$stype = 'percent';
2613				if(isset($matches[3]))
2614				{
2615					$svar = trim($matches[3]);
2616				}
2617				if(isset($matches[6]))
2618				{
2619					$stype = strtolower(trim($matches[6]));
2620				}
2621				// opens the door for other scaley things...
2622				switch($matches[1])
2623				{
2624					case 'ICON':
2625						$varname = 'iconscalevar';
2626						$uvarname = 'useiconscale';
2627						$tvarname = 'iconscaletype';
2628
2629						// if(!function_exists("imagefilter"))
2630						// {
2631						// 	warn("ICON SCALEs require imagefilter, which is not present in your PHP [WMWARN040]\n");
2632						// }
2633						break;
2634					default:
2635						$varname = 'scalevar';
2636						$uvarname = 'usescale';
2637						$tvarname = 'scaletype';
2638						break;
2639				}
2640
2641				if($svar != '')
2642				{
2643					$curnode->$varname = $svar;
2644				}
2645				$curnode->$tvarname = $stype;
2646				$curnode->$uvarname = $matches[2];
2647
2648				// warn("Set $varname and $uvarname\n");
2649
2650				// print ">> $stype $svar ".$matches[2]." ".$curnode->name." \n";
2651
2652				$linematched++;
2653			}
2654
2655			// one REGEXP to rule them all:
2656//				if(preg_match("/^\s*SCALE\s+([A-Za-z][A-Za-z0-9_]*\s+)?(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+)\s+(\d+)\s+(\d+)(?:\s+(\d+)\s+(\d+)\s+(\d+))?\s*$/i",
2657//	0.95b		if(preg_match("/^\s*SCALE\s+([A-Za-z][A-Za-z0-9_]*\s+)?(\d+\.?\d*)\s+(\d+\.?\d*)\s+(\d+)\s+(\d+)\s+(\d+)(?:\s+(\d+)\s+(\d+)\s+(\d+))?\s*(.*)$/i",
2658			if(preg_match("/^\s*SCALE\s+([A-Za-z][A-Za-z0-9_]*\s+)?(\-?\d+\.?\d*[munMGT]?)\s+(\-?\d+\.?\d*[munMGT]?)\s+(?:(\d+)\s+(\d+)\s+(\d+)(?:\s+(\d+)\s+(\d+)\s+(\d+))?|(none))\s*(.*)$/i",
2659				$buffer, $matches))
2660			{
2661				// The default scale name is DEFAULT
2662				if($matches[1]=='') $matches[1] = 'DEFAULT';
2663				else $matches[1] = trim($matches[1]);
2664
2665				$key=$matches[2] . '_' . $matches[3];
2666
2667				$this->colours[$matches[1]][$key]['key']=$key;
2668
2669				$tag = $matches[11];
2670
2671				$this->colours[$matches[1]][$key]['tag']=$tag;
2672
2673				$this->colours[$matches[1]][$key]['bottom'] = unformat_number($matches[2], $this->kilo);
2674				$this->colours[$matches[1]][$key]['top'] = unformat_number($matches[3], $this->kilo);
2675				$this->colours[$matches[1]][$key]['special'] = 0;
2676
2677				if(isset($matches[10]) && $matches[10] == 'none')
2678				{
2679					$this->colours[$matches[1]][$key]['red1'] = -1;
2680					$this->colours[$matches[1]][$key]['green1'] = -1;
2681					$this->colours[$matches[1]][$key]['blue1'] = -1;
2682				}
2683				else
2684				{
2685					$this->colours[$matches[1]][$key]['red1'] = (int)($matches[4]);
2686					$this->colours[$matches[1]][$key]['green1'] = (int)($matches[5]);
2687					$this->colours[$matches[1]][$key]['blue1'] = (int)($matches[6]);
2688				}
2689
2690				// this is the second colour, if there is one
2691				if(isset($matches[7]) && $matches[7] != '')
2692				{
2693					$this->colours[$matches[1]][$key]['red2'] = (int) ($matches[7]);
2694					$this->colours[$matches[1]][$key]['green2'] = (int) ($matches[8]);
2695					$this->colours[$matches[1]][$key]['blue2'] = (int) ($matches[9]);
2696				}
2697
2698
2699				if(! isset($this->numscales[$matches[1]]))
2700				{
2701					$this->numscales[$matches[1]]=1;
2702				}
2703				else
2704				{
2705					$this->numscales[$matches[1]]++;
2706				}
2707				// we count if we've seen any default scale, otherwise, we have to add
2708				// one at the end.
2709				if($matches[1]=='DEFAULT')
2710				{
2711					$scalesseen++;
2712				}
2713
2714				$linematched++;
2715			}
2716
2717			if (preg_match("/^\s*KEYPOS\s+([A-Za-z][A-Za-z0-9_]*\s+)?(-?\d+)\s+(-?\d+)(.*)/i", $buffer, $matches))
2718			{
2719				$whichkey = trim($matches[1]);
2720				if($whichkey == '') $whichkey = 'DEFAULT';
2721
2722				$this->keyx[$whichkey]=$matches[2];
2723				$this->keyy[$whichkey]=$matches[3];
2724				$extra=trim($matches[4]);
2725
2726				if ($extra != '')
2727					$this->keytext[$whichkey] = $extra;
2728				if(!isset($this->keytext[$whichkey]))
2729					$this->keytext[$whichkey] = "DEFAULT TITLE";
2730				if(!isset($this->keystyle[$whichkey]))
2731					$this->keystyle[$whichkey] = "classic";
2732
2733				$linematched++;
2734			}
2735
2736
2737			// truetype font definition (actually, we don't really check if it's truetype) - filename + size
2738			if (preg_match("/^\s*FONTDEFINE\s+(\d+)\s+(\S+)\s+(\d+)\s*$/i", $buffer, $matches))
2739			{
2740				if (function_exists("imagettfbbox"))
2741				{
2742					// test if this font is valid, before adding it to the font table...
2743					$bounds=@imagettfbbox($matches[3], 0, $matches[2], "Ignore me");
2744
2745					if (isset($bounds[0]))
2746					{
2747						$this->fonts[$matches[1]]->type="truetype";
2748						$this->fonts[$matches[1]]->file=$matches[2];
2749						$this->fonts[$matches[1]]->size=$matches[3];
2750					}
2751					else { warn
2752						("Failed to load ttf font " . $matches[2] . " - at config line $linecount\n [WMWARN30]"); }
2753				}
2754				else { warn
2755					("imagettfbbox() is not a defined function. You don't seem to have FreeType compiled into your gd module. [WMWARN31]\n");
2756				}
2757
2758				$linematched++;
2759			}
2760
2761			// GD font definition (no size here)
2762			if (preg_match("/^\s*FONTDEFINE\s+(\d+)\s+(\S+)\s*$/i", $buffer, $matches))
2763			{
2764				$newfont=imageloadfont($matches[2]);
2765
2766				if ($newfont)
2767				{
2768					$this->fonts[$matches[1]]->type="gd";
2769					$this->fonts[$matches[1]]->file=$matches[2];
2770					$this->fonts[$matches[1]]->gdnumber=$newfont;
2771				}
2772				else { warn ("Failed to load GD font: " . $matches[2]
2773					. " ($newfont) at config line $linecount [WMWARN32]\n"); }
2774
2775				$linematched++;
2776			}
2777
2778			if(preg_match("/^\s*KEYSTYLE\s+([A-Za-z][A-Za-z0-9_]+\s+)?(classic|horizontal|vertical|inverted|tags)\s?(\d+)?\s*$/i",$buffer, $matches))
2779			{
2780				$whichkey = trim($matches[1]);
2781				if($whichkey == '') $whichkey = 'DEFAULT';
2782				$this->keystyle[$whichkey] = strtolower($matches[2]);
2783
2784				if(isset($matches[3]) && $matches[3] != '')
2785				{
2786					$this->keysize[$whichkey] = $matches[3];
2787				}
2788				else
2789				{
2790					$this->keysize[$whichkey] = $this->keysize['DEFAULT'];
2791				}
2792
2793				$linematched++;
2794			}
2795
2796
2797			if (preg_match("/^\s*KILO\s+(\d+)\s*$/i", $buffer, $matches))
2798			{
2799				$this->kilo=$matches[1];
2800				# $this->defaultlink->owner->kilo=$matches[1];
2801				# $this->links['DEFAULT']=$matches[1];
2802				$linematched++;
2803			}
2804
2805			if (preg_match(
2806				"/^\s*(TIME|TITLE|KEYBG|KEYTEXT|KEYOUTLINE|BG)COLOR\s+(\d+)\s+(\d+)\s+(\d+)\s*$/i",
2807				$buffer,
2808				$matches))
2809			{
2810				$key=$matches[1];
2811				# "Found colour line for $key\n";
2812				$this->colours['DEFAULT'][$key]['red1']=$matches[2];
2813				$this->colours['DEFAULT'][$key]['green1']=$matches[3];
2814				$this->colours['DEFAULT'][$key]['blue1']=$matches[4];
2815				$this->colours['DEFAULT'][$key]['bottom']=-2;
2816				$this->colours['DEFAULT'][$key]['top']=-1;
2817				$this->colours['DEFAULT'][$key]['special']=1;
2818
2819				$linematched++;
2820			}
2821
2822			if (($last_seen == 'NODE') && (preg_match(
2823				"/^\s*(AICONOUTLINE|AICONFILL|LABELFONT|LABELFONTSHADOW|LABELBG|LABELOUTLINE)COLOR\s+((\d+)\s+(\d+)\s+(\d+)|none|contrast|copy)\s*$/i",
2824				$buffer,
2825				$matches)))
2826			{
2827				$key=$matches[1];
2828				$field=strtolower($matches[1]) . 'colour';
2829				$val = strtolower($matches[2]);
2830
2831				if(isset($matches[3]))	// this is a regular colour setting thing
2832				{
2833					$curnode->$field=array(	$matches[3],$matches[4],$matches[5]);
2834					$linematched++;
2835				}
2836
2837				if($val == 'none' && ($matches[1]=='LABELFONTSHADOW' || $matches[1]=='LABELBG' || $matches[1]=='LABELOUTLINE' || $matches[1]=='AICONOUTLINE'))
2838				{
2839					$curnode->$field=array(-1,-1,-1);
2840					$linematched++;
2841				}
2842
2843				if($val == 'contrast' && $matches[1]=='LABELFONT')
2844				{
2845					$curnode->$field=array(-3,-3,-3);
2846					$linematched++;
2847				}
2848
2849				if($matches[2] == 'copy' && $matches[1]=='AICONFILL')
2850				{
2851					$curnode->$field=array(-2,-2,-2);
2852					$linematched++;
2853				}
2854			}
2855
2856			if (($last_seen == 'LINK') && (preg_match(
2857				"/^\s*(COMMENTFONT|BWBOX|BWFONT|BWOUTLINE|OUTLINE)COLOR\s+((\d+)\s+(\d+)\s+(\d+)|none|contrast|copy)\s*$/i",
2858				$buffer,
2859				$matches)))
2860			{
2861				$key=$matches[1];
2862				$field=strtolower($matches[1]) . 'colour';
2863				$val = strtolower($matches[2]);
2864
2865				if(isset($matches[3]))	// this is a regular colour setting thing
2866				{
2867					$curlink->$field=array(	$matches[3],$matches[4],$matches[5]);
2868					$linematched++;
2869				}
2870
2871				if($val == 'none' && ($key=='BWBOX' || $key=='BWOUTLINE' || $key=='OUTLINE'))
2872				{
2873					// print "***********************************\n";
2874					$curlink->$field=array(-1,-1,-1);
2875					$linematched++;
2876				}
2877
2878				if($val == 'contrast' && $key=='COMMENTFONT')
2879				{
2880					// print "***********************************\n";
2881					$curlink->$field=array(-3,-3,-3);
2882					$linematched++;
2883				}
2884			}
2885
2886			if ($last_seen == 'LINK' && preg_match(
2887				"/^\s*ARROWSTYLE\s+(\d+)\s+(\d+)\s*$/i", $buffer, $matches))
2888			{
2889				$curlink->arrowstyle=$matches[1] . ' ' . $matches[2];
2890				$linematched++;
2891			}
2892
2893
2894			if ($linematched == 0 && trim($buffer) != '') { warn
2895				("Unrecognised config on line $linecount: $buffer\n"); }
2896
2897			if ($linematched > 1) { warn
2898			("Same line ($linecount) interpreted twice. This is a program error. Please report to Howie with your config!\nThe line was: $buffer");
2899			}
2900		} // if blankline
2901	}     // while
2902
2903	if(1==1)
2904	{
2905		$this->ReadConfig_Commit($curobj);
2906	}
2907	else
2908	{
2909	if ($last_seen == "NODE")
2910	{
2911		$this->nodes[$curnode->name]=$curnode;
2912		debug ("Saving Node: " . $curnode->name . "\n");
2913		if($curnode->template == 'DEFAULT') $this->node_template_tree[ "DEFAULT" ][]= $curnode->name;
2914	}
2915
2916	if ($last_seen == "LINK")
2917	{
2918		if (isset($curlink->a) && isset($curlink->b))
2919		{
2920			$this->links[$curlink->name]=$curlink;
2921			debug ("Saving Link: " . $curlink->name . "\n");
2922			if($curlink->template == 'DEFAULT') $this->link_template_tree[ "DEFAULT" ][]= $curlink->name;
2923		}
2924		else { warn ("Dropping LINK " . $curlink->name . " - it hasn't got 2 NODES!"); }
2925	}
2926	}
2927
2928
2929	debug("ReadConfig has finished reading the config ($linecount lines)\n");
2930	debug("------------------------------------------\n");
2931
2932	// load some default colouring, otherwise it all goes wrong
2933	if ($scalesseen == 0)
2934	{
2935		debug ("Adding default SCALE colour set (no SCALE lines seen).\n");
2936		$defaults=array
2937			(
2938				'0_0' => array('bottom' => 0, 'top' => 0, 'red1' => 192, 'green1' => 192, 'blue1' => 192, 'special'=>0),
2939				'0_1' => array('bottom' => 0, 'top' => 1, 'red1' => 255, 'green1' => 255, 'blue1' => 255, 'special'=>0),
2940				'1_10' => array('bottom' => 1, 'top' => 10, 'red1' => 140, 'green1' => 0, 'blue1' => 255, 'special'=>0),
2941				'10_25' => array('bottom' => 10, 'top' => 25, 'red1' => 32, 'green1' => 32, 'blue1' => 255, 'special'=>0),
2942				'25_40' => array('bottom' => 25, 'top' => 40, 'red1' => 0, 'green1' => 192, 'blue1' => 255, 'special'=>0),
2943				'40_55' => array('bottom' => 40, 'top' => 55, 'red1' => 0, 'green1' => 240, 'blue1' => 0, 'special'=>0),
2944				'55_70' => array('bottom' => 55, 'top' => 70, 'red1' => 240, 'green1' => 240, 'blue1' => 0, 'special'=>0),
2945				'70_85' => array('bottom' => 70, 'top' => 85, 'red1' => 255, 'green1' => 192, 'blue1' => 0, 'special'=>0),
2946				'85_100' => array('bottom' => 85, 'top' => 100, 'red1' => 255, 'green1' => 0, 'blue1' => 0, 'special'=>0)
2947			);
2948
2949		foreach ($defaults as $key => $def)
2950		{
2951			$this->colours['DEFAULT'][$key]=$def;
2952			$this->colours['DEFAULT'][$key]['key']=$key;
2953			$scalesseen++;
2954		}
2955		// we have a 0-0 line now, so we need to hide that.
2956		$this->add_hint("key_hidezero_DEFAULT",1);
2957	}
2958	else { debug ("Already have $scalesseen scales, no defaults added.\n"); }
2959
2960	$this->numscales['DEFAULT']=$scalesseen;
2961	$this->configfile="$filename";
2962
2963	if($this->has_overlibs && $this->htmlstyle == 'static')
2964	{
2965		warn("OVERLIBGRAPH is used, but HTMLSTYLE is static. This is probably wrong. [WMWARN41]\n");
2966	}
2967
2968	debug("Building cache of z-layers and finalising bandwidth.\n");
2969
2970// 	$allitems = array_merge($this->links, $this->nodes);
2971
2972	$allitems = array();
2973	foreach ($this->nodes as $node)
2974	{
2975		$allitems[] = $node;
2976	}
2977	foreach ($this->links as $link)
2978	{
2979		$allitems[] = $link;
2980	}
2981
2982	# foreach ($allitems as &$item)
2983	foreach ($allitems as $ky=>$vl)
2984	{
2985		$item =& $allitems[$ky];
2986		$z = $item->zorder;
2987		if(!isset($this->seen_zlayers[$z]) || !is_array($this->seen_zlayers[$z]))
2988		{
2989			$this->seen_zlayers[$z]=array();
2990		}
2991		array_push($this->seen_zlayers[$z], $item);
2992
2993		// while we're looping through, let's set the real bandwidths
2994		if($item->my_type() == "LINK")
2995		{
2996			$this->links[$item->name]->max_bandwidth_in = unformat_number($item->max_bandwidth_in_cfg, $this->kilo);
2997			$this->links[$item->name]->max_bandwidth_out = unformat_number($item->max_bandwidth_out_cfg, $this->kilo);
2998		}
2999		elseif($item->my_type() == "NODE")
3000		{
3001			$this->nodes[$item->name]->max_bandwidth_in = unformat_number($item->max_bandwidth_in_cfg, $this->kilo);
3002			$this->nodes[$item->name]->max_bandwidth_out = unformat_number($item->max_bandwidth_out_cfg, $this->kilo);
3003		}
3004		else
3005		{
3006			warn("Internal bug - found an item of type: ".$item->my_type()."\n");
3007		}
3008		// $item->max_bandwidth_in=unformat_number($item->max_bandwidth_in_cfg, $this->kilo);
3009		// $item->max_bandwidth_out=unformat_number($item->max_bandwidth_out_cfg, $this->kilo);
3010
3011		debug (sprintf("   Setting bandwidth on ".$item->my_type()." $item->name (%s -> %d bps, %s -> %d bps, KILO = %d)\n", $item->max_bandwidth_in_cfg, $item->max_bandwidth_in, $item->max_bandwidth_out_cfg, $item->max_bandwidth_out, $this->kilo));
3012	}
3013
3014	debug("Found ".sizeof($this->seen_zlayers)." z-layers including builtins (0,100).\n");
3015
3016	// calculate any relative positions here - that way, nothing else
3017	// really needs to know about them
3018
3019	debug("Resolving relative positions for NODEs...\n");
3020	// safety net for cyclic dependencies
3021	$i=100;
3022	do
3023	{
3024		$skipped = 0; $set=0;
3025		foreach ($this->nodes as $node)
3026		{
3027			if( ($node->relative_to != '') && (!$node->relative_resolved))
3028			{
3029				debug("Resolving relative position for NODE ".$node->name." to ".$node->relative_to."\n");
3030				if(array_key_exists($node->relative_to,$this->nodes))
3031				{
3032
3033					// check if we are relative to another node which is in turn relative to something
3034					// we need to resolve that one before we can resolve this one!
3035					if(  ($this->nodes[$node->relative_to]->relative_to != '') && (!$this->nodes[$node->relative_to]->relative_resolved) )
3036					{
3037						debug("Skipping unresolved relative_to. Let's hope it's not a circular one\n");
3038						$skipped++;
3039					}
3040					else
3041					{
3042						$rx = $this->nodes[$node->relative_to]->x;
3043						$ry = $this->nodes[$node->relative_to]->y;
3044
3045						if($node->polar)
3046						{
3047							// treat this one as a POLAR relative coordinate.
3048							// - draw rings around a node!
3049							$angle = $node->x;
3050							$distance = $node->y;
3051							$newpos_x = $rx + $distance * sin(deg2rad($angle));
3052							$newpos_y = $ry - $distance * cos(deg2rad($angle));
3053							debug("->$newpos_x,$newpos_y\n");
3054							$this->nodes[$node->name]->x = $newpos_x;
3055							$this->nodes[$node->name]->y = $newpos_y;
3056							$this->nodes[$node->name]->relative_resolved=TRUE;
3057							$set++;
3058						}
3059						else
3060						{
3061
3062							// save the relative coords, so that WriteConfig can work
3063							// resolve the relative stuff
3064
3065							$newpos_x = $rx + $this->nodes[$node->name]->x;
3066							$newpos_y = $ry + $this->nodes[$node->name]->y;
3067							debug("->$newpos_x,$newpos_y\n");
3068							$this->nodes[$node->name]->x = $newpos_x;
3069							$this->nodes[$node->name]->y = $newpos_y;
3070							$this->nodes[$node->name]->relative_resolved=TRUE;
3071							$set++;
3072						}
3073					}
3074				}
3075				else
3076				{
3077					warn("NODE ".$node->name." has a relative position to an unknown node! [WMWARN10]\n");
3078				}
3079			}
3080		}
3081		debug("Relative Positions Cycle $i - set $set and Skipped $skipped for unresolved dependencies\n");
3082		$i--;
3083	} while( ($set>0) && ($i!=0)  );
3084
3085	if($skipped>0)
3086	{
3087		warn("There are Circular dependencies in relative POSITION lines for $skipped nodes. [WMWARN11]\n");
3088	}
3089
3090	debug("-----------------------------------\n");
3091
3092
3093	debug("Running Pre-Processing Plugins...\n");
3094	foreach ($this->preprocessclasses as $pre_class)
3095	{
3096		debug("Running $pre_class"."->run()\n");
3097		$this->plugins['pre'][$pre_class]->run($this);
3098	}
3099	debug("Finished Pre-Processing Plugins...\n");
3100
3101	return (TRUE);
3102}
3103
3104function ReadConfig_Commit(&$curobj)
3105{
3106	if(is_null($curobj)) return;
3107
3108	$last_seen = $curobj->my_type();
3109
3110	// first, save the previous item, before starting work on the new one
3111	if ($last_seen == "NODE")
3112	{
3113		$this->nodes[$curobj->name]=$curobj;
3114		debug ("Saving Node: " . $curobj->name . "\n");
3115		if($curobj->template == 'DEFAULT') $this->node_template_tree[ "DEFAULT" ][]= $curobj->name;
3116	}
3117
3118	if ($last_seen == "LINK")
3119	{
3120		if (isset($curobj->a) && isset($curobj->b))
3121		{
3122			$this->links[$curobj->name]=$curobj;
3123			debug ("Saving Link: " . $curobj->name . "\n");
3124		}
3125		else
3126		{
3127			$this->links[$curobj->name]=$curobj;
3128			debug ("Saving Template-Only Link: " . $curobj->name . "\n");
3129		}
3130		if($curobj->template == 'DEFAULT') $this->link_template_tree[ "DEFAULT" ][]= $curobj->name;
3131	}
3132}
3133
3134function WriteConfig($filename)
3135{
3136	global $WEATHERMAP_VERSION;
3137
3138	$fd=fopen($filename, "w");
3139	$output="";
3140
3141	if ($fd)
3142	{
3143		$output.="# Automatically generated by php-weathermap v$WEATHERMAP_VERSION\n\n";
3144
3145		if (count($this->fonts) > 0)
3146		{
3147			foreach ($this->fonts as $fontnumber => $font)
3148			{
3149				if ($font->type == 'truetype')
3150					$output.=sprintf("FONTDEFINE %d %s %d\n", $fontnumber, $font->file, $font->size);
3151
3152				if ($font->type == 'gd')
3153					$output.=sprintf("FONTDEFINE %d %s\n", $fontnumber, $font->file);
3154			}
3155
3156			$output.="\n";
3157		}
3158
3159		$basic_params = array(
3160				array('background','BACKGROUND',CONFIG_TYPE_LITERAL),
3161				array('width','WIDTH',CONFIG_TYPE_LITERAL),
3162				array('height','HEIGHT',CONFIG_TYPE_LITERAL),
3163				array('htmlstyle','HTMLSTYLE',CONFIG_TYPE_LITERAL),
3164				array('kilo','KILO',CONFIG_TYPE_LITERAL),
3165				array('keyfont','KEYFONT',CONFIG_TYPE_LITERAL),
3166				array('timefont','TIMEFONT',CONFIG_TYPE_LITERAL),
3167				array('titlefont','TITLEFONT',CONFIG_TYPE_LITERAL),
3168				array('title','TITLE',CONFIG_TYPE_LITERAL),
3169				array('htmloutputfile','HTMLOUTPUTFILE',CONFIG_TYPE_LITERAL),
3170				array('htmlstylesheet','HTMLSTYLESHEET',CONFIG_TYPE_LITERAL),
3171				array('imageuri','IMAGEURI',CONFIG_TYPE_LITERAL),
3172				array('imageoutputfile','IMAGEOUTPUTFILE',CONFIG_TYPE_LITERAL)
3173			);
3174
3175		foreach ($basic_params as $param)
3176		{
3177			$field = $param[0];
3178			$keyword = $param[1];
3179
3180			if ($this->$field != $this->inherit_fieldlist[$field])
3181			{
3182				if($param[2] == CONFIG_TYPE_COLOR) $output.="$keyword " . render_colour($this->$field) . "\n";
3183				if($param[2] == CONFIG_TYPE_LITERAL) $output.="$keyword " . $this->$field . "\n";
3184			}
3185		}
3186
3187		if (($this->timex != $this->inherit_fieldlist['timex'])
3188			|| ($this->timey != $this->inherit_fieldlist['timey'])
3189			|| ($this->stamptext != $this->inherit_fieldlist['stamptext']))
3190				$output.="TIMEPOS " . $this->timex . " " . $this->timey . " " . $this->stamptext . "\n";
3191
3192		if (($this->mintimex != $this->inherit_fieldlist['mintimex'])
3193			|| ($this->mintimey != $this->inherit_fieldlist['mintimey'])
3194			|| ($this->minstamptext != $this->inherit_fieldlist['minstamptext']))
3195				$output.="MINTIMEPOS " . $this->mintimex . " " . $this->mintimey . " " . $this->minstamptext . "\n";
3196
3197		if (($this->maxtimex != $this->inherit_fieldlist['maxtimex'])
3198			|| ($this->maxtimey != $this->inherit_fieldlist['maxtimey'])
3199			|| ($this->maxstamptext != $this->inherit_fieldlist['maxstamptext']))
3200				$output.="MAXTIMEPOS " . $this->maxtimex . " " . $this->maxtimey . " " . $this->maxstamptext . "\n";
3201
3202		if (($this->titlex != $this->inherit_fieldlist['titlex'])
3203			|| ($this->titley != $this->inherit_fieldlist['titley']))
3204				$output.="TITLEPOS " . $this->titlex . " " . $this->titley . "\n";
3205
3206		$output.="\n";
3207
3208		foreach ($this->colours as $scalename=>$colours)
3209		{
3210		  // not all keys will have keypos but if they do, then all three vars should be defined
3211		if ( (isset($this->keyx[$scalename])) && (isset($this->keyy[$scalename])) && (isset($this->keytext[$scalename]))
3212		    && (($this->keytext[$scalename] != $this->inherit_fieldlist['keytext'])
3213			|| ($this->keyx[$scalename] != $this->inherit_fieldlist['keyx'])
3214			|| ($this->keyy[$scalename] != $this->inherit_fieldlist['keyy'])))
3215			{
3216			     // sometimes a scale exists but without defaults. A proper scale object would sort this out...
3217			     if($this->keyx[$scalename] == '') { $this->keyx[$scalename] = -1; }
3218			     if($this->keyy[$scalename] == '') { $this->keyy[$scalename] = -1; }
3219
3220				$output.="KEYPOS " . $scalename." ". $this->keyx[$scalename] . " " . $this->keyy[$scalename] . " " . $this->keytext[$scalename] . "\n";
3221            }
3222
3223		if ( (isset($this->keystyle[$scalename])) &&  ($this->keystyle[$scalename] != $this->inherit_fieldlist['keystyle']['DEFAULT']) )
3224		{
3225			$extra='';
3226			if ( (isset($this->keysize[$scalename])) &&  ($this->keysize[$scalename] != $this->inherit_fieldlist['keysize']['DEFAULT']) )
3227			{
3228				$extra = " ".$this->keysize[$scalename];
3229			}
3230			$output.="KEYSTYLE  " . $scalename." ". $this->keystyle[$scalename] . $extra . "\n";
3231		}
3232		$locale = localeconv();
3233		$decimal_point = $locale['decimal_point'];
3234
3235			foreach ($colours as $k => $colour)
3236			{
3237				if (!isset($colour['special']) || ! $colour['special'] )
3238				{
3239					$top = rtrim(rtrim(sprintf("%f",$colour['top']),"0"),$decimal_point);
3240					$bottom= rtrim(rtrim(sprintf("%f",$colour['bottom']),"0"),$decimal_point);
3241
3242                                        if ($bottom > 1000) {
3243                                            $bottom = nice_bandwidth($colour['bottom'], $this->kilo);
3244                                        }
3245
3246                                        if ($top > 1000) {
3247                                            $top = nice_bandwidth($colour['top'], $this->kilo);
3248                                        }
3249
3250					$tag = (isset($colour['tag'])? $colour['tag']:'');
3251
3252					if( ($colour['red1'] == -1) && ($colour['green1'] == -1) && ($colour['blue1'] == -1))
3253					{
3254						$output.=sprintf("SCALE %s %-4s %-4s   none   %s\n", $scalename,
3255							$bottom, $top, $tag);
3256					}
3257					elseif (!isset($colour['red2']))
3258					{
3259						$output.=sprintf("SCALE %s %-4s %-4s %3d %3d %3d  %s\n", $scalename,
3260							$bottom, $top,
3261							$colour['red1'],            $colour['green1'], $colour['blue1'],$tag);
3262					}
3263					else
3264					{
3265						$output.=sprintf("SCALE %s %-4s %-4s %3d %3d %3d   %3d %3d %3d    %s\n", $scalename,
3266							$bottom, $top,
3267							$colour['red1'],
3268							$colour['green1'],                     $colour['blue1'],
3269							$colour['red2'],                       $colour['green2'],
3270							$colour['blue2'], $tag);
3271					}
3272				}
3273				else { $output.=sprintf("%sCOLOR %d %d %d\n", $k, $colour['red1'], $colour['green1'],
3274					$colour['blue1']); }
3275			}
3276			$output .= "\n";
3277		}
3278
3279		foreach ($this->hints as $hintname=>$hint)
3280		{
3281			$output .= "SET $hintname $hint\n";
3282		}
3283
3284		// this doesn't really work right, but let's try anyway
3285		if($this->has_includes)
3286		{
3287			$output .= "\n# Included files\n";
3288			foreach ($this->included_files as $ifile)
3289			{
3290				$output .= "INCLUDE $ifile\n";
3291			}
3292		}
3293
3294		$output.="\n# End of global section\n\n";
3295
3296		fwrite($fd, $output);
3297
3298		## fwrite($fd,$this->nodes['DEFAULT']->WriteConfig());
3299		## fwrite($fd,$this->links['DEFAULT']->WriteConfig());
3300
3301		# fwrite($fd, "\n\n# Node definitions:\n");
3302
3303		foreach (array("template","normal") as $which)
3304		{
3305			if($which == "template") fwrite($fd,"\n# TEMPLATE-only NODEs:\n");
3306			if($which == "normal") fwrite($fd,"\n# regular NODEs:\n");
3307
3308			foreach ($this->nodes as $node)
3309			{
3310				if(!preg_match("/^::\s/",$node->name))
3311				{
3312					if($node->defined_in == $this->configfile)
3313					{
3314
3315						if($which=="template" && $node->x === NULL)  { debug("TEMPLATE\n"); fwrite($fd,$node->WriteConfig()); }
3316						if($which=="normal" && $node->x !== NULL) { fwrite($fd,$node->WriteConfig()); }
3317					}
3318				}
3319			}
3320
3321			if($which == "template") fwrite($fd,"\n# TEMPLATE-only LINKs:\n");
3322			if($which == "normal") fwrite($fd,"\n# regular LINKs:\n");
3323
3324			foreach ($this->links as $link)
3325			{
3326				if(!preg_match("/^::\s/",$link->name))
3327				{
3328					if($link->defined_in == $this->configfile)
3329					{
3330						if($which=="template" && $link->a === NULL) fwrite($fd,$link->WriteConfig());
3331						if($which=="normal" && $link->a !== NULL) fwrite($fd,$link->WriteConfig());
3332					}
3333				}
3334			}
3335		}
3336
3337		fwrite($fd, "\n\n# That's All Folks!\n");
3338
3339		fclose($fd);
3340	}
3341	else
3342	{
3343		warn ("Couldn't open config file $filename for writing");
3344		return (FALSE);
3345	}
3346
3347	return (TRUE);
3348}
3349
3350// pre-allocate colour slots for the colours used by the arrows
3351// this way, it's the pretty icons that suffer if there aren't enough colours, and
3352// not the actual useful data
3353// we skip any gradient scales
3354function AllocateScaleColours($im,$refname='gdref1')
3355{
3356	# $colours=$this->colours['DEFAULT'];
3357	foreach ($this->colours as $scalename=>$colours)
3358	{
3359		foreach ($colours as $key => $colour)
3360		{
3361			if ( (!isset($this->colours[$scalename][$key]['red2']) ) && (!isset( $this->colours[$scalename][$key][$refname] )) )
3362			{
3363				$r=$colour['red1'];
3364				$g=$colour['green1'];
3365				$b=$colour['blue1'];
3366				debug ("AllocateScaleColours: $scalename/$refname $key ($r,$g,$b)\n");
3367				$this->colours[$scalename][$key][$refname]=myimagecolorallocate($im, $r, $g, $b);
3368			}
3369		}
3370	}
3371}
3372
3373function DrawMap($filename = '', $thumbnailfile = '', $thumbnailmax = 250, $withnodes = TRUE, $use_via_overlay = FALSE, $use_rel_overlay=FALSE)
3374{
3375	debug("Trace: DrawMap()\n");
3376	metadump("# start",true);
3377	$bgimage=NULL;
3378	if($this->configfile != "")
3379	{
3380		$this->cachefile_version = crc32(file_get_contents($this->configfile));
3381	}
3382	else
3383	{
3384		$this->cachefile_version = crc32("........");
3385	}
3386
3387	debug("Running Post-Processing Plugins...\n");
3388	foreach ($this->postprocessclasses as $post_class)
3389	{
3390		debug("Running $post_class"."->run()\n");
3391		//call_user_func_array(array($post_class, 'run'), array(&$this));
3392		$this->plugins['post'][$post_class]->run($this);
3393
3394	}
3395	debug("Finished Post-Processing Plugins...\n");
3396
3397	debug("=====================================\n");
3398	debug("Start of Map Drawing\n");
3399
3400	$this->datestamp = strftime($this->stamptext, time());
3401
3402	// do the basic prep work
3403	if ($this->background != '')
3404	{
3405		if (is_readable($this->background))
3406		{
3407			$bgimage=imagecreatefromfile($this->background);
3408
3409			if (!$bgimage) { warn
3410				("Failed to open background image.  One possible reason: Is your BACKGROUND really a PNG?\n");
3411			}
3412			else
3413			{
3414				$this->width=imagesx($bgimage);
3415				$this->height=imagesy($bgimage);
3416			}
3417		}
3418		else { warn
3419			("Your background image file could not be read. Check the filename, and permissions, for "
3420			. $this->background . "\n"); }
3421	}
3422
3423	$image=wimagecreatetruecolor($this->width, $this->height);
3424
3425	# $image = imagecreate($this->width, $this->height);
3426	if (!$image) { warn
3427		("Couldn't create output image in memory (" . $this->width . "x" . $this->height . ")."); }
3428	else
3429	{
3430		ImageAlphaBlending($image, true);
3431		# imageantialias($image,true);
3432
3433		// by here, we should have a valid image handle
3434
3435		// save this away, now
3436		$this->image=$image;
3437
3438		$this->white=myimagecolorallocate($image, 255, 255, 255);
3439		$this->black=myimagecolorallocate($image, 0, 0, 0);
3440		$this->grey=myimagecolorallocate($image, 192, 192, 192);
3441		$this->selected=myimagecolorallocate($image, 255, 0, 0); // for selections in the editor
3442
3443		$this->AllocateScaleColours($image);
3444
3445		// fill with background colour anyway, in case the background image failed to load
3446		wimagefilledrectangle($image, 0, 0, $this->width, $this->height, $this->colours['DEFAULT']['BG']['gdref1']);
3447
3448		if ($bgimage)
3449		{
3450			imagecopy($image, $bgimage, 0, 0, 0, 0, $this->width, $this->height);
3451			imagedestroy ($bgimage);
3452		}
3453
3454		// Now it's time to draw a map
3455
3456		// do the node rendering stuff first, regardless of where they are actually drawn.
3457		// this is so we can get the size of the nodes, which links will need if they use offsets
3458		foreach ($this->nodes as $node)
3459		{
3460			// don't try and draw template nodes
3461			debug("Pre-rendering ".$node->name." to get bounding boxes.\n");
3462			if(!is_null($node->x)) $this->nodes[$node->name]->pre_render($image, $this);
3463		}
3464
3465		$all_layers = array_keys($this->seen_zlayers);
3466		sort($all_layers);
3467
3468		foreach ($all_layers as $z)
3469		{
3470			$z_items = $this->seen_zlayers[$z];
3471			debug("Drawing layer $z\n");
3472			// all the map 'furniture' is fixed at z=1000
3473			if($z==1000)
3474			{
3475				foreach ($this->colours as $scalename=>$colours)
3476				{
3477					debug("Drawing KEY for $scalename if necessary.\n");
3478
3479					if( (isset($this->numscales[$scalename])) && (isset($this->keyx[$scalename])) && ($this->keyx[$scalename] >= 0) && ($this->keyy[$scalename] >= 0) )
3480					{
3481						if($this->keystyle[$scalename]=='classic') $this->DrawLegend_Classic($image,$scalename,FALSE);
3482						if($this->keystyle[$scalename]=='horizontal') $this->DrawLegend_Horizontal($image,$scalename,$this->keysize[$scalename]);
3483						if($this->keystyle[$scalename]=='vertical') $this->DrawLegend_Vertical($image,$scalename,$this->keysize[$scalename]);
3484						if($this->keystyle[$scalename]=='inverted') $this->DrawLegend_Vertical($image,$scalename,$this->keysize[$scalename],true);
3485						if($this->keystyle[$scalename]=='tags') $this->DrawLegend_Classic($image,$scalename,TRUE);
3486					}
3487				}
3488
3489				$this->DrawTimestamp($image, $this->timefont, $this->colours['DEFAULT']['TIME']['gdref1']);
3490				if(! is_null($this->min_data_time))
3491				{
3492					$this->DrawTimestamp($image, $this->timefont, $this->colours['DEFAULT']['TIME']['gdref1'],"MIN");
3493					$this->DrawTimestamp($image, $this->timefont, $this->colours['DEFAULT']['TIME']['gdref1'],"MAX");
3494				}
3495				$this->DrawTitle($image, $this->titlefont, $this->colours['DEFAULT']['TITLE']['gdref1']);
3496			}
3497
3498			if(is_array($z_items))
3499			{
3500				foreach($z_items as $it)
3501				{
3502					if(strtolower(get_class($it))=='weathermaplink')
3503					{
3504						// only draw LINKs if they have NODES defined (not templates)
3505						// (also, check if the link still exists - if this is in the editor, it may have been deleted by now)
3506						if ( isset($this->links[$it->name]) && isset($it->a) && isset($it->b))
3507						{
3508							debug("Drawing LINK ".$it->name."\n");
3509							$this->links[$it->name]->Draw($image, $this);
3510						}
3511					}
3512					if(strtolower(get_class($it))=='weathermapnode')
3513					{
3514						// if(!is_null($it->x)) $it->pre_render($image, $this);
3515						if($withnodes)
3516						{
3517							// don't try and draw template nodes
3518							if( isset($this->nodes[$it->name]) && !is_null($it->x))
3519							{
3520								# print "::".get_class($it)."\n";
3521								debug("Drawing NODE ".$it->name."\n");
3522								$this->nodes[$it->name]->NewDraw($image, $this);
3523								$ii=0;
3524								foreach($this->nodes[$it->name]->boundingboxes as $bbox)
3525								{
3526									# $areaname = "NODE:" . $it->name . ':'.$ii;
3527									$areaname = "NODE:N". $it->id . ":" . $ii;
3528									$this->imap->addArea("Rectangle", $areaname, '', $bbox);
3529									debug("Adding imagemap area");
3530									$ii++;
3531								}
3532								debug("Added $ii bounding boxes too\n");
3533							}
3534						}
3535					}
3536				}
3537			}
3538		}
3539
3540		$overlay = myimagecolorallocate($image, 200, 0, 0);
3541
3542		// for the editor, we can optionally overlay some other stuff
3543        if($this->context == 'editor')
3544        {
3545		if($use_rel_overlay)
3546		{
3547		#		$overlay = myimagecolorallocate($image, 200, 0, 0);
3548
3549			// first, we can show relatively positioned NODEs
3550			foreach ($this->nodes as $node) {
3551					if($node->relative_to != '')
3552					{
3553							$rel_x = $this->nodes[$node->relative_to]->x;
3554							$rel_y = $this->nodes[$node->relative_to]->y;
3555							imagearc($image,$node->x, $node->y,
3556									15,15,0,360,$overlay);
3557							imagearc($image,$node->x, $node->y,
3558									16,16,0,360,$overlay);
3559
3560							imageline($image,$node->x, $node->y,
3561									$rel_x, $rel_y, $overlay);
3562					}
3563			}
3564		}
3565
3566		if($use_via_overlay)
3567		{
3568			// then overlay VIAs, so they can be seen
3569			foreach($this->links as $link)
3570			{
3571				foreach ($link->vialist as $via)
3572				{
3573					if(isset($via[2]))
3574					{
3575						$x = $this->nodes[$via[2]]->x + $via[0];
3576						$y = $this->nodes[$via[2]]->y + $via[1];
3577					}
3578					else
3579					{
3580						$x = $via[0];
3581						$y = $via[1];
3582					}
3583					imagearc($image, $x,$y, 10,10,0,360,$overlay);
3584					imagearc($image, $x,$y, 12,12,0,360,$overlay);
3585				}
3586			}
3587		}
3588        }
3589
3590		#$this->myimagestring($image, 3, 200, 100, "Test 1\nLine 2", $overlay,0);
3591
3592#	$this->myimagestring($image, 30, 100, 100, "Test 1\nLine 2", $overlay,0);
3593		#$this->myimagestring($image, 30, 200, 200, "Test 1\nLine 2", $overlay,45);
3594
3595		// Ready to output the results...
3596
3597		if($filename == 'null')
3598		{
3599			// do nothing at all - we just wanted the HTML AREAs for the editor or HTML output
3600		}
3601		else
3602		{
3603			if ($filename == '') { imagepng ($image); }
3604			else {
3605				$result = FALSE;
3606				$functions = TRUE;
3607				if(function_exists('imagejpeg') && preg_match("/\.jpg/i",$filename))
3608				{
3609					debug("Writing JPEG file to $filename\n");
3610					$result = imagejpeg($image, $filename);
3611				}
3612				elseif(function_exists('imagegif') && preg_match("/\.gif/i",$filename))
3613				{
3614					debug("Writing GIF file to $filename\n");
3615					$result = imagegif($image, $filename);
3616				}
3617				elseif(function_exists('imagepng') && preg_match("/\.png/i",$filename))
3618				{
3619					debug("Writing PNG file to $filename\n");
3620					$result = imagepng($image, $filename);
3621				}
3622				else
3623				{
3624					warn("Failed to write map image. No function existed for the image format you requested. [WMWARN12]\n");
3625					$functions = FALSE;
3626				}
3627
3628				if(($result==FALSE) && ($functions==TRUE))
3629				{
3630					if(file_exists($filename))
3631					{
3632						warn("Failed to overwrite existing image file $filename - permissions of existing file are wrong? [WMWARN13]");
3633					}
3634					else
3635					{
3636						warn("Failed to create image file $filename - permissions of output directory are wrong? [WMWARN14]");
3637					}
3638				}
3639			}
3640		}
3641
3642		if($this->context == 'editor2')
3643		{
3644			$cachefile = $this->cachefolder.DIRECTORY_SEPARATOR.dechex(crc32($this->configfile))."_bg.".$this->cachefile_version.".png";
3645			imagepng($image, $cachefile);
3646			$cacheuri = $this->cachefolder.'/'.dechex(crc32($this->configfile))."_bg.".$this->cachefile_version.".png";
3647			$this->mapcache = $cacheuri;
3648		}
3649
3650		if (function_exists('imagecopyresampled'))
3651		{
3652			// if one is specified, and we can, write a thumbnail too
3653			if ($thumbnailfile != '')
3654			{
3655				$result = FALSE;
3656				if ($this->width > $this->height) { $factor=($thumbnailmax / $this->width); }
3657				else { $factor=($thumbnailmax / $this->height); }
3658
3659				$this->thumb_width = $this->width * $factor;
3660				$this->thumb_height = $this->height * $factor;
3661
3662				$imagethumb=imagecreatetruecolor($this->thumb_width, $this->thumb_height);
3663				imagecopyresampled($imagethumb, $image, 0, 0, 0, 0, $this->thumb_width, $this->thumb_height,
3664					$this->width, $this->height);
3665				$result = imagepng($imagethumb, $thumbnailfile);
3666				imagedestroy($imagethumb);
3667
3668
3669
3670				if(($result==FALSE))
3671				{
3672					if(file_exists($filename))
3673					{
3674						warn("Failed to overwrite existing image file $filename - permissions of existing file are wrong? [WMWARN15]");
3675					}
3676					else
3677					{
3678						warn("Failed to create image file $filename - permissions of output directory are wrong? [WMWARN16]");
3679					}
3680				}
3681			}
3682		}
3683		else
3684		{
3685			warn("Skipping thumbnail creation, since we don't have the necessary function. [WMWARN17]");
3686		}
3687		imagedestroy ($image);
3688	}
3689}
3690
3691function CleanUp()
3692{
3693	// destroy all the images we created, to prevent memory leaks
3694	foreach ($this->nodes as $node) { if(isset($node->image)) imagedestroy($node->image); }
3695	#foreach ($this->nodes as $node) { unset($node); }
3696	#foreach ($this->links as $link) { unset($link); }
3697
3698}
3699
3700function PreloadMapHTML()
3701{
3702	debug("Trace: PreloadMapHTML()\n");
3703		//   onmouseover="return overlib('<img src=graph.png>',DELAY,250,CAPTION,'$caption');"  onmouseout="return nd();"
3704
3705		// find the middle of the map
3706		$center_x=$this->width / 2;
3707		$center_y=$this->height / 2;
3708
3709		// loop through everything. Figure out along the way if it's a node or a link
3710		$allitems = array(&$this->nodes, &$this->links);
3711		reset($allitems);
3712
3713		while( list($kk,) = each($allitems))
3714		{
3715			unset($objects);
3716			# $objects = &$this->links;
3717			$objects = &$allitems[$kk];
3718
3719			reset($objects);
3720			while (list($k,) = each($objects))
3721			{
3722				unset($myobj);
3723				$myobj = &$objects[$k];
3724
3725				$type = $myobj->my_type();
3726				$prefix = substr($type,0,1);
3727
3728				$dirs = array();
3729				//print "\n\nConsidering a $type - ".$myobj->name.".\n";
3730				if($type == 'LINK') $dirs = array(IN=>array(0,2), OUT=>array(1,3));
3731				if($type == 'NODE') $dirs = array(IN=>array(0,1,2,3));
3732
3733				// check to see if any of the relevant things have a value
3734				$change = "";
3735				foreach ($dirs as $d=>$parts)
3736				{
3737					//print "$d - ".join(" ",$parts)."\n";
3738					$change .= join('',$myobj->overliburl[$d]);
3739					$change .= $myobj->notestext[$d];
3740				}
3741
3742				if ($this->htmlstyle == "overlib")
3743				{
3744					//print "CHANGE: $change\n";
3745
3746					// skip all this if it's a template node
3747					if($type=='LINK' && ! isset($myobj->a->name)) { $change = ''; }
3748					if($type=='NODE' && ! isset($myobj->x)) { $change = ''; }
3749
3750					if($change != '')
3751					{
3752						//print "Something to be done.\n";
3753						if($type=='NODE')
3754						{
3755							$mid_x = $myobj->x;
3756							$mid_y = $myobj->y;
3757						}
3758						if($type=='LINK')
3759						{
3760							$a_x = $this->nodes[$myobj->a->name]->x;
3761							$a_y = $this->nodes[$myobj->a->name]->y;
3762
3763							$b_x = $this->nodes[$myobj->b->name]->x;
3764							$b_y = $this->nodes[$myobj->b->name]->y;
3765
3766							$mid_x=($a_x + $b_x) / 2;
3767							$mid_y=($a_y + $b_y) / 2;
3768						}
3769						$left=""; $above="";
3770						$img_extra = "";
3771
3772						if ($myobj->overlibwidth != 0)
3773						{
3774							$left="WIDTH," . $myobj->overlibwidth . ",";
3775							$img_extra .= " WIDTH=$myobj->overlibwidth";
3776
3777							if ($mid_x > $center_x) $left.="LEFT,";
3778						}
3779
3780						if ($myobj->overlibheight != 0)
3781						{
3782							$above="HEIGHT," . $myobj->overlibheight . ",";
3783							$img_extra .= " HEIGHT=$myobj->overlibheight";
3784
3785							if ($mid_y > $center_y) $above.="ABOVE,";
3786						}
3787
3788						foreach ($dirs as $dir=>$parts)
3789						{
3790							$caption = ($myobj->overlibcaption[$dir] != '' ? $myobj->overlibcaption[$dir] : $myobj->name);
3791							$caption = $this->ProcessString($caption,$myobj);
3792
3793							$overlibhtml = "onmouseover=\"return overlib('";
3794
3795							$n = 0;
3796							if(sizeof($myobj->overliburl[$dir]) > 0)
3797							{
3798								// print "ARRAY:".is_array($link->overliburl[$dir])."\n";
3799								foreach ($myobj->overliburl[$dir] as $url)
3800								{
3801									if($n>0) { $overlibhtml .= '&lt;br /&gt;'; }
3802									$overlibhtml .= "&lt;img $img_extra src=" . $this->ProcessString($url,$myobj) . "&gt;";
3803									$n++;
3804								}
3805							}
3806							# print "Added $n for $dir\n";
3807							if(trim($myobj->notestext[$dir]) != '')
3808							{
3809								# put in a linebreak if there was an image AND notes
3810								if($n>0) $overlibhtml .= '&lt;br /&gt;';
3811								$note = $this->ProcessString($myobj->notestext[$dir],$myobj);
3812								$note = htmlspecialchars($note, ENT_NOQUOTES);
3813								$note=str_replace("'", "\\&apos;", $note);
3814								$note=str_replace('"', "&quot;", $note);
3815								$overlibhtml .= $note;
3816							}
3817							$overlibhtml .= "',DELAY,250,${left}${above}CAPTION,'" . $caption
3818							. "');\"  onmouseout=\"return nd();\"";
3819
3820							foreach ($parts as $part)
3821							{
3822								$areaname = $type.":" . $prefix . $myobj->id. ":" . $part;
3823								//print "INFOURL for $areaname - ";
3824
3825								$this->imap->setProp("extrahtml", $overlibhtml, $areaname);
3826							}
3827						}
3828					} // if change
3829				} // overlib?
3830
3831				// now look at inforurls
3832				foreach ($dirs as $dir=>$parts)
3833				{
3834					foreach ($parts as $part)
3835					{
3836						# $areaname = $type.":" . $myobj->name . ":" . $part;
3837						$areaname = $type.":" . $prefix . $myobj->id. ":" . $part;
3838						//print "INFOURL for $areaname - ";
3839
3840						if ( ($this->htmlstyle != 'editor') && ($myobj->infourl[$dir] != '') ) {
3841							$this->imap->setProp("href", $this->ProcessString($myobj->infourl[$dir],$myobj), $areaname);
3842							//print "Setting.\n";
3843						}
3844						else
3845						{
3846							//print "NOT Setting.\n";
3847						}
3848					}
3849				}
3850
3851			}
3852		}
3853
3854}
3855
3856function asJS()
3857{
3858	$js='';
3859
3860	$js .= "var Links = new Array();\n";
3861	$js .= "var LinkIDs = new Array();\n";
3862	# $js.=$this->defaultlink->asJS();
3863
3864	foreach ($this->links as $link) { $js.=$link->asJS(); }
3865
3866	$js .= "var Nodes = new Array();\n";
3867	$js .= "var NodeIDs = new Array();\n";
3868	# $js.=$this->defaultnode->asJS();
3869
3870	foreach ($this->nodes as $node) { $js.=$node->asJS(); }
3871
3872	return $js;
3873}
3874
3875function asJSON()
3876{
3877	$json = '';
3878
3879	$json .= "{ \n";
3880
3881	$json .= "\"map\": {  \n";
3882	foreach (array_keys($this->inherit_fieldlist)as $fld)
3883	{
3884		$json .= js_escape($fld).": ";
3885		$json .= js_escape($this->$fld);
3886		$json .= ",\n";
3887	}
3888	$json = rtrim($json,", \n");
3889	$json .= "\n},\n";
3890
3891	$json .= "\"nodes\": {\n";
3892	$json .= $this->defaultnode->asJSON();
3893	foreach ($this->nodes as $node) { $json .= $node->asJSON(); }
3894	$json = rtrim($json,", \n");
3895	$json .= "\n},\n";
3896
3897
3898
3899	$json .= "\"links\": {\n";
3900	$json .= $this->defaultlink->asJSON();
3901	foreach ($this->links as $link) { $json .= $link->asJSON(); }
3902	$json = rtrim($json,", \n");
3903	$json .= "\n},\n";
3904
3905	$json .= "'imap': [\n";
3906	$json .= $this->imap->subJSON("NODE:");
3907	// should check if there WERE nodes...
3908	$json .= ",\n";
3909	$json .= $this->imap->subJSON("LINK:");
3910	$json .= "\n]\n";
3911	$json .= "\n";
3912
3913	$json .= ", 'valid': 1}\n";
3914
3915	return($json);
3916}
3917
3918// This method MUST run *after* DrawMap. It relies on DrawMap to call the map-drawing bits
3919// which will populate the ImageMap with regions.
3920//
3921// imagemapname is a parameter, so we can stack up several maps in the Cacti plugin with their own imagemaps
3922function MakeHTML($imagemapname = "weathermap_imap")
3923{
3924	debug("Trace: MakeHTML()\n");
3925	// PreloadMapHTML fills in the ImageMap info, ready for the HTML to be created.
3926	$this->PreloadMapHTML();
3927
3928	$html='';
3929
3930	$html .= '<div class="weathermapimage" style="margin-left: auto; margin-right: auto; width: '.$this->width.'px;" >';
3931	if ( $this->imageuri != '') {
3932		$html.=sprintf(
3933			'<img id="wmapimage" src="%s" width="%d" height="%d" border="0" usemap="#%s"',
3934			$this->imageuri,
3935			$this->width,
3936			$this->height,
3937			$imagemapname
3938		);
3939		//$html .=  'alt="network weathermap" ';
3940		$html .= '/>';
3941		}
3942	else {
3943		$html.=sprintf(
3944			'<img id="wmapimage" src="%s" width="%d" height="%d" border="0" usemap="#%s"',
3945			$this->imagefile,
3946			$this->width,
3947			$this->height,
3948			$imagemapname
3949		);
3950		//$html .=  'alt="network weathermap" ';
3951		$html .= '/>';
3952	}
3953	$html .= '</div>';
3954
3955	$html .= $this->SortedImagemap($imagemapname);
3956
3957	return ($html);
3958}
3959
3960function SortedImagemap($imagemapname)
3961{
3962        $html='<map name="' . $imagemapname . '" id="' . $imagemapname . '">';
3963
3964        # $html.=$this->imap->subHTML("NODE:",true);
3965        # $html.=$this->imap->subHTML("LINK:",true);
3966
3967        $all_layers = array_keys($this->seen_zlayers);
3968        rsort($all_layers);
3969
3970        debug("Starting to dump imagemap in reverse Z-order...\n");
3971        // this is not precisely efficient, but it'll get us going
3972        // XXX - get Imagemap to store Z order, or map items to store the imagemap
3973        foreach ($all_layers as $z)
3974        {
3975                debug("Writing HTML for layer $z\n");
3976                $z_items = $this->seen_zlayers[$z];
3977                if(is_array($z_items))
3978                {
3979                        debug("   Found things for layer $z\n");
3980
3981                        // at z=1000, the legends and timestamps live
3982                        if($z == 1000) {
3983                            debug("     Builtins fit here.\n");
3984                            $html .= $this->imap->subHTML("LEGEND:",true,($this->context != 'editor'));
3985                            $html .= $this->imap->subHTML("TIMESTAMP",true,($this->context != 'editor'));
3986                        }
3987
3988                        foreach($z_items as $it)
3989                        {
3990                                # print "     " . $it->name . "\n";
3991                                if($it->name != 'DEFAULT' && $it->name != ":: DEFAULT ::")
3992                                {
3993                                        $name = "";
3994                                        if(strtolower(get_class($it))=='weathermaplink') $name = "LINK:L";
3995                                        if(strtolower(get_class($it))=='weathermapnode') $name = "NODE:N";
3996                                        $name .= $it->id . ":";
3997                                        debug("      Writing $name from imagemap\n");
3998                                        // skip the linkless areas if we are in the editor - they're redundant
3999                                        $html .= $this->imap->subHTML($name,true,($this->context != 'editor'));
4000                                }
4001                        }
4002                }
4003        }
4004
4005        $html.='</map>';
4006
4007	return($html);
4008}
4009
4010// update any editor cache files.
4011// if the config file is newer than the cache files, or $agelimit seconds have passed,
4012// then write new stuff, otherwise just return.
4013// ALWAYS deletes files in the cache folder older than $agelimit, also!
4014function CacheUpdate($agelimit=600)
4015{
4016	global $weathermap_lazycounter;
4017
4018	$cachefolder = $this->cachefolder;
4019	$configchanged = filemtime($this->configfile );
4020	// make a unique, but safe, prefix for all cachefiles related to this map config
4021	// we use CRC32 because it makes for a shorter filename, and collisions aren't the end of the world.
4022	$cacheprefix = dechex(crc32($this->configfile));
4023
4024	debug("Comparing files in $cachefolder starting with $cacheprefix, with date of $configchanged\n");
4025
4026	$dh=opendir($cachefolder);
4027
4028	if ($dh)
4029	{
4030		while ($file=readdir($dh))
4031		{
4032			$realfile = $cachefolder . DIRECTORY_SEPARATOR . $file;
4033
4034			if(is_file($realfile) && ( preg_match('/^'.$cacheprefix.'/',$file) ))
4035				//                                            if (is_file($realfile) )
4036			{
4037				debug("$realfile\n");
4038				if( (filemtime($realfile) < $configchanged) || ((time() - filemtime($realfile)) > $agelimit) )
4039				{
4040					debug("Cache: deleting $realfile\n");
4041					unlink($realfile);
4042				}
4043			}
4044		}
4045		closedir ($dh);
4046
4047		foreach ($this->nodes as $node)
4048		{
4049			if(isset($node->image))
4050			{
4051				$nodefile = $cacheprefix."_".dechex(crc32($node->name)).".png";
4052				$this->nodes[$node->name]->cachefile = $nodefile;
4053				imagepng($node->image,$cachefolder.DIRECTORY_SEPARATOR.$nodefile);
4054			}
4055		}
4056
4057		foreach ($this->keyimage as $key=>$image)
4058		{
4059				$scalefile = $cacheprefix."_scale_".dechex(crc32($key)).".png";
4060				$this->keycache[$key] = $scalefile;
4061				imagepng($image,$cachefolder.DIRECTORY_SEPARATOR.$scalefile);
4062		}
4063
4064
4065		$json = "";
4066		$fd = fopen($cachefolder.DIRECTORY_SEPARATOR.$cacheprefix."_map.json","w");
4067		foreach (array_keys($this->inherit_fieldlist)as $fld)
4068		{
4069			$json .= js_escape($fld).": ";
4070			$json .= js_escape($this->$fld);
4071			$json .= ",\n";
4072		}
4073		$json = rtrim($json,", \n");
4074		fputs($fd,$json);
4075		fclose($fd);
4076
4077		$json = "";
4078		$fd = fopen($cachefolder.DIRECTORY_SEPARATOR.$cacheprefix."_tree.json","w");
4079		$id = 10;	// first ID for user-supplied thing
4080
4081		$json .= "{ id: 1, text: 'SCALEs'\n, children: [\n";
4082		foreach ($this->colours as $scalename=>$colours)
4083		{
4084			$json .= "{ id: " . $id++ . ", text:" . js_escape($scalename) . ", leaf: true }, \n";
4085		}
4086		$json = rtrim($json,", \n");
4087		$json .= "]},\n";
4088
4089		$json .= "{ id: 2, text: 'FONTs',\n children: [\n";
4090		foreach ($this->fonts as $fontnumber => $font)
4091		{
4092			if ($font->type == 'truetype')
4093				$json .= sprintf("{ id: %d, text: %s, leaf: true}, \n", $id++, js_escape("Font $fontnumber (TT)"));
4094
4095			if ($font->type == 'gd')
4096				$json .= sprintf("{ id: %d, text: %s, leaf: true}, \n", $id++, js_escape("Font $fontnumber (GD)"));
4097		}
4098		$json = rtrim($json,", \n");
4099		$json .= "]},\n";
4100
4101		$json .= "{ id: 3, text: 'NODEs',\n children: [\n";
4102		$json .= "{ id: ". $id++ . ", text: 'DEFAULT', children: [\n";
4103
4104		$weathemap_lazycounter = $id;
4105		// pass the list of subordinate nodes to the recursive tree function
4106		$json .= $this->MakeTemplateTree( $this->node_template_tree );
4107		$id = $weathermap_lazycounter;
4108
4109		$json = rtrim($json,", \n");
4110		$json .= "]} ]},\n";
4111
4112		$json .= "{ id: 4, text: 'LINKs',\n children: [\n";
4113		$json .= "{ id: ". $id++ . ", text: 'DEFAULT', children: [\n";
4114		$weathemap_lazycounter = $id;
4115		$json .= $this->MakeTemplateTree( $this->link_template_tree );
4116		$id = $weathermap_lazycounter;
4117		$json = rtrim($json,", \n");
4118		$json .= "]} ]}\n";
4119
4120		fputs($fd,"[". $json . "]");
4121		fclose($fd);
4122
4123		$fd = fopen($cachefolder.DIRECTORY_SEPARATOR.$cacheprefix."_nodes.json","w");
4124		$json = "";
4125//		$json = $this->defaultnode->asJSON(TRUE);
4126		foreach ($this->nodes as $node) { $json .= $node->asJSON(TRUE); }
4127		$json = rtrim($json,", \n");
4128		fputs($fd,$json);
4129		fclose($fd);
4130
4131		$fd = fopen($cachefolder.DIRECTORY_SEPARATOR.$cacheprefix."_nodes_lite.json","w");
4132		$json = "";
4133//		$json = $this->defaultnode->asJSON(FALSE);
4134		foreach ($this->nodes as $node) { $json .= $node->asJSON(FALSE); }
4135		$json = rtrim($json,", \n");
4136		fputs($fd,$json);
4137		fclose($fd);
4138
4139
4140
4141		$fd = fopen($cachefolder.DIRECTORY_SEPARATOR.$cacheprefix."_links.json","w");
4142		$json = "";
4143//		$json = $this->defaultlink->asJSON(TRUE);
4144		foreach ($this->links as $link) { $json .= $link->asJSON(TRUE); }
4145		$json = rtrim($json,", \n");
4146		fputs($fd,$json);
4147		fclose($fd);
4148
4149		$fd = fopen($cachefolder.DIRECTORY_SEPARATOR.$cacheprefix."_links_lite.json","w");
4150		$json = "";
4151//		$json = $this->defaultlink->asJSON(FALSE);
4152		foreach ($this->links as $link) { $json .= $link->asJSON(FALSE); }
4153		$json = rtrim($json,", \n");
4154		fputs($fd,$json);
4155		fclose($fd);
4156
4157		$fd = fopen($cachefolder.DIRECTORY_SEPARATOR.$cacheprefix."_imaphtml.json","w");
4158		$json = $this->imap->subHTML("LINK:");
4159		fputs($fd,$json);
4160		fclose($fd);
4161
4162
4163		$fd = fopen($cachefolder.DIRECTORY_SEPARATOR.$cacheprefix."_imap.json","w");
4164		$json = '';
4165		$nodejson = trim($this->imap->subJSON("NODE:"));
4166		if($nodejson != '')
4167		{
4168			$json .= $nodejson;
4169			// should check if there WERE nodes...
4170			$json .= ",\n";
4171		}
4172		$json .= $this->imap->subJSON("LINK:");
4173		fputs($fd,$json);
4174		fclose($fd);
4175
4176	}
4177	else { debug("Couldn't read cache folder.\n"); }
4178}
4179
4180function MakeTemplateTree( &$tree_list, $startpoint="DEFAULT")
4181{
4182	global $weathermap_lazycounter;
4183
4184	$output = "";
4185	foreach ($tree_list[$startpoint] as $subnode)
4186	{
4187		$output .= "{ id: " . $weathermap_lazycounter++ . ", text: " . js_escape($subnode);
4188		if( isset($tree_list[$subnode]))
4189		{
4190			$output .= ", children: [ \n";
4191			$output .= $this->MakeTemplateTree($tree_list, $subnode);
4192			$output = rtrim($output,", \n");
4193			$output .= "] \n";
4194		}
4195		else
4196		{
4197			$output .= ", leaf: true ";
4198		}
4199		$output .= "}, \n";
4200	}
4201
4202	return($output);
4203}
4204
4205function DumpStats($filename="")
4206{
4207	$report = "Feature Statistics:\n\n";
4208	foreach ($this->usage_stats as $key=>$val)
4209	{
4210		$report .= sprintf("%70s => %d\n",$key,$val);
4211	}
4212
4213	if($filename == "") print $report;
4214}
4215
4216};
4217// vim:ts=4:sw=4:
4218?>
4219