1<?php
2/*
3 +-------------------------------------------------------------------------+
4 | Copyright (C) 2004-2021 The Cacti Group                                 |
5 |                                                                         |
6 | This program is free software; you can redistribute it and/or           |
7 | modify it under the terms of the GNU General Public License             |
8 | as published by the Free Software Foundation; either version 2          |
9 | of the License, or (at your option) any later version.                  |
10 |                                                                         |
11 | This program is distributed in the hope that it will be useful,         |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of          |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
14 | GNU General Public License for more details.                            |
15 +-------------------------------------------------------------------------+
16 | Cacti: The Complete RRDtool-based Graphing Solution                     |
17 +-------------------------------------------------------------------------+
18 | This code is designed, written, and maintained by the Cacti Group. See  |
19 | about.php and/or the AUTHORS file for specific developer information.   |
20 +-------------------------------------------------------------------------+
21 | http://www.cacti.net/                                                   |
22 +-------------------------------------------------------------------------+
23*/
24
25define('RRD_NL', " \\\n");
26define('MAX_FETCH_CACHE_SIZE', 5);
27
28if (read_config_option('storage_location')) {
29	/* load crypt libraries only if the Cacti RRDtool Proxy Server is in use */
30	set_include_path($config['include_path'] . '/vendor/phpseclib/');
31	include_once('Math/BigInteger.php');
32	include_once('Crypt/Base.php');
33	include_once('Crypt/Hash.php');
34	include_once('Crypt/Random.php');
35	include_once('Crypt/RSA.php');
36	include_once('Crypt/Rijndael.php');
37
38	global $encryption;
39	$encryption = true;
40}
41
42function escape_command($command) {
43	return $command;		# we escape every single argument now, no need for 'special' escaping
44	#return preg_replace("/(\\\$|`)/", "", $command); # current cacti code
45	#TODO return preg_replace((\\\$(?=\w+|\*|\@|\#|\?|\-|\\\$|\!|\_|[0-9]|\(.*\))|`(?=.*(?=`)))","$2", $command);  #suggested by ldevantier to allow for a single $
46}
47
48/** set the language environment variable for rrdtool functions
49 * @param string $lang		- the desired language to set
50 * @return null
51 */
52function rrdtool_set_language($lang = -1) {
53	global $prev_lang;
54
55	$prev_lang = getenv('LANG');
56
57	if ($lang == -1) {
58		putenv('LANG=' . str_replace('-', '_', CACTI_LOCALE) . '.UTF-8');
59	} else {
60		putenv('LANG=en_EN.UTF-8');
61	}
62}
63
64/** restore the default language environment variable after rrdtool functions
65 * @return null
66 */
67function rrdtool_reset_language() {
68	global $prev_lang;
69
70	putenv('LANG=' . $prev_lang);
71}
72
73function rrd_init($output_to_term = true) {
74	global $config;
75
76	$args = func_get_args();
77	$force_storage_location_local = (isset($config['force_storage_location_local']) && $config['force_storage_location_local'] === true ) ? true : false;
78	$function = ($force_storage_location_local === false && read_config_option('storage_location')) ? '__rrd_proxy_init' : '__rrd_init';
79	return call_user_func_array($function, $args);
80}
81
82function __rrd_init($output_to_term = true) {
83	global $config;
84
85	/* set the rrdtool default font */
86	if (read_config_option('path_rrdtool_default_font')) {
87		putenv('RRD_DEFAULT_FONT=' . read_config_option('path_rrdtool_default_font'));
88	}
89
90	rrdtool_set_language();
91
92	if ($output_to_term) {
93		$command = read_config_option('path_rrdtool') . ' - ';
94	} elseif ($config['cacti_server_os'] == 'win32') {
95		$command = read_config_option('path_rrdtool') . ' - > nul';
96	} else {
97		$command = read_config_option('path_rrdtool') . ' - > /dev/null 2>&1';
98	}
99
100	return popen($command, 'w');
101}
102
103function __rrd_proxy_init($logopt = 'WEBLOG') {
104	global $encryption;
105	$terminator = "_EOT_\r\n";
106	$encryption = true;
107	$rsa = new \phpseclib\Crypt\RSA();
108
109	$rrdp_socket = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
110	if ($rrdp_socket === false) {
111		cacti_log('CACTI2RRDP ERROR: Unable to create socket to connect to RRDtool Proxy Server', false, $logopt, POLLER_VERBOSITY_LOW);
112		return false;
113	}
114
115	if ( read_config_option('rrdp_load_balancing') == 'on' ) {
116		$rrdp_id = rand(1,2);
117		$rrdp = @socket_connect($rrdp_socket, (($rrdp_id == 1 ) ? read_config_option('rrdp_server') : read_config_option('rrdp_server_backup')), (($rrdp_id == 1 ) ? read_config_option('rrdp_port') : read_config_option('rrdp_port_backup')) );
118	} else {
119		$rrdp_id = 1;
120		$rrdp = @socket_connect($rrdp_socket, read_config_option('rrdp_server'), read_config_option('rrdp_port'));
121	}
122
123	if ($rrdp === false) {
124		/* log entry ... */
125		cacti_log('CACTI2RRDP ERROR: Unable to connect to RRDtool Proxy Server #' . $rrdp_id, false, $logopt, POLLER_VERBOSITY_LOW);
126
127		/* ... and try to use backup path */
128		$rrdp_id = ($rrdp_id + 1) % 2;
129		$rrdp = @socket_connect($rrdp_socket, (($rrdp_id == 1 ) ? read_config_option('rrdp_server') : read_config_option('rrdp_server_backup')), (($rrdp_id == 1 ) ? read_config_option('rrdp_port') : read_config_option('rrdp_port_backup')) );
130
131		if ($rrdp === false) {
132			cacti_log('CACTI2RRDP ERROR: Unable to connect to RRDtool Proxy Server #' . $rrdp_id, false, $logopt, POLLER_VERBOSITY_LOW);
133			return false;
134		}
135	}
136
137	$rrdp_fingerprint = ($rrdp_id == 1 ) ? read_config_option('rrdp_fingerprint') : read_config_option('rrdp_fingerprint_backup');
138
139	socket_write($rrdp_socket, read_config_option('rsa_public_key') . $terminator);
140
141	/* read public key being returned by the proxy server */
142	$rrdp_public_key = '';
143	while(1) {
144		$recv = socket_read($rrdp_socket, 1000, PHP_BINARY_READ );
145		if ($recv === false) {
146			/* timeout  */
147			cacti_log('CACTI2RRDP ERROR: Public RSA Key Exchange - Time-out while reading', false, $logopt, POLLER_VERBOSITY_LOW);
148			$rrdp_public_key = false;
149			break;
150		} elseif ($recv == '') {
151			cacti_log('CACTI2RRDP ERROR: Session closed by Proxy.', false, $logopt, POLLER_VERBOSITY_LOW);
152			/* session closed by Proxy */
153			break;
154		} else {
155			$rrdp_public_key .= $recv;
156			if (strpos($rrdp_public_key, $terminator) !== false) {
157				$rrdp_public_key = trim(trim($rrdp_public_key, $terminator));
158				break;
159			}
160		}
161	}
162
163	$rsa->loadKey($rrdp_public_key);
164	$fingerprint = $rsa->getPublicKeyFingerprint();
165
166	if ($rrdp_fingerprint != $fingerprint) {
167		cacti_log('CACTI2RRDP ERROR: Mismatch RSA Fingerprint.', false, $logopt, POLLER_VERBOSITY_LOW);
168		return false;
169	} else {
170		$rrdproxy = array($rrdp_socket, $rrdp_public_key);
171		/* set the rrdtool default font */
172		if (read_config_option('path_rrdtool_default_font')) {
173			rrdtool_execute("setenv RRD_DEFAULT_FONT '" . read_config_option('path_rrdtool_default_font') . "'", false, RRDTOOL_OUTPUT_NULL, $rrdproxy, $logopt = 'WEBLOG');
174		}
175
176		/* disable encryption */
177		$encryption = rrdtool_execute('setcnn encryption off', false, RRDTOOL_OUTPUT_BOOLEAN, $rrdproxy, $logopt = 'WEBLOG') ? false : true;
178		return $rrdproxy;
179	}
180}
181
182function rrd_close() {
183	global $config;
184	$args = func_get_args();
185	$force_storage_location_local = (isset($config['force_storage_location_local']) && $config['force_storage_location_local'] === true) ? true : false;
186	$function = ($force_storage_location_local === false && read_config_option('storage_location')) ? '__rrd_proxy_close' : '__rrd_close';
187	return call_user_func_array($function, $args);
188}
189
190function __rrd_close($rrdtool_pipe) {
191	/* close the rrdtool file descriptor */
192	if (is_resource($rrdtool_pipe)) {
193		pclose($rrdtool_pipe);
194	}
195
196	rrdtool_reset_language();
197}
198
199function __rrd_proxy_close($rrdp) {
200	/* close the rrdtool proxy server connection */
201	$terminator = "_EOT_\r\n";
202	if ($rrdp) {
203		socket_write($rrdp[0], encrypt('quit', $rrdp[1]) . $terminator);
204		@socket_shutdown($rrdp[0], 2);
205		@socket_close($rrdp[0]);
206		return;
207	}
208}
209
210function encrypt($output, $rsa_key) {
211	global $encryption;
212
213	if($encryption) {
214		$rsa = new \phpseclib\Crypt\RSA();
215		$aes = new \phpseclib\Crypt\Rijndael();
216		$aes_key = \phpseclib\Crypt\Random::string(192);
217
218		$aes->setKey($aes_key);
219		$ciphertext = base64_encode($aes->encrypt($output));
220		$rsa->loadKey($rsa_key);
221		$aes_key = base64_encode($rsa->encrypt($aes_key));
222		$aes_key_length = str_pad(dechex(strlen($aes_key)),3,'0',STR_PAD_LEFT);
223
224		return $aes_key_length . $aes_key . $ciphertext;
225	}else {
226		return $output;
227	}
228}
229
230function decrypt($input){
231	global $encryption;
232
233	if($encryption) {
234		$rsa = new \phpseclib\Crypt\RSA();
235		$aes = new \phpseclib\Crypt\Rijndael();
236
237		$rsa_private_key = read_config_option('rsa_private_key');
238
239		$aes_key_length = hexdec(substr($input,0,3));
240		$aes_key = base64_decode(substr($input,3,$aes_key_length));
241		$ciphertext = base64_decode(substr($input,3+$aes_key_length));
242
243		$rsa->loadKey( $rsa_private_key );
244		$aes_key = $rsa->decrypt($aes_key);
245		$aes->setKey($aes_key);
246		$plaintext = $aes->decrypt($ciphertext);
247
248		return $plaintext;
249	}else {
250		return $input;
251	}
252}
253
254function rrdtool_execute() {
255	global $config;
256
257	$args = func_get_args();
258	$force_storage_location_local = (isset($config['force_storage_location_local']) && $config['force_storage_location_local'] === true) ? true : false;
259	$function = ($force_storage_location_local === false && read_config_option('storage_location')) ? '__rrd_proxy_execute' : '__rrd_execute';
260
261	return call_user_func_array($function, $args);
262}
263
264function __rrd_execute($command_line, $log_to_stdout, $output_flag, $rrdtool_pipe = false, $logopt = 'WEBLOG') {
265	global $config;
266
267	static $last_command;
268
269	if (!is_numeric($output_flag)) {
270		$output_flag = RRDTOOL_OUTPUT_STDOUT;
271	}
272
273	/* WIN32: before sending this command off to rrdtool, get rid
274	of all of the backslash (\) characters. Unix does not care; win32 does.
275	Also make sure to replace all of the backslashes at the end of the line,
276	but make sure not to get rid of newlines (\n) that are supposed to be
277	in there (text format) */
278	$command_line = str_replace("\\\n", ' ', $command_line);
279
280	/* output information to the log file if appropriate */
281	cacti_log('CACTI2RRD: ' . read_config_option('path_rrdtool') . " $command_line", $log_to_stdout, $logopt, POLLER_VERBOSITY_DEBUG);
282
283	$debug = '';
284	/* if we want to see the error output from rrdtool; make sure to specify this */
285	if ($config['cacti_server_os'] != 'win32') {
286		if (($output_flag == RRDTOOL_OUTPUT_STDERR || $output_flag == RRDTOOL_OUTPUT_RETURN_STDERR) && !is_resource($rrdtool_pipe)) {
287			$debug .= ' 2>&1';
288		}
289	}
290
291	/* use popen to eliminate the zombie issue */
292	if ($config['cacti_server_os'] == 'unix') {
293		$pipe_mode = 'r';
294	} else {
295		$pipe_mode = 'rb';
296	}
297
298	/* an empty $rrdtool_pipe array means no fp is available */
299	if (!is_resource($rrdtool_pipe)) {
300		if (substr($command_line, 0, 5) == 'fetch' || substr($command_line, 0, 4) == 'info') {
301			rrdtool_set_language('en');
302		} else {
303			rrdtool_set_language();
304		}
305
306		cacti_session_close();
307		if (is_file(read_config_option('path_rrdtool')) && is_executable(read_config_option('path_rrdtool'))) {
308			$descriptorspec = array(
309				0 => array('pipe', 'r'),
310				1 => array('pipe', 'w')
311			);
312
313			$process = proc_open(read_config_option('path_rrdtool') . ' - ' . $debug, $descriptorspec, $pipes);
314
315			if (!is_resource($process)) {
316				unset($process);
317			} else {
318				fwrite($pipes[0], escape_command($command_line) . "\r\nquit\r\n");
319				fclose($pipes[0]);
320				$fp = $pipes[1];
321			}
322		} else {
323			cacti_log("ERROR: RRDtool executable not found, not executable or error in path '" . read_config_option('path_rrdtool') . "'.  No output written to RRDfile.");
324		}
325
326		rrdtool_reset_language();
327	} else {
328		$i = 0;
329		while (1) {
330			if (fwrite($rrdtool_pipe, escape_command(" $command_line") . "\r\n") === false) {
331				cacti_log("ERROR: Detected RRDtool Crash on '$command_line'.  Last command was '$last_command'");
332
333				/* close the invalid pipe */
334				rrd_close($rrdtool_pipe);
335
336				/* open a new rrdtool process */
337				$rrdtool_pipe = rrd_init();
338
339				if ($i > 4) {
340					cacti_log("FATAL: RRDtool Restart Attempts Exceeded. Giving up on '$command_line'.");
341
342					break;
343				} else {
344					$i++;
345				}
346
347				continue;
348			} else {
349				fflush($rrdtool_pipe);
350
351				break;
352			}
353		}
354	}
355
356	/* store the last command to provide rrdtool segfault diagnostics */
357	$last_command = $command_line;
358
359	if (!isset($fp)) {
360		return;
361	}
362
363	switch ($output_flag) {
364		case RRDTOOL_OUTPUT_STDOUT:
365		case RRDTOOL_OUTPUT_GRAPH_DATA:
366			$output = '';
367			while (!feof($fp)) {
368				$output .= fgets($fp, 4096);
369			}
370
371			if (isset($process)) {
372				fclose($fp);
373				proc_close($process);
374			}
375
376			rrdtool_trim_output($output);
377
378			return $output;
379			break;
380		case RRDTOOL_OUTPUT_STDERR:
381		case RRDTOOL_OUTPUT_RETURN_STDERR:
382			$output = fgets($fp, 1000000);
383
384			if (isset($process)) {
385				fclose($fp);
386				proc_close($process);
387			}
388
389			rrdtool_trim_output($output);
390
391			if (substr($output, 1, 3) == 'PNG') {
392				return 'OK';
393			}
394
395			if (substr($output, 0, 5) == '<?xml') {
396				return 'SVG/XML Output OK';
397			}
398
399			if ($output_flag == RRDTOOL_OUTPUT_RETURN_STDERR) {
400				return $output;
401			} else {
402				print $output;
403			}
404
405			break;
406		case RRDTOOL_OUTPUT_NULL:
407		default:
408			return;
409			break;
410	}
411}
412
413function rrdtool_trim_output(&$output) {
414	/* When using RRDtool with proc_open for long strings
415	 * and using the '-' to handle standard in from inside
416	 * the process, RRDtool automatically appends stderr
417	 * to stdout for batch programs to parse the output
418	 * string.  So, therefore, we have to prune that
419	 * output.
420	 */
421	$okpos = strrpos($output, 'OK u:');
422	if ($okpos !== false) {
423		$output = substr($output, 0, $okpos);
424	}
425}
426
427function __rrd_proxy_execute($command_line, $log_to_stdout, $output_flag, $rrdp='', $logopt = 'WEBLOG') {
428	global $config, $encryption;
429
430	static $last_command;
431	$end_of_packet = "_EOP_\r\n";
432	$end_of_sequence = "_EOT_\r\n";
433
434	if (!is_numeric($output_flag)) {
435		$output_flag = RRDTOOL_OUTPUT_STDOUT;
436	}
437
438	/* WIN32: before sending this command off to rrdtool, get rid
439	of all of the '\' characters. Unix does not care; win32 does.
440	Also make sure to replace all of the fancy "\"s at the end of the line,
441	but make sure not to get rid of the "\n"s that are supposed to be
442	in there (text format) */
443	$command_line = str_replace(array($config['rra_path'], "\\\n"), array('.', ' '), $command_line);
444
445	/* output information to the log file if appropriate */
446	cacti_log('CACTI2RRDP: ' . read_config_option('path_rrdtool') . " $command_line", $log_to_stdout, $logopt, POLLER_VERBOSITY_DEBUG);
447
448	/* store the last command to provide rrdtool segfault diagnostics */
449	$last_command = $command_line;
450	$rrdp_auto_close = false;
451
452	if (!$rrdp) {
453		$rrdp = __rrd_proxy_init($logopt);
454		$rrdp_auto_close = true;
455	}
456
457	if (!$rrdp) {
458		cacti_log('CACTI2RRDP ERROR: Unable to connect to RRDtool proxy.', $log_to_stdout, $logopt, POLLER_VERBOSITY_LOW);
459		return null;
460	} else {
461		cacti_log('CACTI2RRDP NOTE: Connection to RRDtool proxy has already been established.', $log_to_stdout, $logopt, POLLER_VERBOSITY_DEBUG);
462	}
463
464	$rrdp_socket = $rrdp[0];
465	$rrdp_public_key = $rrdp[1];
466
467	if (strlen($command_line) >= 8192) {
468		$command_line = gzencode($command_line, 1);
469	}
470	socket_write($rrdp_socket, encrypt($command_line, $rrdp_public_key) . $end_of_sequence);
471
472	$input = '';
473	$output = '';
474
475	while(1) {
476		$recv = socket_read($rrdp_socket, 100000, PHP_BINARY_READ );
477		if ($recv === false) {
478			cacti_log('CACTI2RRDP ERROR: Data Transfer - Time-out while reading.', $log_to_stdout, $logopt, POLLER_VERBOSITY_LOW);
479			break;
480		} elseif ($recv == '') {
481			/* session closed by Proxy */
482			if ($output) {
483				cacti_log('CACTI2RRDP ERROR: Session closed by Proxy.', $log_to_stdout, $logopt, POLLER_VERBOSITY_LOW);
484			}
485			break;
486		} else {
487			$input .= $recv;
488			if (strpos($input, $end_of_sequence) !== false) {
489				$input = str_replace($end_of_sequence, '', $input);
490				$transactions = explode($end_of_packet, $input);
491				foreach ($transactions as $transaction) {
492					$packet = $transaction;
493					$transaction = decrypt($transaction);
494					if($transaction === false){
495						cacti_log("CACTI2RRDP ERROR: Proxy message decryption failed: ###". $packet . '###', $log_to_stdout, $logopt, POLLER_VERBOSITY_LOW);
496						break 2;
497					}
498					if(strpos($transaction, "\x1f\x8b") === 0) {
499						$transaction = gzdecode($transaction);
500					}
501					$output .= $transaction;
502					if (substr_count($output, 'OK u') || substr_count($output, 'ERROR:')) {
503						cacti_log('RRDP: ' . $output, $log_to_stdout, $logopt, POLLER_VERBOSITY_DEBUG);
504						break 2;
505					}
506				}
507			}
508		}
509	}
510
511	if ($rrdp_auto_close) {
512		__rrd_proxy_close($rrdp);
513	}
514
515	switch ($output_flag) {
516		case RRDTOOL_OUTPUT_NULL:
517			return;
518		case RRDTOOL_OUTPUT_STDOUT:
519		case RRDTOOL_OUTPUT_GRAPH_DATA:
520			return rtrim(substr($output, 0, strpos($output, 'OK u')));
521			break;
522		case RRDTOOL_OUTPUT_STDERR:
523			if (substr($output, 1, 3) == 'PNG') {
524				return 'OK';
525			}
526			if (substr($output, 0, 5) == 'GIF87') {
527				return 'OK';
528			}
529			if (substr($output, 0, 5) == '<?xml') {
530			    return 'SVG/XML Output OK';
531            }
532			print $output;
533			break;
534		case RRDTOOL_OUTPUT_BOOLEAN :
535			return (substr_count($output, 'OK u')) ? true : false;
536			break;
537	}
538}
539
540function rrdtool_function_interface_speed($data_local) {
541	$ifHighSpeed = db_fetch_cell_prepared('SELECT field_value
542		FROM host_snmp_cache
543		WHERE host_id = ?
544		AND snmp_query_id = ?
545		AND snmp_index = ?
546		AND field_name="ifHighSpeed"',
547		array($data_local['host_id'], $data_local['snmp_query_id'], $data_local['snmp_index'])
548	);
549
550	$ifSpeed = db_fetch_cell_prepared('SELECT field_value
551		FROM host_snmp_cache
552		WHERE host_id = ?
553		AND snmp_query_id = ?
554		AND snmp_index = ?
555		AND field_name="ifSpeed"',
556		array($data_local['host_id'], $data_local['snmp_query_id'], $data_local['snmp_index'])
557	);
558
559	if (!empty($ifHighSpeed)) {
560		$speed = $ifHighSpeed * 1000000;
561	} elseif (!empty($ifSpeed)) {
562		$speed = $ifSpeed;
563	} else {
564		$speed = read_config_option('default_interface_speed');
565
566		if (empty($speed)) {
567			$speed = '10000000000000';
568		} else {
569			$speed = $speed * 1000000;
570		}
571	}
572
573	return $speed;
574}
575
576function rrdtool_function_create($local_data_id, $show_source, $rrdtool_pipe = false) {
577	global $config, $data_source_types, $consolidation_functions, $encryption;
578
579	include ($config['include_path'] . '/global_arrays.php');
580
581	$data_source_path = get_data_source_path($local_data_id, true);
582
583	/* ok, if that passes lets check to make sure an rra does not already
584	exist, the last thing we want to do is overright data! */
585	if ($show_source != true) {
586		if (read_config_option('storage_location')) {
587			if (rrdtool_execute("file_exists $data_source_path", true, RRDTOOL_OUTPUT_BOOLEAN, $rrdtool_pipe, 'POLLER')) {
588				return -1;
589			}
590		} elseif (file_exists($data_source_path)) {
591			return -1;
592		}
593	}
594
595	/* the first thing we must do is make sure there is at least one
596	rra associated with this data source... *
597	UPDATE: As of version 0.6.6, we are splitting this up into two
598	SQL strings because of the multiple DS per RRD support. This is
599	not a big deal however since this function gets called once per
600	data source */
601
602	$rras = db_fetch_assoc_prepared('SELECT dtd.rrd_step, dsp.x_files_factor,
603		dspr.steps, dspr.rows, dspc.consolidation_function_id,
604		(dspr.rows*dspr.steps) AS rra_order
605		FROM data_template_data AS dtd
606		LEFT JOIN data_source_profiles AS dsp
607		ON dtd.data_source_profile_id=dsp.id
608		LEFT JOIN data_source_profiles_rra AS dspr
609		ON dsp.id=dspr.data_source_profile_id
610		LEFT JOIN data_source_profiles_cf AS dspc
611		ON dsp.id=dspc.data_source_profile_id
612		WHERE dtd.local_data_id = ?
613		AND (dspr.steps IS NOT NULL OR dspr.rows IS NOT NULL)
614		ORDER BY dspc.consolidation_function_id, rra_order',
615		array($local_data_id)
616	);
617
618	/* if we find that this DS has no RRA associated; get out */
619	if (cacti_sizeof($rras) <= 0) {
620		cacti_log("ERROR: There are no RRA's assigned to local_data_id: $local_data_id.");
621		return false;
622	}
623
624	/* create the "--step" line */
625	$create_ds = RRD_NL . '--start 0 --step '. $rras[0]['rrd_step'] . ' ' . RRD_NL;
626
627	/* query the data sources to be used in this .rrd file */
628	$data_sources = db_fetch_assoc_prepared('SELECT id, rrd_heartbeat,
629		rrd_minimum, rrd_maximum, data_source_type_id
630		FROM data_template_rrd
631		WHERE local_data_id = ?
632		ORDER BY local_data_template_rrd_id',
633		array($local_data_id)
634	);
635
636	/* ONLY make a new DS entry if:
637	- There is multiple data sources and this item is not the main one.
638	- There is only one data source (then use it) */
639
640	if (cacti_sizeof($data_sources)) {
641		$data_local = db_fetch_row_prepared('SELECT host_id,
642			snmp_query_id, snmp_index
643			FROM data_local
644			WHERE id = ?',
645			array($local_data_id)
646		);
647
648		$speed = rrdtool_function_interface_speed($data_local);
649
650		foreach ($data_sources as $data_source) {
651			/* use the cacti ds name by default or the user defined one, if entered */
652			$data_source_name = get_data_source_item_name($data_source['id']);
653
654			// Trim the data source maximum
655			$data_source['rrd_maximum'] = trim($data_source['rrd_maximum']);
656
657			if ($data_source['rrd_maximum'] == 'U') {
658				/* in case no maximum is given, use "Undef" value */
659				$data_source['rrd_maximum'] = 'U';
660			} elseif (strpos($data_source['rrd_maximum'], '|query_') !== false) {
661				/* in case a query variable is given, evaluate it */
662				if ($data_source['rrd_maximum'] == '|query_ifSpeed|' || $data_source['rrd_maximum'] == '|query_ifHighSpeed|') {
663					$data_source['rrd_maximum'] = $speed;
664				} else {
665					$data_source['rrd_maximum'] = substitute_snmp_query_data($data_source['rrd_maximum'], $data_local['host_id'], $data_local['snmp_query_id'], $data_local['snmp_index']);
666				}
667			} elseif ($data_source['rrd_maximum'] != 'U' && (int)$data_source['rrd_maximum'] <= (int)$data_source['rrd_minimum']) {
668				/* max > min required, but take care of an "Undef" value */
669				if ($data_source['data_source_type_id'] == 1 || $data_source['data_source_type_id'] == 4) {
670					$data_source['rrd_maximum'] = 'U';
671				} else {
672					$data_source['rrd_maximum'] = (int)$data_source['rrd_minimum'] + 1;
673				}
674			}
675
676			/* min==max==0 won't work with rrdtool */
677			if ($data_source['rrd_minimum'] == 0 && $data_source['rrd_maximum'] == 0) {
678				$data_source['rrd_maximum'] = 'U';
679			}
680
681			$create_ds .= "DS:$data_source_name:" . $data_source_types[$data_source['data_source_type_id']] . ':' . $data_source['rrd_heartbeat'] . ':' . $data_source['rrd_minimum'] . ':' . $data_source['rrd_maximum'] . RRD_NL;
682		}
683	}
684
685	$create_rra = '';
686	/* loop through each available RRA for this DS */
687	foreach ($rras as $rra) {
688		$create_rra .= 'RRA:' . $consolidation_functions[$rra['consolidation_function_id']] . ':' . $rra['x_files_factor'] . ':' . $rra['steps'] . ':' . $rra['rows'] . RRD_NL;
689	}
690
691	/* check for structured path configuration, if in place verify directory
692	   exists and if not create it.
693	 */
694	if (read_config_option('extended_paths') == 'on') {
695		if (read_config_option('storage_location')) {
696			if (false === rrdtool_execute('is_dir ' . dirname($data_source_path), true, RRDTOOL_OUTPUT_BOOLEAN, $rrdtool_pipe, 'POLLER') ) {
697				if (false === rrdtool_execute('mkdir ' . dirname($data_source_path), true, RRDTOOL_OUTPUT_BOOLEAN, $rrdtool_pipe, 'POLLER') ) {
698					cacti_log("ERROR: Unable to create directory '" . dirname($data_source_path) . "'", false);
699				}
700			}
701		} elseif (!is_dir(dirname($data_source_path))) {
702			if ($config['is_web'] == false) {
703				if (mkdir(dirname($data_source_path), 0775)) {
704					if ($config['cacti_server_os'] != 'win32') {
705						$owner_id = fileowner($config['rra_path']);
706						$group_id = filegroup($config['rra_path']);
707
708						if ((chown(dirname($data_source_path), $owner_id)) &&
709								(chgrp(dirname($data_source_path), $group_id))) {
710							/* permissions set ok */
711						} else {
712							cacti_log("ERROR: Unable to set directory permissions for '" . dirname($data_source_path) . "'", false);
713						}
714					}
715				} else {
716					cacti_log("ERROR: Unable to create directory '" . dirname($data_source_path) . "'", false);
717				}
718			} else {
719				cacti_log("WARNING: Poller has not created structured path '" . dirname($data_source_path) . "' yet.", false);
720			}
721		}
722	}
723
724	if ($show_source == true) {
725		return read_config_option('path_rrdtool') . ' create' . RRD_NL . "$data_source_path$create_ds$create_rra";
726	} else {
727		rrdtool_execute("create $data_source_path $create_ds$create_rra", true, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'POLLER');
728	}
729}
730
731function rrdtool_function_update($update_cache_array, $rrdtool_pipe = false) {
732	/* lets count the number of rrd files processed */
733	$rrds_processed = 0;
734
735	foreach ($update_cache_array as $rrd_path => $rrd_fields) {
736		$create_rrd_file = false;
737
738		if (is_array($rrd_fields['times']) && cacti_sizeof($rrd_fields['times'])) {
739			/* create the rrd if one does not already exist */
740			if (read_config_option('storage_location')) {
741				$file_exists = rrdtool_execute("file_exists $rrd_path" , true, RRDTOOL_OUTPUT_BOOLEAN, $rrdtool_pipe, 'POLLER');
742			} else {
743				$file_exists = file_exists($rrd_path);
744			}
745
746			ksort($rrd_fields['times']);
747
748			if ($file_exists === false) {
749				$times = array_keys($rrd_fields['times']);
750				rrdtool_function_create($rrd_fields['local_data_id'], false, $rrdtool_pipe);
751				$create_rrd_file = true;
752			}
753
754			foreach ($rrd_fields['times'] as $update_time => $field_array) {
755				if (empty($update_time)) {
756					/* default the rrdupdate time to now */
757					$rrd_update_values = 'N:';
758				} else {
759					$rrd_update_values = $update_time . ':';
760				}
761
762				$rrd_update_template = '';
763
764				foreach ($field_array as $field_name => $value) {
765					if ($rrd_update_template != '') {
766						$rrd_update_template .= ':';
767						$rrd_update_values .= ':';
768					}
769
770					$rrd_update_template .= $field_name;
771
772					/* if we have "invalid data", give rrdtool an Unknown (U) */
773					if (!isset($value) || !is_numeric($value)) {
774						$value = 'U';
775					}
776
777					$rrd_update_values .= $value;
778				}
779
780				if (cacti_version_compare(get_rrdtool_version(),'1.5','>=')) {
781					$update_options='--skip-past-updates';
782				} else {
783					$update_options='';
784				}
785
786				rrdtool_execute("update $rrd_path $update_options --template $rrd_update_template $rrd_update_values", true, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'POLLER');
787				$rrds_processed++;
788			}
789		}
790	}
791
792	return $rrds_processed;
793}
794
795function rrdtool_function_tune($rrd_tune_array) {
796	global $config, $data_source_types;
797
798	include($config['include_path'] . '/global_arrays.php');
799
800	$data_source_name = get_data_source_item_name($rrd_tune_array['data_source_id']);
801	$data_source_type = $data_source_types[$rrd_tune_array['data-source-type']];
802	$data_source_path = get_data_source_path($rrd_tune_array['data_source_id'], true);
803
804	$rrd_tune = '';
805	if ($rrd_tune_array['heartbeat'] != '') {
806		$rrd_tune .= " --heartbeat $data_source_name:" . $rrd_tune_array['heartbeat'];
807	}
808
809	if ($rrd_tune_array['minimum'] != '') {
810		$rrd_tune .= " --minimum $data_source_name:" . $rrd_tune_array['minimum'];
811	}
812
813	if ($rrd_tune_array['maximum'] != '') {
814		$rrd_tune .= " --maximum $data_source_name:" . $rrd_tune_array['maximum'];
815	}
816
817	if ($rrd_tune_array['data-source-type'] != '') {
818		$rrd_tune .= " --data-source-type $data_source_name:" . $data_source_type;
819	}
820
821	if ($rrd_tune_array['data-source-rename'] != '') {
822		$rrd_tune .= " --data-source-rename $data_source_name:" . $rrd_tune_array['data-source-rename'];
823	}
824
825	if ($rrd_tune != '') {
826		if (file_exists($data_source_path) == true) {
827			if (is_file(read_config_option('path_rrdtool')) && is_executable(read_config_option('path_rrdtool'))) {
828				$fp = popen(read_config_option('path_rrdtool') . " tune $data_source_path $rrd_tune", 'r');
829				pclose($fp);
830
831				cacti_log('CACTI2RRD: ' . read_config_option('path_rrdtool') . " tune $data_source_path $rrd_tune", false, 'WEBLOG', POLLER_VERBOSITY_DEBUG);
832			} else {
833				cacti_log("ERROR: RRDtool executable not found, not executable or error in path '" . read_config_option('path_rrdtool') . "'.  No output written to RRDfile.");
834			}
835		}
836	}
837}
838
839/* rrdtool_function_fetch - given a data source, return all of its data in an array
840   @arg $local_data_id - the data source to fetch data for
841   @arg $start_time - the start time to use for the data calculation. this value can
842     either be absolute (unix timestamp) or relative (to now)
843   @arg $end_time - the end time to use for the data calculation. this value can
844     either be absolute (unix timestamp) or relative (to now)
845   @arg $resolution - the accuracy of the data measured in seconds
846   @arg $show_unknown - Show unknown 'NAN' values in the output as 'U'
847   @arg $rrdtool_file - Don't force Cacti to calculate the file
848   @arg $cf - Specify the consolidation function to use
849   @arg $rrdtool_pipe - a pipe to an rrdtool command
850   @returns - (array) an array containing all data in this data source broken down
851     by each data source item. the maximum of all data source items is included in
852     an item called 'nth_percentile_maximum'.  The array will look as follows:
853
854     $fetch_array['data_source_names'][0] = 'ds1'
855     $fetch_array['data_source_names'][1] = 'ds2'
856     $fetch_array['data_source_names'][2] = 'nth_percentile_maximum'
857     $fetch_array['start_time'] = $timestamp;
858     $fetch_array['end_time']   = $timestamp;
859     $fetch_array['values'][$dsindex1][...]  = $value;
860     $fetch_array['values'][$dsindex2][...]  = $value;
861     $fetch_array['values'][$nth_index][...] = $value;
862
863     Again, the 'nth_percentile_maximum' will have the maximum value amoungst all the
864     data sources for each set of data.  So, if you have traffic_in and traffic_out,
865     each member element in the array will have the maximum of traffic_in and traffic_out
866     in it.
867 */
868function rrdtool_function_fetch($local_data_id, $start_time, $end_time, $resolution = 0, $show_unknown = false, $rrdtool_file = null, $cf = 'AVERAGE', $rrdtool_pipe = false) {
869	global $config;
870
871	include_once($config['library_path'] . '/boost.php');
872
873	/* validate local data id */
874	if (empty($local_data_id) && is_null($rrdtool_file)) {
875		return array();
876	}
877
878	$time = time();
879
880	/* initialize fetch array */
881	$fetch_array = array();
882
883	/* check if we have been passed a file instead of lodal data source to look up */
884	if (is_null($rrdtool_file)) {
885		$data_source_path = get_data_source_path($local_data_id, true);
886	} else {
887		$data_source_path = $rrdtool_file;
888	}
889
890	// Find the correct resolution
891	if ($resolution == 0) {
892		$resolution = rrdtool_function_get_resstep($local_data_id, $start_time, $end_time, 'res');
893	}
894
895	/* update the rrdfile if performing a fetch */
896	boost_fetch_cache_check($local_data_id, $rrdtool_pipe);
897
898	/* build and run the rrdtool fetch command with all of our data */
899	$cmd_line = "fetch $data_source_path $cf -s $start_time -e $end_time";
900	if ($resolution > 0) {
901		$cmd_line .= " -r $resolution";
902	}
903
904	$output = rrdtool_execute($cmd_line, false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe);
905	$output = explode("\n", $output);
906
907	$first  = true;
908	$count  = 0;
909
910	if (cacti_sizeof($output)) {
911		$timestamp = 0;
912		foreach($output as $line) {
913			$line      = trim($line);
914			$max_array = array();
915
916			if ($first) {
917				/* get the data source names */
918				$fetch_array['data_source_names'] = preg_split('/\s+/', $line);
919				$first = false;
920			} elseif ($line != '') {
921				/* process the data sources into an array */
922				$parts     = explode(':', $line);
923				$timestamp = $parts[0];
924				$data      = explode(' ', trim($parts[1]));
925
926				if (!isset($fetch_array['timestamp']['start_time'])) {
927					$fetch_array['timestamp']['start_time'] = $timestamp;
928				}
929
930				/* process out bad data */
931				foreach($data as $index => $number) {
932					if (strtolower($number) == 'nan' || strtolower($number) == '-nan') {
933						if ($show_unknown) {
934							$fetch_array['values'][$index][$timestamp] = 'U';
935						}
936					} else {
937						$fetch_array['values'][$index][$timestamp] = $number + 0;
938					}
939				}
940			}
941		}
942
943		$fetch_array['timestamp']['end_time'] = $timestamp;
944	}
945
946	return $fetch_array;
947}
948
949function rrd_function_process_graph_options($graph_start, $graph_end, &$graph, &$graph_data_array) {
950	global $config, $image_types;
951
952	include($config['include_path'] . '/global_arrays.php');
953
954	/* define some variables */
955	$scale               = '';
956	$rigid               = '';
957	$unit_value          = '';
958	$version             = get_rrdtool_version();
959	$unit_exponent_value = '';
960
961	if ($graph['auto_scale'] == 'on') {
962		switch ($graph['auto_scale_opts']) {
963			case '1': /* autoscale ignores lower, upper limit */
964				$scale = '--alt-autoscale' . RRD_NL;
965				break;
966			case '2': /* autoscale-max, accepts a given lower limit */
967				$scale = '--alt-autoscale-max' . RRD_NL;
968				if (is_numeric($graph['lower_limit'])) {
969					$scale .= '--lower-limit=' . cacti_escapeshellarg($graph['lower_limit']) . RRD_NL;
970				}
971				break;
972			case '3': /* autoscale-min, accepts a given upper limit */
973				$scale = '--alt-autoscale-min' . RRD_NL;
974				if ( is_numeric($graph['upper_limit'])) {
975					$scale .= '--upper-limit=' . cacti_escapeshellarg($graph['upper_limit']) . RRD_NL;
976				}
977				break;
978			case '4': /* auto_scale with limits */
979				$scale = '--alt-autoscale' . RRD_NL;
980				if ( is_numeric($graph['upper_limit'])) {
981					$scale .= '--upper-limit=' . cacti_escapeshellarg($graph['upper_limit']) . RRD_NL;
982				}
983				if ( is_numeric($graph['lower_limit'])) {
984					$scale .= '--lower-limit=' . cacti_escapeshellarg($graph['lower_limit']) . RRD_NL;
985				}
986				break;
987		}
988	} else {
989		if ($graph['upper_limit'] != '') {
990			$scale =  '--upper-limit=' . cacti_escapeshellarg($graph['upper_limit']) . RRD_NL;
991		}
992		if ($graph['lower_limit'] != '') {
993			$scale .= '--lower-limit=' . cacti_escapeshellarg($graph['lower_limit']) . RRD_NL;
994		}
995	}
996
997	if ($graph['auto_scale_log'] == 'on') {
998		$scale .= '--logarithmic' . RRD_NL;
999	}
1000
1001	/* --units=si only defined for logarithmic y-axis scaling, even if it doesn't hurt on linear graphs */
1002	if ($graph['scale_log_units'] == 'on' && $graph['auto_scale_log'] == 'on') {
1003		$scale .= '--units=si' . RRD_NL;
1004	}
1005
1006	if ($graph['auto_scale_rigid'] == 'on') {
1007		$rigid = '--rigid' . RRD_NL;
1008	}
1009
1010	if ($graph['unit_value'] != '') {
1011		$unit_value = '--y-grid=' . cacti_escapeshellarg($graph['unit_value']) . RRD_NL;
1012	}
1013
1014	if (preg_match('/^[0-9]+$/', $graph['unit_exponent_value'])) {
1015		$unit_exponent_value = '--units-exponent=' . cacti_escapeshellarg($graph['unit_exponent_value']) . RRD_NL;
1016	}
1017
1018	/*
1019	 * optionally you can specify and array that overrides some of the db's values, lets set
1020	 * that all up here
1021	 */
1022
1023	/* override: graph height (in pixels) */
1024	if (isset($graph_data_array['graph_height'])) {
1025		$graph_height = $graph_data_array['graph_height'];
1026	} else {
1027		$graph_height = $graph['height'];
1028	}
1029
1030	/* override: graph width (in pixels) */
1031	if (isset($graph_data_array['graph_width'])) {
1032		$graph_width = $graph_data_array['graph_width'];
1033	} else {
1034		$graph_width = $graph['width'];
1035	}
1036
1037	/* override: skip drawing the legend? */
1038	if (isset($graph_data_array['graph_nolegend'])) {
1039		$graph_legend = '--no-legend' . RRD_NL;
1040	} else {
1041		$graph_legend = '';
1042	}
1043
1044	/* export options */
1045	if (isset($graph_data_array['export'])) {
1046		$graph_opts = $graph_data_array['export_filename'] . RRD_NL;
1047	} else {
1048		if (empty($graph_data_array['output_filename'])) {
1049				$graph_opts = '-' . RRD_NL;
1050		} else {
1051			$graph_opts = $graph_data_array['output_filename'] . RRD_NL;
1052		}
1053	}
1054
1055	if (isset($graph_data_array['image_format']) && $graph_data_array['image_format'] == 'png') {
1056		$graph['image_format_id'] = 1;
1057	}
1058
1059	/* basic graph options */
1060	$graph_opts .=
1061		'--imgformat=' . $image_types[$graph['image_format_id']] . RRD_NL .
1062		'--start=' . cacti_escapeshellarg($graph_start) . RRD_NL .
1063		'--end=' . cacti_escapeshellarg($graph_end) . RRD_NL;
1064
1065	$graph_opts .= '--pango-markup ' . RRD_NL;
1066
1067	if (read_config_option('rrdtool_watermark') == 'on') {
1068		$graph_opts .= '--disable-rrdtool-tag ' . RRD_NL;
1069	}
1070
1071	foreach($graph as $key => $value) {
1072		switch($key) {
1073		case 'title_cache':
1074			if (!empty($value)) {
1075				$graph_opts .= '--title=' . cacti_escapeshellarg(html_escape($value)) . RRD_NL;
1076			}
1077			break;
1078		case 'alt_y_grid':
1079			if ($value == CHECKED)  {
1080				$graph_opts .= '--alt-y-grid' . RRD_NL;
1081			}
1082			break;
1083		case 'unit_value':
1084			if (!empty($value)) {
1085				$graph_opts .= '--y-grid=' . cacti_escapeshellarg($value) . RRD_NL;
1086			}
1087			break;
1088		case 'unit_exponent_value':
1089			if (preg_match('/^[0-9]+$/', $value)) {
1090				$graph_opts .= '--units-exponent=' . $value . RRD_NL;
1091			}
1092			break;
1093		case 'height':
1094			if (isset($graph_data_array['graph_height']) && preg_match('/^[0-9]+$/', $graph_data_array['graph_height'])) {
1095				$graph_opts .= '--height=' . $graph_data_array['graph_height'] . RRD_NL;
1096			} else {
1097				$graph_opts .= '--height=' . $value . RRD_NL;
1098			}
1099			break;
1100		case 'width':
1101			if (isset($graph_data_array['graph_width']) && preg_match('/^[0-9]+$/', $graph_data_array['graph_width'])) {
1102				$graph_opts .= '--width=' . $graph_data_array['graph_width'] . RRD_NL;
1103			} else {
1104				$graph_opts .= '--width=' . $value . RRD_NL;
1105			}
1106			break;
1107		case 'graph_nolegend':
1108			if (isset($graph_data_array['graph_nolegend'])) {
1109				$graph_opts .= '--no-legend' . RRD_NL;
1110			} else {
1111				$graph_opts .= '';
1112			}
1113			break;
1114		case 'base_value':
1115			if ($value == 1000 || $value == 1024) {
1116			$graph_opts .= '--base=' . $value . RRD_NL;
1117			}
1118			break;
1119		case 'vertical_label':
1120			if (!empty($value)) {
1121				$graph_opts .= '--vertical-label=' . cacti_escapeshellarg(html_escape($value)) . RRD_NL;
1122			}
1123			break;
1124		case 'slope_mode':
1125			if ($value == CHECKED) {
1126				$graph_opts .= '--slope-mode' . RRD_NL;
1127			}
1128			break;
1129		case 'right_axis':
1130			if (!empty($value)) {
1131				$graph_opts .= '--right-axis ' . cacti_escapeshellarg($value) . RRD_NL;
1132			}
1133			break;
1134		case 'right_axis_label':
1135			if (!empty($value)) {
1136				$graph_opts .= '--right-axis-label ' . cacti_escapeshellarg($value) . RRD_NL;
1137			}
1138			break;
1139		case 'right_axis_format':
1140			if (!empty($value)) {
1141				$format = db_fetch_cell_prepared('SELECT gprint_text from graph_templates_gprint WHERE id = ?', array($value));
1142				$graph_opts .= '--right-axis-format ' . cacti_escapeshellarg(trim(str_replace('%s', '', $format))) . RRD_NL;
1143			}
1144			break;
1145		case 'no_gridfit':
1146			if ($value == CHECKED) {
1147				$graph_opts .= '--no-gridfit' . RRD_NL;
1148			}
1149			break;
1150		case 'unit_length':
1151			if (!empty($value)) {
1152				$graph_opts .= '--units-length ' . cacti_escapeshellarg($value) . RRD_NL;
1153			}
1154			break;
1155		case 'tab_width':
1156			if (!empty($value)) {
1157				$graph_opts .= '--tabwidth ' . cacti_escapeshellarg($value) . RRD_NL;
1158			}
1159			break;
1160		case 'dynamic_labels':
1161			if ($value == CHECKED) {
1162				$graph_opts .= '--dynamic-labels' . RRD_NL;
1163			}
1164			break;
1165		case 'force_rules_legend':
1166			if ($value == CHECKED) {
1167				$graph_opts .= '--force-rules-legend' . RRD_NL;
1168			}
1169			break;
1170		case 'legend_position':
1171			if (cacti_version_compare($version, '1.4', '>=')) {
1172				if (!empty($value)) {
1173					$graph_opts .= '--legend-position ' . cacti_escapeshellarg($value) . RRD_NL;
1174				}
1175			}
1176			break;
1177		case 'legend_direction':
1178			if (cacti_version_compare($version, '1.4', '>=')) {
1179				if (!empty($value)) {
1180					$graph_opts .= '--legend-direction ' . cacti_escapeshellarg($value) . RRD_NL;
1181				}
1182			}
1183			break;
1184		case 'left_axis_formatter':
1185			if (cacti_version_compare($version, '1.4', '>=')) {
1186				if (!empty($value)) {
1187					$graph_opts .= '--left-axis-formatter ' . cacti_escapeshellarg($value) . RRD_NL;
1188				}
1189			}
1190			break;
1191		case 'right_axis_formatter':
1192			if (cacti_version_compare($version, '1.4', '>=')) {
1193				if (!empty($value)) {
1194					$graph_opts .= '--right-axis-formatter ' . cacti_escapeshellarg($value) . RRD_NL;
1195				}
1196			}
1197			break;
1198		}
1199	}
1200
1201	$graph_opts .= "$rigid" . trim("$scale$unit_value$unit_exponent_value$graph_legend", "\n\r " . RRD_NL) . RRD_NL;
1202
1203	/* add a date to the graph legend */
1204	$graph_opts .= rrdtool_function_format_graph_date($graph_data_array);
1205
1206	/* process theme and font styling options */
1207	$graph_opts .= rrdtool_function_theme_font_options($graph_data_array);
1208
1209	/* Replace "|query_*|" in the graph command to replace e.g. vertical_label.  */
1210	$graph_opts = rrd_substitute_host_query_data($graph_opts, $graph, array());
1211
1212	/* provide smooth lines */
1213	if ($graph['slope_mode'] == 'on') {
1214		$graph_opts .= '--slope-mode' . RRD_NL;
1215	}
1216
1217	/* if the user desires a wartermark set it */
1218	if (read_config_option('graph_watermark') != '') {
1219		$graph_opts .= '--watermark ' . cacti_escapeshellarg(read_config_option('graph_watermark')) . RRD_NL;
1220	}
1221
1222	return $graph_opts;
1223}
1224
1225function rrdtool_function_graph($local_graph_id, $rra_id, $graph_data_array, $rrdtool_pipe = false, &$xport_meta = array(), $user = 0) {
1226	global $config, $consolidation_functions, $graph_item_types, $encryption;
1227
1228	include_once($config['library_path'] . '/cdef.php');
1229	include_once($config['library_path'] . '/vdef.php');
1230	include_once($config['library_path'] . '/graph_variables.php');
1231	include_once($config['library_path'] . '/boost.php');
1232	include_once($config['library_path'] . '/xml.php');
1233	include($config['include_path'] . '/global_arrays.php');
1234
1235	/* prevent command injection
1236	 * This function prepares an rrdtool graph statement to be executed by the web server.
1237	 * We have to take care, that the attacker does not insert shell code.
1238	 * As some rrdtool parameters accept "Cacti variables", we have to perform the
1239	 * variable substitution prior to vulnerability checks.
1240	 * We will enclose all parameters in quotes and substitute quotation marks within
1241	 * those parameters.
1242	 */
1243
1244	/* before we do anything; make sure the user has permission to view this graph,
1245	if not then get out */
1246	if ($user > 0) {
1247		if (!is_graph_allowed($local_graph_id, $user)) {
1248			return 'GRAPH ACCESS DENIED';
1249		}
1250	}
1251
1252	if (getenv('LANG') == '') {
1253		putenv('LANG=' . str_replace('-', '_', CACTI_LOCALE) . '.UTF-8');
1254	}
1255
1256	/* check the purge the boost poller output cache, and check for a live image file if caching is enabled */
1257	$graph_data = boost_graph_cache_check($local_graph_id, $rra_id, $rrdtool_pipe, $graph_data_array, false);
1258	if ($graph_data !== false) {
1259		return $graph_data;
1260	}
1261
1262	if (empty($graph_data_array['graph_start'])) {
1263		$graph_data_array['graph_start'] = -86400;
1264	}
1265
1266	if (empty($graph_data_array['graph_end'])) {
1267		$graph_data_array['graph_end']   = -300;
1268	}
1269
1270	$local_data_ids = array_rekey(
1271		db_fetch_assoc_prepared('SELECT dtr.local_data_id
1272			FROM graph_templates_item AS gti
1273			INNER JOIN data_template_rrd AS dtr
1274			ON gti.task_item_id = dtr.id
1275			WHERE dtr.local_data_id > 0
1276			AND gti.local_graph_id = ?',
1277			array($local_graph_id)),
1278		'local_data_id', 'local_data_id'
1279	);
1280
1281	$ds_step = rrdtool_function_get_resstep($local_data_ids, $graph_data_array['graph_start'], $graph_data_array['graph_end'], 'step');
1282
1283	/* if no rra was specified, we need to figure out which one RRDtool will choose using
1284	 * "best-fit" resolution fit algorithm */
1285	if (empty($rra_id)) {
1286		if (empty($graph_data_array['graph_start']) || empty($graph_data_array['graph_end'])) {
1287			$rra['rows']     = 600;
1288			$rra['steps']    = 1;
1289			$rra['timespan'] = 86400;
1290		} else {
1291			/* get a list of RRAs related to this graph */
1292			$rras = get_associated_rras($local_graph_id);
1293
1294			if (cacti_sizeof($rras)) {
1295				foreach ($rras as $unchosen_rra) {
1296					/* the timespan specified in the RRA "timespan" field may not be accurate */
1297					$real_timespan = ($ds_step * $unchosen_rra['steps'] * $unchosen_rra['rows']);
1298
1299					/* make sure the current start/end times fit within each RRA's timespan */
1300					if ($graph_data_array['graph_end'] - $graph_data_array['graph_start'] <= $real_timespan && time() - $graph_data_array['graph_start'] <= $real_timespan) {
1301						/* is this RRA better than the already chosen one? */
1302						if (isset($rra) && $unchosen_rra['steps'] < $rra['steps']) {
1303							$rra = $unchosen_rra;
1304						} elseif (!isset($rra)) {
1305							$rra = $unchosen_rra;
1306						}
1307					}
1308				}
1309			}
1310
1311			if (!isset($rra)) {
1312				$rra['rows']     = 600;
1313				$rra['steps']    = 1;
1314				$rra['timespan'] = 86400;
1315			}
1316		}
1317	} else {
1318		$rra = db_fetch_row_prepared('SELECT
1319			dspr.rows, dsp.step, dspr.steps
1320			FROM data_source_profiles_rra AS dspr
1321			INNER JOIN data_source_profiles AS dsp
1322			ON dspr.data_source_profile_id=dsp.id
1323			WHERE dspr.id = ?',
1324			array($rra_id)
1325		);
1326
1327		if (isset($rra['steps'])) {
1328			$rra['timespan'] = $rra['rows'] * $rra['step'] * $rra['steps'];
1329		} else {
1330			$rra['timespan'] = 86400;
1331			$rra['steps']    = 1;
1332			$rra['rows']     = 600;
1333		}
1334	}
1335
1336	if (!isset($graph_data_array['export_realtime']) && isset($rra['steps'])) {
1337		$rra_seconds = ($ds_step * $rra['steps']);
1338	} else {
1339		$rra_seconds = 5;
1340	}
1341
1342	$graph = db_fetch_row_prepared('SELECT gl.id AS local_graph_id, gl.host_id,
1343		gl.snmp_query_id, gl.snmp_index, gtg.title_cache, gtg.vertical_label,
1344		gtg.slope_mode, gtg.auto_scale, gtg.auto_scale_opts, gtg.auto_scale_log,
1345		gtg.scale_log_units, gtg.auto_scale_rigid, gtg.auto_padding, gtg.base_value,
1346		gtg.upper_limit, gtg.lower_limit, gtg.height, gtg.width, gtg.image_format_id,
1347		gtg.unit_value, gtg.unit_exponent_value, gtg.alt_y_grid,
1348		gtg.right_axis, gtg.right_axis_label, gtg.right_axis_format, gtg.no_gridfit,
1349		gtg.unit_length, gtg.tab_width, gtg.dynamic_labels, gtg.force_rules_legend,
1350		gtg.legend_position, gtg.legend_direction, gtg.right_axis_formatter,
1351		gtg.left_axis_formatter
1352		FROM graph_templates_graph AS gtg
1353		INNER JOIN graph_local AS gl
1354		ON gl.id=gtg.local_graph_id
1355		WHERE gtg.local_graph_id = ?',
1356		array($local_graph_id)
1357	);
1358
1359	/* handle the case where the graph has been deleted */
1360	if (!cacti_sizeof($graph)) {
1361		return false;
1362	}
1363
1364	/* lets make that sql query... */
1365	$graph_items = db_fetch_assoc_prepared('SELECT gti.id AS graph_templates_item_id,
1366		gti.cdef_id, gti.vdef_id, gti.text_format, gti.value, gti.hard_return,
1367		gti.consolidation_function_id, gti.graph_type_id, gtgp.gprint_text,
1368		colors.hex, gti.alpha, gti.line_width, gti.dashes, gti.shift,
1369		gti.dash_offset, gti.textalign, dl.snmp_query_id, dl.snmp_index,
1370		dtr.id AS data_template_rrd_id, dtr.local_data_id,
1371		dtr.rrd_minimum, dtr.rrd_maximum, dtr.data_source_name, dtr.local_data_template_rrd_id
1372		FROM graph_templates_item AS gti
1373		LEFT JOIN data_template_rrd AS dtr
1374		ON gti.task_item_id=dtr.id
1375		LEFT JOIN data_local AS dl
1376		ON dl.id = dtr.local_data_id
1377		LEFT JOIN colors
1378		ON gti.color_id=colors.id
1379		LEFT JOIN graph_templates_gprint AS gtgp
1380		ON gti.gprint_id=gtgp.id
1381		WHERE gti.local_graph_id = ?
1382		ORDER BY gti.sequence',
1383		array($local_graph_id)
1384	);
1385
1386	/* variables for use below */
1387	$graph_defs       = '';
1388	$txt_graph_items  = '';
1389	$pad_number       = 0;
1390
1391	/* override: graph start time */
1392	if (!isset($graph_data_array['graph_start']) || $graph_data_array['graph_start'] == '0') {
1393		$graph_start = -($rra['timespan']);
1394	} else {
1395		$graph_start = $graph_data_array['graph_start'];
1396	}
1397
1398	/* override: graph end time */
1399	if (!isset($graph_data_array['graph_end']) || $graph_data_array['graph_end'] == '0') {
1400		$graph_end = -($rra_seconds);
1401	} else {
1402		$graph_end = $graph_data_array['graph_end'];
1403	}
1404
1405	/* +++++++++++++++++++++++ GRAPH OPTIONS +++++++++++++++++++++++ */
1406
1407	if (!isset($graph_data_array['export_csv'])) {
1408		$graph_opts = rrd_function_process_graph_options($graph_start, $graph_end, $graph, $graph_data_array);
1409	} else {
1410		/* basic export options */
1411		$graph_opts =
1412			'--start=' . cacti_escapeshellarg($graph_start) . RRD_NL .
1413			'--end=' . cacti_escapeshellarg($graph_end) . RRD_NL .
1414			'--maxrows=10000' . RRD_NL;
1415	}
1416
1417	/* +++++++++++++++++++++++ LEGEND: MAGIC +++++++++++++++++++++++ */
1418
1419	$realtimeCachePath = read_config_option('realtime_cache_path');
1420	$dateTimeFormat = read_config_option('graph_dateformat');
1421	$dateTime = date($dateTimeFormat, strtotime(read_config_option('date')));
1422
1423	/* the following fields will be searched for graph variables */
1424	$variable_fields = array(
1425		'text_format' => array(
1426			'process_no_legend' => false
1427		),
1428		'value' => array(
1429			'process_no_legend' => true
1430		),
1431		'cdef_cache' => array(
1432			'process_no_legend' => true
1433		),
1434		'vdef_cache' => array(
1435			'process_no_legend' => true
1436		)
1437	);
1438
1439	$i = 0;
1440	$j = 0;
1441	$nth = 0;
1442	$sum = 0;
1443	$last_graph_cf = array();
1444	if (cacti_sizeof($graph_items)) {
1445		/* we need to add a new column 'cf_reference', so unless PHP 5 is used, this foreach syntax is required */
1446		foreach ($graph_items as $key => $graph_item) {
1447			/* mimic the old behavior: LINE[123], AREA and STACK items use the CF specified in the graph item */
1448			switch ($graph_item['graph_type_id']) {
1449				case GRAPH_ITEM_TYPE_LINE1:
1450				case GRAPH_ITEM_TYPE_LINE2:
1451				case GRAPH_ITEM_TYPE_LINE3:
1452				case GRAPH_ITEM_TYPE_LINESTACK:
1453				case GRAPH_ITEM_TYPE_TIC:
1454				case GRAPH_ITEM_TYPE_AREA:
1455				case GRAPH_ITEM_TYPE_STACK:
1456					$graph_cf = generate_graph_best_cf($graph_item['local_data_id'], $graph_item['consolidation_function_id'], $rra_seconds);
1457					/* remember the last CF for this data source for use with GPRINT
1458					 * if e.g. an AREA/AVERAGE and a LINE/MAX is used
1459					 * we will have AVERAGE first and then MAX, depending on GPRINT sequence */
1460					$last_graph_cf['data_source_name']['local_data_template_rrd_id'] = $graph_cf;
1461					/* remember this for second foreach loop */
1462					$graph_items[$key]['cf_reference'] = $graph_cf;
1463
1464					break;
1465				case GRAPH_ITEM_TYPE_GPRINT:
1466					/* ATTENTION!
1467					 * the 'CF' given on graph_item edit screen for GPRINT is indeed NOT a real 'CF',
1468					 * but an aggregation function
1469					 * see 'man rrdgraph_data' for the correct VDEF based notation
1470					 * so our task now is to 'guess' the very graph_item, this GPRINT is related to
1471					 * and to use that graph_item's CF */
1472					if (isset($last_graph_cf['data_source_name']['local_data_template_rrd_id'])) {
1473						$graph_cf = $last_graph_cf['data_source_name']['local_data_template_rrd_id'];
1474						/* remember this for second foreach loop */
1475						$graph_items[$key]['cf_reference'] = $graph_cf;
1476					} else {
1477						$graph_cf = generate_graph_best_cf($graph_item['local_data_id'], $graph_item['consolidation_function_id'], $rra_seconds);
1478						/* remember this for second foreach loop */
1479						$graph_items[$key]['cf_reference'] = $graph_cf;
1480					}
1481					break;
1482				case GRAPH_ITEM_TYPE_GPRINT_AVERAGE:
1483					$graph_cf = $graph_item['consolidation_function_id'];
1484					$graph_items[$key]['cf_reference'] = $graph_cf;
1485					break;
1486				case GRAPH_ITEM_TYPE_GPRINT_LAST:
1487					$graph_cf = $graph_item['consolidation_function_id'];
1488					$graph_items[$key]['cf_reference'] = $graph_cf;
1489					break;
1490				case GRAPH_ITEM_TYPE_GPRINT_MAX:
1491					$graph_cf = $graph_item['consolidation_function_id'];
1492					$graph_items[$key]['cf_reference'] = $graph_cf;
1493					break;
1494				case GRAPH_ITEM_TYPE_GPRINT_MIN:
1495					$graph_cf = $graph_item['consolidation_function_id'];
1496					$graph_items[$key]['cf_reference'] = $graph_cf;
1497					break;
1498				default:
1499					/* all other types are based on the best matching CF */
1500					$graph_cf = generate_graph_best_cf($graph_item['local_data_id'], $graph_item['consolidation_function_id'], $rra_seconds);
1501					/* remember this for second foreach loop */
1502					$graph_items[$key]['cf_reference'] = $graph_cf;
1503					break;
1504			}
1505
1506			if (!empty($graph_item['local_data_id']) && !isset($cf_ds_cache[$graph_item['data_template_rrd_id']][$graph_cf])) {
1507				/* use a user-specified ds path if one is entered */
1508				if (isset($graph_data_array['export_realtime'])) {
1509					$data_source_path = $realtimeCachePath . '/user_' . hash('sha256',session_id()) . '_' . $graph_item['local_data_id'] . '.rrd';
1510				} else {
1511					$data_source_path = get_data_source_path($graph_item['local_data_id'], true);
1512				}
1513
1514				/* FOR WIN32: Escape all colon for drive letters (ex. D\:/path/to/rra) */
1515				$data_source_path = rrdtool_escape_string($data_source_path);
1516
1517				if (!empty($data_source_path)) {
1518					/* NOTE: (Update) Data source DEF names are created using the graph_item_id; then passed
1519					to a function that matches the digits with letters. rrdtool likes letters instead
1520					of numbers in DEF names; especially with CDEFs. CDEFs are created
1521					the same way, except a 'cdef' is put on the beginning of the hash */
1522					$graph_defs .= 'DEF:' . generate_graph_def_name(strval($i)) . '=' . cacti_escapeshellarg($data_source_path) . ':' . cacti_escapeshellarg($graph_item['data_source_name'], true) . ':' . $consolidation_functions[$graph_cf] . RRD_NL;
1523
1524					$cf_ds_cache[$graph_item['data_template_rrd_id']][$graph_cf] = "$i";
1525
1526					$i++;
1527				}
1528			}
1529
1530			/* cache cdef value here to support data query variables in the cdef string */
1531			if (empty($graph_item['cdef_id'])) {
1532				$graph_item['cdef_cache'] = '';
1533				$graph_items[$j]['cdef_cache'] = '';
1534			} else {
1535				$cdef = get_cdef($graph_item['cdef_id']);
1536
1537				$graph_item['cdef_cache'] = $cdef;
1538				$graph_items[$j]['cdef_cache'] = $cdef;
1539			}
1540
1541			/* cache vdef value here */
1542			if (empty($graph_item['vdef_id'])) {
1543				$graph_item['vdef_cache'] = '';
1544				$graph_items[$j]['vdef_cache'] = '';
1545			} else {
1546				$vdef = get_vdef($graph_item['vdef_id']);
1547
1548				$graph_item['vdef_cache'] = $vdef;
1549				$graph_items[$j]['vdef_cache'] = $vdef;
1550			}
1551
1552			/* +++++++++++++++++++++++ LEGEND: TEXT SUBSTITUTION (<>) +++++++++++++++++++++++ */
1553
1554			/* note the current item_id for easy access */
1555			$graph_item_id = $graph_item['graph_templates_item_id'];
1556
1557			/* loop through each field that we want to substitute values for:
1558			currently: text format and value */
1559			foreach ($variable_fields as $field_name => $field_array) {
1560				/* certain fields do not require values when the legend is not to be shown */
1561				if ($field_array['process_no_legend'] == false && isset($graph_data_array['graph_nolegend'])) {
1562					continue;
1563				}
1564
1565				$graph_variables[$field_name][$graph_item_id] = $graph_item[$field_name];
1566
1567				$search  = array();
1568				$replace = array();
1569
1570				/* date/time substitution */
1571				if (strstr($graph_variables[$field_name][$graph_item_id], '|date_time|')) {
1572					$search[]  = '|date_time|';
1573					$replace[] =  $dateTime;
1574				}
1575
1576				/* data source title substitution */
1577				if (strstr($graph_variables[$field_name][$graph_item_id], '|data_source_title|')) {
1578					$search[]  = '|data_source_title|';
1579					$replace[] =  get_data_source_title($graph_item['local_data_id']);
1580				}
1581
1582				/* data query variables */
1583				$graph_variables[$field_name][$graph_item_id] = rrd_substitute_host_query_data($graph_variables[$field_name][$graph_item_id], $graph, $graph_item);
1584
1585				/* Nth percentile */
1586				if (preg_match_all('/\|([0-9]{1,2}):(bits|bytes):(\d):(current|total|max|total_peak|all_max_current|all_max_peak|aggregate_max|aggregate_sum|aggregate_current|aggregate_peak|aggregate):(\d)?\|/', $graph_variables[$field_name][$graph_item_id], $matches, PREG_SET_ORDER)) {
1587					foreach ($matches as $match) {
1588						$search[]  = $match[0];
1589						$value     = variable_nth_percentile($match, $graph, $graph_item, $graph_items, $graph_start, $graph_end);
1590						$replace[] = $value;
1591
1592						if ($field_name == 'value') {
1593							$xport_meta['NthPercentile'][$nth]['format'] = $match[0];
1594							$xport_meta['NthPercentile'][$nth]['value']  = str_replace($match[0], $value, $graph_variables[$field_name][$graph_item_id]);
1595							$nth++;
1596						}
1597					}
1598				}
1599
1600				/* bandwidth summation */
1601				if (preg_match_all('/\|sum:(\d|auto):(current|total|atomic):(\d):(\d+|auto)\|/', $graph_variables[$field_name][$graph_item_id], $matches, PREG_SET_ORDER)) {
1602					foreach ($matches as $match) {
1603						$search[]  = $match[0];
1604						$value     = variable_bandwidth_summation($match, $graph, $graph_item, $graph_items, $graph_start, $graph_end, $rra['steps'], $ds_step);
1605						$replace[] = $value;
1606
1607						if ($field_name == 'text_format') {
1608							$xport_meta['Summation'][$sum]['format'] = $match[0];
1609							$xport_meta['Summation'][$sum]['value']  = str_replace($match[0], $value, $graph_variables[$field_name][$graph_item_id]);
1610							$sum++;
1611						}
1612					}
1613				}
1614
1615				if (cacti_count($search)) {
1616					$graph_variables[$field_name][$graph_item_id] = str_replace($search, $replace, $graph_variables[$field_name][$graph_item_id]);
1617				}
1618			}
1619
1620			/* if we are not displaying a legend there is no point in us even processing the auto padding,
1621			text format stuff. */
1622			if (!isset($graph_data_array['graph_nolegend'])) {
1623				/* set hard return variable if selected (\n) */
1624				if ($graph_item['hard_return'] == 'on') {
1625					$hardreturn[$graph_item_id] = "\\n";
1626				} else {
1627					$hardreturn[$graph_item_id] = '';
1628				}
1629			}
1630
1631			$j++;
1632		}
1633	}
1634
1635	/* +++++++++++++++++++++++ LEGEND: AUTO PADDING (<>) +++++++++++++++++++++++ */
1636	if (cacti_sizeof($graph_items)) {
1637		/* we need to add a new column 'cf_reference', so unless PHP 5 is used, this foreach
1638		 * syntax is required
1639		 */
1640		foreach ($graph_items as $key => $graph_item) {
1641			/* note the current item_id for easy access */
1642			$graph_item_id = $graph_item['graph_templates_item_id'];
1643
1644			/* if we are not displaying a legend there is no point in us even processing the
1645			 * auto padding, text format stuff.
1646			 */
1647			if (!isset($graph_data_array['graph_nolegend'])) {
1648				/* PADDING: remember this is not perfect! its main use is for the basic graph setup of:
1649				 * AREA - GPRINT-CURRENT - GPRINT-AVERAGE - GPRINT-MAXIMUM \n
1650				 * of course it can be used in other situations, however may not work as intended.
1651				 * If you have any additions to this small peice of code, feel free to send them to me.
1652				 */
1653				if ($graph['auto_padding'] == 'on') {
1654					/* only applies to AREA, STACK and LINEs */
1655					if (preg_match('/(AREA|STACK|LINE[123])/', $graph_item_types[$graph_item['graph_type_id']])) {
1656						$text_format_length = mb_strlen(trim($graph_variables['text_format'][$graph_item_id]), 'UTF-8');
1657
1658						if ($text_format_length > $pad_number) {
1659							$pad_number = $text_format_length;
1660						}
1661					}
1662				}
1663			}
1664		}
1665	}
1666	/* +++++++++++++++++++++++ LEGEND: AUTO PADDING (<>) +++++++++++++++++++++++ */
1667
1668	/* +++++++++++++++++++++++ GRAPH ITEMS: CDEF +++++++++++++++++++++++ */
1669
1670	$i = 0;
1671
1672	/* hack for rrdtool 1.2.x support */
1673	$graph_item_stack_type = '';
1674
1675	if (cacti_sizeof($graph_items)) {
1676		foreach ($graph_items as $graph_item) {
1677			/* hack around RRDtool behavior in first RRA */
1678			$graph_cf = generate_graph_best_cf($graph_item['local_data_id'], $graph_item['consolidation_function_id'], $rra_seconds);
1679
1680			/* first we need to check if there is a DEF for the current data source/cf combination. if so,
1681			we will use that */
1682			if (isset($cf_ds_cache[$graph_item['data_template_rrd_id']][$graph_cf])) {
1683				$cf_id = $graph_item['consolidation_function_id'];
1684			} else {
1685			/* if there is not a DEF defined for the current data source/cf combination, then we will have to
1686			improvise. choose the first available cf in the following order: AVERAGE, MAX, MIN, LAST */
1687				if (isset($cf_ds_cache[$graph_item['data_template_rrd_id']][1])) {
1688					$cf_id = 1; /* CF: AVERAGE */
1689				} elseif (isset($cf_ds_cache[$graph_item['data_template_rrd_id']][3])) {
1690					$cf_id = 3; /* CF: MAX */
1691				} elseif (isset($cf_ds_cache[$graph_item['data_template_rrd_id']][2])) {
1692					$cf_id = 2; /* CF: MIN */
1693				} elseif (isset($cf_ds_cache[$graph_item['data_template_rrd_id']][4])) {
1694					$cf_id = 4; /* CF: LAST */
1695				} else {
1696					$cf_id = 1; /* CF: AVERAGE */
1697				}
1698			}
1699			/* now remember the correct CF reference */
1700			$cf_id = $graph_item['cf_reference'];
1701
1702			/* +++++++++++++++++++++++ GRAPH ITEMS: CDEF START +++++++++++++++++++++++ */
1703
1704			/* make cdef string here; a note about CDEFs in cacti. A CDEF is neither unique to a
1705			data source of global cdef, but is unique when those two variables combine. */
1706			$cdef_graph_defs = '';
1707
1708			if ((!empty($graph_item['cdef_id'])) && (!isset($cdef_cache[$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id]))) {
1709
1710				$cdef_string 	= $graph_variables['cdef_cache'][$graph_item['graph_templates_item_id']];
1711				$magic_item 	= array();
1712				$already_seen	= array();
1713				$sources_seen	= array();
1714
1715				$count_all_ds_dups       = 0;
1716				$count_all_ds_nodups     = 0;
1717				$count_similar_ds_dups   = 0;
1718				$count_similar_ds_nodups = 0;
1719
1720				/* if any of those magic variables are requested ... */
1721				if (preg_match('/(ALL_DATA_SOURCES_(NO)?DUPS|SIMILAR_DATA_SOURCES_(NO)?DUPS)/', $cdef_string) ||
1722					preg_match('/(COUNT_ALL_DS_(NO)?DUPS|COUNT_SIMILAR_DS_(NO)?DUPS)/', $cdef_string)) {
1723
1724					/* now walk through each case to initialize array*/
1725					if (preg_match('/ALL_DATA_SOURCES_DUPS/', $cdef_string)) {
1726						$magic_item['ALL_DATA_SOURCES_DUPS'] = '';
1727					}
1728
1729					if (preg_match('/ALL_DATA_SOURCES_NODUPS/', $cdef_string)) {
1730						$magic_item['ALL_DATA_SOURCES_NODUPS'] = '';
1731					}
1732
1733					if (preg_match('/SIMILAR_DATA_SOURCES_DUPS/', $cdef_string)) {
1734						$magic_item['SIMILAR_DATA_SOURCES_DUPS'] = '';
1735					}
1736
1737					if (preg_match('/SIMILAR_DATA_SOURCES_NODUPS/', $cdef_string)) {
1738						$magic_item['SIMILAR_DATA_SOURCES_NODUPS'] = '';
1739					}
1740
1741					if (preg_match('/COUNT_ALL_DS_DUPS/', $cdef_string)) {
1742						$magic_item['COUNT_ALL_DS_DUPS'] = '';
1743					}
1744
1745					if (preg_match('/COUNT_ALL_DS_NODUPS/', $cdef_string)) {
1746						$magic_item['COUNT_ALL_DS_NODUPS'] = '';
1747					}
1748
1749					if (preg_match('/COUNT_SIMILAR_DS_DUPS/', $cdef_string)) {
1750						$magic_item['COUNT_SIMILAR_DS_DUPS'] = '';
1751					}
1752
1753					if (preg_match('/COUNT_SIMILAR_DS_NODUPS/', $cdef_string)) {
1754						$magic_item['COUNT_SIMILAR_DS_NODUPS'] = '';
1755					}
1756
1757					/* loop over all graph items */
1758					foreach($graph_items as $gi_check) {
1759						/* only work on graph items, omit GRPINTs, COMMENTs and stuff */
1760						if ((preg_match('/(AREA|STACK|LINE[123])/', $graph_item_types[$gi_check['graph_type_id']])) && (!empty($gi_check['data_template_rrd_id']))) {
1761							/* if the user screws up CF settings, PHP will generate warnings if left unchecked */
1762
1763							/* matching consolidation function? */
1764							if (isset($cf_ds_cache[$gi_check['data_template_rrd_id']][$cf_id])) {
1765								$def_name = generate_graph_def_name(strval($cf_ds_cache[$gi_check['data_template_rrd_id']][$cf_id]));
1766
1767								/* do we need ALL_DATA_SOURCES_DUPS? */
1768								if (isset($magic_item['ALL_DATA_SOURCES_DUPS'])) {
1769									$magic_item['ALL_DATA_SOURCES_DUPS'] .= ($count_all_ds_dups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,$def_name,$def_name,UN,0,$def_name,IF,IF"; /* convert unknowns to '0' first */
1770								}
1771
1772								/* do we need COUNT_ALL_DS_DUPS? */
1773								if (isset($magic_item['COUNT_ALL_DS_DUPS'])) {
1774									$magic_item['COUNT_ALL_DS_DUPS'] .= ($count_all_ds_dups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,1,$def_name,UN,0,1,IF,IF"; /* convert unknowns to '0' first */
1775								}
1776
1777								$count_all_ds_dups++;
1778
1779								/* check if this item also qualifies for NODUPS  */
1780								if (!isset($already_seen[$def_name])) {
1781									if (isset($magic_item['ALL_DATA_SOURCES_NODUPS'])) {
1782										$magic_item['ALL_DATA_SOURCES_NODUPS'] .= ($count_all_ds_nodups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,$def_name,$def_name,UN,0,$def_name,IF,IF"; /* convert unknowns to '0' first */
1783									}
1784
1785									if (isset($magic_item['COUNT_ALL_DS_NODUPS'])) {
1786										$magic_item['COUNT_ALL_DS_NODUPS'] .= ($count_all_ds_nodups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,1,$def_name,UN,0,1,IF,IF"; /* convert unknowns to '0' first */
1787									}
1788
1789									$count_all_ds_nodups++;
1790									$already_seen[$def_name] = true;
1791								}
1792
1793								/* check for SIMILAR data sources */
1794								if ($graph_item['data_source_name'] == $gi_check['data_source_name']) {
1795									/* do we need SIMILAR_DATA_SOURCES_DUPS? */
1796									if (isset($magic_item['SIMILAR_DATA_SOURCES_DUPS']) && ($graph_item['data_source_name'] == $gi_check['data_source_name'])) {
1797										$magic_item['SIMILAR_DATA_SOURCES_DUPS'] .= ($count_similar_ds_dups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,$def_name,$def_name,UN,0,$def_name,IF,IF"; /* convert unknowns to '0' first */
1798									}
1799
1800									/* do we need COUNT_SIMILAR_DS_DUPS? */
1801									if (isset($magic_item['COUNT_SIMILAR_DS_DUPS']) && ($graph_item['data_source_name'] == $gi_check['data_source_name'])) {
1802										$magic_item['COUNT_SIMILAR_DS_DUPS'] .= ($count_similar_ds_dups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,1,$def_name,UN,0,1,IF,IF"; /* convert unknowns to '0' first */
1803									}
1804
1805									$count_similar_ds_dups++;
1806
1807									/* check if this item also qualifies for NODUPS  */
1808									if (!isset($sources_seen[$gi_check['data_template_rrd_id']])) {
1809										if (isset($magic_item['SIMILAR_DATA_SOURCES_NODUPS'])) {
1810											$magic_item['SIMILAR_DATA_SOURCES_NODUPS'] .= ($count_similar_ds_nodups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,$def_name,$def_name,UN,0,$def_name,IF,IF"; /* convert unknowns to '0' first */
1811										}
1812
1813										if (isset($magic_item['COUNT_SIMILAR_DS_NODUPS']) && ($graph_item['data_source_name'] == $gi_check['data_source_name'])) {
1814											$magic_item['COUNT_SIMILAR_DS_NODUPS'] .= ($count_similar_ds_nodups == 0 ? '' : ',') . 'TIME,' . (time() - $rra_seconds) . ",GT,1,$def_name,UN,0,1,IF,IF"; /* convert unknowns to '0' first */
1815										}
1816
1817										$count_similar_ds_nodups++;
1818										$sources_seen[$gi_check['data_template_rrd_id']] = true;
1819									}
1820								} # SIMILAR data sources
1821							} # matching consolidation function?
1822						} # only work on graph items, omit GRPINTs, COMMENTs and stuff
1823					} #  loop over all graph items
1824
1825					/* if there is only one item to total, don't even bother with the summation.
1826					 * Otherwise cdef=a,b,c,+,+ is fine. */
1827					if ($count_all_ds_dups > 1 && isset($magic_item['ALL_DATA_SOURCES_DUPS'])) {
1828						$magic_item['ALL_DATA_SOURCES_DUPS'] .= str_repeat(',+', ($count_all_ds_dups - 2)) . ',+';
1829					}
1830
1831					if ($count_all_ds_nodups > 1 && isset($magic_item['ALL_DATA_SOURCES_NODUPS'])) {
1832						$magic_item['ALL_DATA_SOURCES_NODUPS'] .= str_repeat(',+', ($count_all_ds_nodups - 2)) . ',+';
1833					}
1834
1835					if ($count_similar_ds_dups > 1 && isset($magic_item['SIMILAR_DATA_SOURCES_DUPS'])) {
1836						$magic_item['SIMILAR_DATA_SOURCES_DUPS'] .= str_repeat(',+', ($count_similar_ds_dups - 2)) . ',+';
1837					}
1838
1839					if ($count_similar_ds_nodups > 1 && isset($magic_item['SIMILAR_DATA_SOURCES_NODUPS'])) {
1840						$magic_item['SIMILAR_DATA_SOURCES_NODUPS'] .= str_repeat(',+', ($count_similar_ds_nodups - 2)) . ',+';
1841					}
1842
1843					if ($count_all_ds_dups > 1 && isset($magic_item['COUNT_ALL_DS_DUPS'])) {
1844						$magic_item['COUNT_ALL_DS_DUPS'] .= str_repeat(',+', ($count_all_ds_dups - 2)) . ',+';
1845					}
1846
1847					if ($count_all_ds_nodups > 1 && isset($magic_item['COUNT_ALL_DS_NODUPS'])) {
1848						$magic_item['COUNT_ALL_DS_NODUPS'] .= str_repeat(',+', ($count_all_ds_nodups - 2)) . ',+';
1849					}
1850
1851					if ($count_similar_ds_dups > 1 && isset($magic_item['COUNT_SIMILAR_DS_DUPS'])) {
1852						$magic_item['COUNT_SIMILAR_DS_DUPS'] .= str_repeat(',+', ($count_similar_ds_dups - 2)) . ',+';
1853					}
1854
1855					if ($count_similar_ds_nodups > 1 && isset($magic_item['COUNT_SIMILAR_DS_NODUPS'])) {
1856						$magic_item['COUNT_SIMILAR_DS_NODUPS'] .= str_repeat(',+', ($count_similar_ds_nodups - 2)) . ',+';
1857					}
1858				}
1859
1860				/* allow automatic rate calculations on raw guage data */
1861				if (isset($graph_item['local_data_id'])) {
1862					$cdef_string = str_replace('CURRENT_DATA_SOURCE_PI', db_fetch_cell_prepared('SELECT rrd_step FROM data_template_data WHERE local_data_id = ?', array($graph_item['local_data_id'])), $cdef_string);
1863				} else {
1864					$cdef_string = str_replace('CURRENT_DATA_SOURCE_PI', read_config_option('poller_interval'), $cdef_string);
1865				}
1866
1867				$cdef_string = str_replace('CURRENT_DATA_SOURCE', generate_graph_def_name(strval((isset($cf_ds_cache[$graph_item['data_template_rrd_id']][$cf_id]) ? $cf_ds_cache[$graph_item['data_template_rrd_id']][$cf_id] : '0'))), $cdef_string);
1868
1869				/* allow automatic rate calculations on raw guage data */
1870				if (isset($graph_item['local_data_id'])) {
1871					$cdef_string = str_replace('ALL_DATA_SOURCES_DUPS_PI', db_fetch_cell_prepared('SELECT rrd_step FROM data_template_data WHERE local_data_id = ?', array($graph_item['local_data_id'])), $cdef_string);
1872				} else {
1873					$cdef_string = str_replace('ALL_DATA_SOURCES_DUPS_PI', read_config_option('poller_interval'), $cdef_string);
1874				}
1875
1876				/* ALL|SIMILAR_DATA_SOURCES(NO)?DUPS are to be replaced here */
1877				if (isset($magic_item['ALL_DATA_SOURCES_DUPS'])) {
1878					$cdef_string = str_replace('ALL_DATA_SOURCES_DUPS', $magic_item['ALL_DATA_SOURCES_DUPS'], $cdef_string);
1879				}
1880
1881				/* allow automatic rate calculations on raw guage data */
1882				if (isset($graph_item['local_data_id'])) {
1883					$cdef_string = str_replace('ALL_DATA_SOURCES_NODUPS_PI', db_fetch_cell_prepared('SELECT rrd_step FROM data_template_data WHERE local_data_id = ?', array($graph_item['local_data_id'])), $cdef_string);
1884				} else {
1885					$cdef_string = str_replace('ALL_DATA_SOURCES_NODUPS_PI', read_config_option('poller_interval'), $cdef_string);
1886				}
1887
1888				if (isset($magic_item['ALL_DATA_SOURCES_NODUPS'])) {
1889					$cdef_string = str_replace('ALL_DATA_SOURCES_NODUPS', $magic_item['ALL_DATA_SOURCES_NODUPS'], $cdef_string);
1890				}
1891
1892				/* allow automatic rate calculations on raw guage data */
1893				if (isset($graph_item['local_data_id'])) {
1894					$cdef_string = str_replace('SIMILAR_DATA_SOURCES_DUPS_PI', db_fetch_cell_prepared('SELECT rrd_step FROM data_template_data WHERE local_data_id = ?', array($graph_item['local_data_id'])), $cdef_string);
1895				} else {
1896					$cdef_string = str_replace('SIMILAR_DATA_SOURCES_DUPS_PI', read_config_option('poller_interval'), $cdef_string);
1897				}
1898
1899				if (isset($magic_item['SIMILAR_DATA_SOURCES_DUPS'])) {
1900					$cdef_string = str_replace('SIMILAR_DATA_SOURCES_DUPS', $magic_item['SIMILAR_DATA_SOURCES_DUPS'], $cdef_string);
1901				}
1902
1903				if (isset($graph_item['local_data_id'])) {
1904					$cdef_string = str_replace('SIMILAR_DATA_SOURCES_NODUPS_PI', db_fetch_cell_prepared('SELECT rrd_step FROM data_template_data WHERE local_data_id = ?', array($graph_item['local_data_id'])), $cdef_string);
1905				} else {
1906					$cdef_string = str_replace('SIMILAR_DATA_SOURCES_NODUPS_PI', read_config_option('poller_id'), $cdef_string);
1907				}
1908
1909				if (isset($magic_item['SIMILAR_DATA_SOURCES_NODUPS'])) {
1910					$cdef_string = str_replace('SIMILAR_DATA_SOURCES_NODUPS', $magic_item['SIMILAR_DATA_SOURCES_NODUPS'], $cdef_string);
1911				}
1912
1913				/* COUNT_ALL|SIMILAR_DATA_SOURCES(NO)?DUPS are to be replaced here */
1914				if (isset($magic_item['COUNT_ALL_DS_DUPS'])) {
1915					$cdef_string = str_replace('COUNT_ALL_DS_DUPS', $magic_item['COUNT_ALL_DS_DUPS'], $cdef_string);
1916				}
1917
1918				if (isset($magic_item['COUNT_ALL_DS_NODUPS'])) {
1919					$cdef_string = str_replace('COUNT_ALL_DS_NODUPS', $magic_item['COUNT_ALL_DS_NODUPS'], $cdef_string);
1920				}
1921
1922				if (isset($magic_item['COUNT_SIMILAR_DS_DUPS'])) {
1923					$cdef_string = str_replace('COUNT_SIMILAR_DS_DUPS', $magic_item['COUNT_SIMILAR_DS_DUPS'], $cdef_string);
1924				}
1925
1926				if (isset($magic_item['COUNT_SIMILAR_DS_NODUPS'])) {
1927					$cdef_string = str_replace('COUNT_SIMILAR_DS_NODUPS', $magic_item['COUNT_SIMILAR_DS_NODUPS'], $cdef_string);
1928				}
1929
1930				/* data source item variables */
1931				$cdef_string = str_replace('CURRENT_DS_MINIMUM_VALUE', (empty($graph_item['rrd_minimum']) ? '0' : $graph_item['rrd_minimum']), $cdef_string);
1932				$cdef_string = str_replace('CURRENT_DS_MAXIMUM_VALUE', (empty($graph_item['rrd_maximum']) ? '0' : $graph_item['rrd_maximum']), $cdef_string);
1933				$cdef_string = str_replace('CURRENT_GRAPH_MINIMUM_VALUE', (empty($graph['lower_limit']) ? '0' : $graph['lower_limit']), $cdef_string);
1934				$cdef_string = str_replace('CURRENT_GRAPH_MAXIMUM_VALUE', (empty($graph['upper_limit']) ? '0' : $graph['upper_limit']), $cdef_string);
1935
1936				if ((strpos($cdef_string, '|query_ifHighSpeed|') !== false) ||
1937					(strpos($cdef_string, '|query_ifSpeed|') !== false)) {
1938					$local_data = db_fetch_row_prepared('SELECT *
1939						FROM data_local
1940						WHERE id = ?',
1941						array($graph_item['local_data_id']));
1942
1943					$speed = rrdtool_function_interface_speed($local_data);
1944
1945					$cdef_string = str_replace(array('|query_ifHighSpeed|','|query_ifSpeed|'), array($speed, $speed), $cdef_string);
1946				}
1947
1948				/* replace query variables in cdefs */
1949				$cdef_string = rrd_substitute_host_query_data($cdef_string, $graph, $graph_item);
1950
1951				/* make the initial 'virtual' cdef name: 'cdef' + [a,b,c,d...] */
1952				$cdef_graph_defs .= 'CDEF:cdef' . generate_graph_def_name(strval($i)) . '=';
1953				/* prohibit command injection and provide platform specific quoting */
1954				$cdef_graph_defs .= cacti_escapeshellarg(sanitize_cdef($cdef_string), true);
1955				$cdef_graph_defs .= " \\\n";
1956
1957				/* the CDEF cache is so we do not create duplicate CDEF's on a graph */
1958				$cdef_cache[$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id] = $i;
1959			}
1960
1961			/* add the cdef string to the end of the def string */
1962			$graph_defs .= $cdef_graph_defs;
1963
1964			/* +++++++++++++++++++++++ GRAPH ITEMS: CDEFs END   +++++++++++++++++++++++ */
1965
1966			/* +++++++++++++++++++++++ GRAPH ITEMS: VDEFs START +++++++++++++++++++++++ */
1967
1968			/* make vdef string here, copied from cdef stuff */
1969			$vdef_graph_defs = '';
1970
1971			if ((!empty($graph_item['vdef_id'])) && (!isset($vdef_cache[$graph_item['vdef_id']][$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id]))) {
1972				$vdef_string = $graph_variables['vdef_cache'][$graph_item['graph_templates_item_id']];
1973				/* do we refer to a CDEF within this VDEF? */
1974				if ($graph_item['cdef_id'] != '0') {
1975					/* 'calculated' VDEF: use (cached) CDEF as base, only way to get calculations into VDEFs */
1976					$vdef_string = 'cdef' . str_replace('CURRENT_DATA_SOURCE', generate_graph_def_name(strval(isset($cdef_cache[$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id]) ? $cdef_cache[$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id] : '0')), $vdef_string);
1977				} else {
1978					/* 'pure' VDEF: use DEF as base */
1979					$vdef_string = str_replace('CURRENT_DATA_SOURCE', generate_graph_def_name(strval(isset($cf_ds_cache[$graph_item['data_template_rrd_id']][$cf_id]) ? $cf_ds_cache[$graph_item['data_template_rrd_id']][$cf_id] : '0')), $vdef_string);
1980				}
1981
1982				# TODO: It would be possible to refer to a CDEF, but that's all. So ALL_DATA_SOURCES_NODUPS and stuff can't be used directly!
1983				# $vdef_string = str_replace('ALL_DATA_SOURCES_NODUPS', $magic_item['ALL_DATA_SOURCES_NODUPS'], $vdef_string);
1984				# $vdef_string = str_replace('ALL_DATA_SOURCES_DUPS', $magic_item['ALL_DATA_SOURCES_DUPS'], $vdef_string);
1985				# $vdef_string = str_replace('SIMILAR_DATA_SOURCES_NODUPS', $magic_item['SIMILAR_DATA_SOURCES_NODUPS'], $vdef_string);
1986				# $vdef_string = str_replace('SIMILAR_DATA_SOURCES_DUPS', $magic_item['SIMILAR_DATA_SOURCES_DUPS'], $vdef_string);
1987
1988				/* make the initial 'virtual' vdef name */
1989				$vdef_graph_defs .= 'VDEF:vdef' . generate_graph_def_name(strval($i)) . '=';
1990				$vdef_graph_defs .= cacti_escapeshellarg(sanitize_cdef($vdef_string));
1991				$vdef_graph_defs .= " \\\n";
1992
1993				/* the VDEF cache is so we do not create duplicate VDEFs on a graph,
1994				* but take info account, that same VDEF may use different CDEFs
1995				* so index over VDEF_ID, CDEF_ID per DATA_TEMPLATE_RRD_ID, lvm */
1996				$vdef_cache[$graph_item['vdef_id']][$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id] = $i;
1997			}
1998
1999			/* add the cdef string to the end of the def string */
2000			$graph_defs .= $vdef_graph_defs;
2001
2002			/* +++++++++++++++++++++++ GRAPH ITEMS: VDEFs END +++++++++++++++++++++++ */
2003
2004			/* note the current item_id for easy access */
2005			$graph_item_id = $graph_item['graph_templates_item_id'];
2006
2007			/* we put this in a variable so it can be manipulated before mainly used
2008			if we want to skip it, like below */
2009			$current_graph_item_type = $graph_item_types[$graph_item['graph_type_id']];
2010
2011			/* IF this graph item has a data source... get a DEF name for it, or the cdef if that applies
2012			to this graph item */
2013			if ($graph_item['cdef_id'] == '0') {
2014				if (isset($cf_ds_cache[$graph_item['data_template_rrd_id']][$cf_id])) {
2015					$data_source_name = generate_graph_def_name(strval($cf_ds_cache[$graph_item['data_template_rrd_id']][$cf_id]));
2016				} else {
2017					$data_source_name = '';
2018				}
2019			} else {
2020				$data_source_name = 'cdef' . generate_graph_def_name(strval($cdef_cache[$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id]));
2021			}
2022
2023			/* IF this graph item has a data source... get a DEF name for it, or the vdef if that applies
2024			to this graph item */
2025			if ($graph_item['vdef_id'] == '0') {
2026				/* do not overwrite $data_source_name that stems from cdef above */
2027			} else {
2028				$data_source_name = 'vdef' . generate_graph_def_name(strval($vdef_cache[$graph_item['vdef_id']][$graph_item['cdef_id']][$graph_item['data_template_rrd_id']][$cf_id]));
2029			}
2030
2031			/* to make things easier... if there is no text format set; set blank text */
2032			if (!isset($graph_variables['text_format'][$graph_item_id])) {
2033				$graph_variables['text_format'][$graph_item_id] = '';
2034			}
2035
2036			if (!isset($hardreturn[$graph_item_id])) {
2037				$hardreturn[$graph_item_id] = '';
2038			}
2039
2040			/* +++++++++++++++++++++++ GRAPH ITEMS +++++++++++++++++++++++ */
2041
2042			/* most of the calculations have been done above. now we have for print everything out
2043			in an RRDtool-friendly fashion */
2044
2045			$need_rrd_nl = true;
2046
2047			/* initialize color support */
2048			$graph_item_color_code = '';
2049			if (!empty($graph_item['hex'])) {
2050				$graph_item_color_code = '#' . $graph_item['hex'];
2051				$graph_item_color_code .= $graph_item['alpha'];
2052			}
2053
2054			/* initialize dash support */
2055			$dash = '';
2056			if ($graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_LINE1 ||
2057				$graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_LINE2 ||
2058				$graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_LINE3 ||
2059				$graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_LINESTACK ||
2060				$graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_HRULE ||
2061				$graph_item['graph_type_id'] == GRAPH_ITEM_TYPE_VRULE) {
2062				if (!empty($graph_item['dashes'])) {
2063					$dash .= ':dashes=' . $graph_item['dashes'];
2064				}
2065
2066				if (!empty($graph_item['dash_offset'])) {
2067					$dash .= ':dash-offset=' . $graph_item['dash_offset'];
2068				}
2069			}
2070
2071			if (!isset($graph_data_array['export_csv'])) {
2072				switch($graph_item['graph_type_id']) {
2073				case GRAPH_ITEM_TYPE_COMMENT:
2074					if (!isset($graph_data_array['graph_nolegend'])) {
2075						$comments = array();
2076
2077						$comment_arg = rrd_substitute_host_query_data($graph_variables['text_format'][$graph_item_id], $graph, $graph_item);
2078
2079						// Check for a wrapping comment
2080						$max = read_config_option('max_title_length') - 20;
2081						if (strlen($comment_arg) > $max) {
2082							$comments = explode("\n", wordwrap($comment_arg, $max));
2083						}else{
2084							$comments[] = $comment_arg;
2085						}
2086
2087						foreach($comments as $comment) {
2088							# next, compute the argument of the COMMENT statement and perform injection counter measures
2089							if (trim($comment) == '') { # an empty COMMENT must be treated with care
2090								$comment = cacti_escapeshellarg(' ' . $hardreturn[$graph_item_id]);
2091							} else {
2092								$comment = cacti_escapeshellarg(rrdtool_escape_string(html_escape($comment)) . $hardreturn[$graph_item_id]);
2093							}
2094
2095							# create rrdtool specific command line
2096							$txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $comment . ' ';
2097						}
2098					}
2099
2100					break;
2101				case GRAPH_ITEM_TYPE_TEXTALIGN:
2102					if (!isset($graph_data_array['graph_nolegend'])) {
2103						if (!empty($graph_item['textalign'])) {
2104							$txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $graph_item['textalign'];
2105						}
2106					}
2107
2108					break;
2109				case GRAPH_ITEM_TYPE_GPRINT:
2110					$text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id]), false);
2111
2112					if ($graph_item['vdef_id'] == '0') {
2113						$txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $data_source_name . ':' . $consolidation_functions[$graph_item['consolidation_function_id']] . ':' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' ';
2114					} else {
2115						$txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $data_source_name . ':' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' ';
2116					}
2117
2118					break;
2119				case GRAPH_ITEM_TYPE_GPRINT_AVERAGE:
2120					if (!isset($graph_data_array['graph_nolegend'])) {
2121						$text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id]));
2122
2123						if ($graph_item['vdef_id'] == '0') {
2124							$txt_graph_items .= 'GPRINT:' . $data_source_name . ':AVERAGE:' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' ';
2125						} else {
2126							$txt_graph_items .= 'GPRINT:' . $data_source_name . ':' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' ';
2127						}
2128					}
2129
2130					break;
2131				case GRAPH_ITEM_TYPE_GPRINT_LAST:
2132					if (!isset($graph_data_array['graph_nolegend'])) {
2133						$text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id]));
2134
2135						if ($graph_item['vdef_id'] == '0') {
2136							$txt_graph_items .= 'GPRINT:' . $data_source_name . ':LAST:' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' ';
2137						} else {
2138							$txt_graph_items .= 'GPRINT:' . $data_source_name . ':' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' ';
2139						}
2140					}
2141
2142					break;
2143				case GRAPH_ITEM_TYPE_GPRINT_MAX:
2144					if (!isset($graph_data_array['graph_nolegend'])) {
2145						$text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id]));
2146
2147						if ($graph_item['vdef_id'] == '0') {
2148							$txt_graph_items .= 'GPRINT:' . $data_source_name . ':MAX:' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' ';
2149						} else {
2150							$txt_graph_items .= 'GPRINT:' . $data_source_name . ':' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' ';
2151						}
2152					}
2153
2154					break;
2155				case GRAPH_ITEM_TYPE_GPRINT_MIN:
2156					if (!isset($graph_data_array['graph_nolegend'])) {
2157						$text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id]));
2158
2159						if ($graph_item['vdef_id'] == '0') {
2160							$txt_graph_items .= 'GPRINT:' . $data_source_name . ':MIN:' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' ';
2161						} else {
2162							$txt_graph_items .= 'GPRINT:' . $data_source_name . ':' . cacti_escapeshellarg($text_format . $graph_item['gprint_text'] . $hardreturn[$graph_item_id]) . ' ';
2163						}
2164					}
2165
2166					break;
2167				case GRAPH_ITEM_TYPE_AREA:
2168					$text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id] != '' ? str_pad($graph_variables['text_format'][$graph_item_id], $pad_number):''));
2169
2170					$txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $data_source_name . $graph_item_color_code . ':' . cacti_escapeshellarg($text_format . $hardreturn[$graph_item_id]) . ' ';
2171
2172					if ($graph_item['shift'] == CHECKED && abs($graph_item['value']) > 0) {      # create a SHIFT statement
2173						$txt_graph_items .= RRD_NL . 'SHIFT:' . $data_source_name . ':' . $graph_item['value'];
2174					}
2175
2176					break;
2177				case GRAPH_ITEM_TYPE_STACK:
2178					$text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id] != '' ? str_pad($graph_variables['text_format'][$graph_item_id],$pad_number):''));
2179
2180					$txt_graph_items .= 'AREA:' . $data_source_name . $graph_item_color_code . ':' . cacti_escapeshellarg($text_format . $hardreturn[$graph_item_id]) . ':STACK';
2181
2182					if ($graph_item['shift'] == CHECKED && $graph_item['value'] > 0) {      # create a SHIFT statement
2183						$txt_graph_items .= RRD_NL . 'SHIFT:' . $data_source_name . ':' . $graph_item['value'];
2184					}
2185
2186					break;
2187				case GRAPH_ITEM_TYPE_LINE1:
2188				case GRAPH_ITEM_TYPE_LINE2:
2189				case GRAPH_ITEM_TYPE_LINE3:
2190					$text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id] != '' ? str_pad($graph_variables['text_format'][$graph_item_id], $pad_number):''));
2191
2192					$txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $data_source_name . $graph_item_color_code . ':' . cacti_escapeshellarg($text_format . $hardreturn[$graph_item_id]) . ' ';
2193
2194					if ($graph_item['shift'] == CHECKED && $graph_item['value'] > 0) {      # create a SHIFT statement
2195						$txt_graph_items .= RRD_NL . 'SHIFT:' . $data_source_name . ':' . $graph_item['value'];
2196					}
2197
2198					break;
2199				case GRAPH_ITEM_TYPE_LINESTACK:
2200					$text_format = rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id] != '' ? str_pad($graph_variables['text_format'][$graph_item_id], $pad_number):''));
2201
2202					$txt_graph_items .= 'LINE' . $graph_item['line_width'] . ':' . $data_source_name . $graph_item_color_code . ':' . cacti_escapeshellarg($text_format . $hardreturn[$graph_item_id]) . ':STACK' . $dash;
2203
2204					if ($graph_item['shift'] == CHECKED && $graph_item['value'] > 0) {      # create a SHIFT statement
2205						$txt_graph_items .= RRD_NL . 'SHIFT:' . $data_source_name . ':' . $graph_item['value'];
2206					}
2207
2208					break;
2209				case GRAPH_ITEM_TYPE_TIC:
2210					$_fraction = (empty($graph_item['graph_type_id']) ? '' : (':' . $graph_item['value']));
2211					$_legend   = ':' . cacti_escapeshellarg(rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id])) . $hardreturn[$graph_item_id]);
2212					$txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $data_source_name . $graph_item_color_code . $_fraction . $_legend;
2213
2214					break;
2215				case GRAPH_ITEM_TYPE_HRULE:
2216					/* perform variable substitution; if this does not return a number, rrdtool will FAIL! */
2217					$substitute = strip_alpha(rrd_substitute_host_query_data($graph_variables['value'][$graph_item_id], $graph, $graph_item));
2218
2219					$text_format = rrdtool_escape_string(html_escape(rrd_substitute_host_query_data($graph_variables['text_format'][$graph_item_id], $graph, $graph_item)));
2220
2221					/* don't break rrdtool if the strip_alpha() returns false */
2222					if ($substitute !== false) {
2223						$graph_variables['value'][$graph_item_id] = $substitute;
2224					} else {
2225						$graph_variables['value'][$graph_item_id] = '0';
2226					}
2227
2228					$txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $graph_variables['value'][$graph_item_id] . $graph_item_color_code . ':' . cacti_escapeshellarg($text_format . $hardreturn[$graph_item_id]) . '' . $dash;
2229
2230					break;
2231				case GRAPH_ITEM_TYPE_VRULE:
2232					if (substr_count($graph_item['value'], ':')) {
2233						$value_array = explode(':', $graph_item['value']);
2234
2235						if ($value_array[0] < 0) {
2236							$value = date('U') - (-3600 * $value_array[0]) - 60 * $value_array[1];
2237						} else {
2238							$value = date('U', mktime($value_array[0],$value_array[1],0));
2239						}
2240
2241						$txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $value . $graph_item_color_code . ':' . cacti_escapeshellarg(rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id])) . $hardreturn[$graph_item_id]) . $dash;
2242					} elseif (is_numeric($graph_item['value'])) {
2243						$value = $graph_item['value'];
2244
2245						$txt_graph_items .= $graph_item_types[$graph_item['graph_type_id']] . ':' . $value . $graph_item_color_code . ':' . cacti_escapeshellarg(rrdtool_escape_string(html_escape($graph_variables['text_format'][$graph_item_id])) . $hardreturn[$graph_item_id]) . $dash;
2246					}
2247
2248					break;
2249				default:
2250					$need_rrd_nl = false;
2251				}
2252			} else {
2253				if (preg_match('/^(AREA|AREA:STACK|LINE[123]|STACK)$/', $graph_item_types[$graph_item['graph_type_id']])) {
2254					/* give all export items a name */
2255					if (trim($graph_variables['text_format'][$graph_item_id]) == '') {
2256						$legend_name = 'col' . $j . '-' . $data_source_name;
2257					} else {
2258						$legend_name = $graph_variables['text_format'][$graph_item_id];
2259					}
2260					$stacked_columns['col' . $j] = ($graph_item_types[$graph_item['graph_type_id']] == 'STACK') ? 1 : 0;
2261					$j++;
2262
2263					$txt_graph_items .= 'XPORT:' . cacti_escapeshellarg($data_source_name) . ':' . str_replace(':', '', cacti_escapeshellarg($legend_name)) ;
2264				} else {
2265					$need_rrd_nl = false;
2266				}
2267			}
2268
2269			$i++;
2270
2271			if (($i < cacti_sizeof($graph_items)) && ($need_rrd_nl)) {
2272				$txt_graph_items .= RRD_NL;
2273			}
2274		}
2275	}
2276
2277	if (!isset($graph_data_array['export_csv']) || $graph_data_array['export_csv'] != true) {
2278		$graph_array = api_plugin_hook_function('rrd_graph_graph_options', array('graph_opts' => $graph_opts, 'graph_defs' => $graph_defs, 'txt_graph_items' => $txt_graph_items, 'graph_id' => $local_graph_id, 'start' => $graph_start, 'end' => $graph_end));
2279
2280		if (!empty($graph_array)) {
2281			$graph_defs = $graph_array['graph_defs'];
2282			$txt_graph_items = $graph_array['txt_graph_items'];
2283			$graph_opts = $graph_array['graph_opts'];
2284		}
2285
2286		/* either print out the source or pass the source onto rrdtool to get us a nice PNG */
2287		if (isset($graph_data_array['print_source'])) {
2288			$source_command_line = read_config_option('path_rrdtool') . ' graph ' . $graph_opts . $graph_defs . $txt_graph_items;
2289			$source_command_line_lengths = strlen(str_replace("\\\n", ' ', $source_command_line));
2290			print '<PRE>' . html_escape($source_command_line) . '</PRE>';
2291			print '<span class="textInfo">' . 'RRDtool Command lengths = ' . $source_command_line_lengths . ' charaters.</span><br>';
2292			if ( $config['cacti_server_os'] == 'win32' && $source_command_line_lengths > 8191 ) {
2293				print '<PRE>' . 'Warning: The Cacti OS is Windows system, RRDtool Command lengths should not exceed 8191 charaters.' . '</PRE>';
2294			}
2295		} else {
2296			if (isset($graph_data_array['graphv'])) {
2297				$graph = 'graphv';
2298			} else {
2299				$graph = 'graph';
2300			}
2301
2302			if (isset($graph_data_array['get_error'])) {
2303				return rrdtool_execute("graph $graph_opts$graph_defs$txt_graph_items", false, RRDTOOL_OUTPUT_STDERR);
2304			} elseif (isset($graph_data_array['export'])) {
2305				rrdtool_execute("graph $graph_opts$graph_defs$txt_graph_items", false, RRDTOOL_OUTPUT_NULL, $rrdtool_pipe);
2306
2307				return 0;
2308			} elseif (isset($graph_data_array['export_realtime'])) {
2309				$output_flag = RRDTOOL_OUTPUT_GRAPH_DATA;
2310				$output = rrdtool_execute("graph $graph_opts$graph_defs$txt_graph_items", false, $output_flag, $rrdtool_pipe);
2311
2312				if ($fp = fopen($graph_data_array['export_realtime'], 'w')) {
2313					fwrite($fp, $output, strlen($output));
2314					fclose($fp);
2315					chmod($graph_data_array['export_realtime'], 0644);
2316				}
2317
2318				return $output;
2319			} else {
2320				$graph_data_array = boost_prep_graph_array($graph_data_array);
2321
2322				if (!isset($graph_data_array['output_flag'])) {
2323					$output_flag = RRDTOOL_OUTPUT_GRAPH_DATA;
2324				} else {
2325					$output_flag = $graph_data_array['output_flag'];
2326				}
2327
2328				$output = rrdtool_execute("$graph $graph_opts$graph_defs$txt_graph_items", false, $output_flag, $rrdtool_pipe);
2329
2330				boost_graph_set_file($output, $local_graph_id, $rra_id);
2331
2332				return $output;
2333			}
2334		}
2335	} else {
2336		$output_flag = RRDTOOL_OUTPUT_STDOUT;
2337
2338		$xport_array = rrdxport2array(rrdtool_execute("xport $graph_opts$graph_defs$txt_graph_items", false, $output_flag, $rrdtool_pipe));
2339
2340		/* add host and graph information */
2341		$xport_array['meta']['stacked_columns']= $stacked_columns;
2342		$xport_array['meta']['title_cache']    = $graph['title_cache'];
2343		$xport_array['meta']['vertical_label'] = $graph['vertical_label'];
2344		$xport_array['meta']['local_graph_id'] = $local_graph_id;
2345		$xport_array['meta']['host_id']        = $graph['host_id'];
2346
2347		return $xport_array;
2348	}
2349}
2350
2351function rrdtool_escape_string($text, $ignore_percent = true) {
2352	if ($ignore_percent) {
2353		return str_replace(array('"', ':'), array('\"', '\:'), $text);
2354	} else {
2355		return str_replace(array('"', ':', '%'), array('\"', '\:', '%%'), $text);
2356	}
2357}
2358
2359function rrdtool_function_xport($local_graph_id, $rra_id, $xport_data_array, &$xport_meta, $user = 0) {
2360	return rrdtool_function_graph($local_graph_id, $rra_id, $xport_data_array, null, $xport_meta, $user);
2361}
2362
2363function rrdtool_function_format_graph_date(&$graph_data_array) {
2364	global $datechar;
2365
2366	$graph_legend = '';
2367	/* setup date format */
2368	$date_fmt = read_user_setting('default_date_format',read_config_option('default_date_format'));
2369	$dateCharSetting = read_user_setting('default_datechar',read_config_option('default_datechar'));
2370	$datecharacter = $datechar[$dateCharSetting];
2371
2372	switch ($date_fmt) {
2373		case GD_MO_D_Y:
2374			$graph_date = 'm' . $datecharacter . 'd' . $datecharacter . 'Y H:i:s';
2375			break;
2376		case GD_MN_D_Y:
2377			$graph_date = 'M' . $datecharacter . 'd' . $datecharacter . 'Y H:i:s';
2378			break;
2379		case GD_D_MO_Y:
2380			$graph_date = 'd' . $datecharacter . 'm' . $datecharacter . 'Y H:i:s';
2381			break;
2382		case GD_D_MN_Y:
2383			$graph_date = 'd' . $datecharacter . 'M' . $datecharacter . 'Y H:i:s';
2384			break;
2385		case GD_Y_MO_D:
2386			$graph_date = 'Y' . $datecharacter . 'm' . $datecharacter . 'd H:i:s';
2387			break;
2388		case GD_Y_MN_D:
2389			$graph_date = 'Y' . $datecharacter . 'M' . $datecharacter . 'd H:i:s';
2390			break;
2391	}
2392
2393	/* display the timespan for zoomed graphs */
2394	if ((isset($graph_data_array['graph_start'])) && (isset($graph_data_array['graph_end']))) {
2395		if (($graph_data_array['graph_start'] < 0) && ($graph_data_array['graph_end'] < 0)) {
2396			$graph_legend = "COMMENT:\"From " . str_replace(':', '\:', date($graph_date, time()+$graph_data_array['graph_start'])) . ' To ' . str_replace(':', '\:', date($graph_date, time()+$graph_data_array['graph_end'])) . "\\c\"" . RRD_NL . "COMMENT:\"  \\n\"" . RRD_NL;
2397		} elseif (($graph_data_array['graph_start'] >= 0) && ($graph_data_array['graph_end'] >= 0)) {
2398			$graph_legend = "COMMENT:\"From " . str_replace(':', '\:', date($graph_date, $graph_data_array['graph_start'])) . ' To ' . str_replace(':', '\:', date($graph_date, $graph_data_array['graph_end'])) . "\\c\"" . RRD_NL . "COMMENT:\"  \\n\"" . RRD_NL;
2399		}
2400	}
2401
2402	return $graph_legend;
2403}
2404
2405function rrdtool_function_theme_font_options(&$graph_data_array) {
2406	global $config;
2407
2408	/* implement theme colors */
2409	$graph_opts = '';
2410	$themefonts = array();
2411
2412	if (isset($graph_data_array['graph_theme'])) {
2413		$rrdtheme = $config['base_path'] . '/include/themes/' . $graph_data_array['graph_theme'] . '/rrdtheme.php';
2414	} else {
2415		$rrdtheme = $config['base_path'] . '/include/themes/' . get_selected_theme() . '/rrdtheme.php';
2416	}
2417
2418	if (file_exists($rrdtheme) && is_readable($rrdtheme)) {
2419		$rrdversion = get_rrdtool_version();
2420		include($rrdtheme);
2421
2422		if (isset($rrdcolors)) {
2423			foreach($rrdcolors as $colortag => $color) {
2424				$graph_opts .= '--color ' . strtoupper($colortag) . '#' . strtoupper($color) . RRD_NL;
2425			}
2426		}
2427
2428		if (isset($rrdborder) && cacti_version_compare($rrdversion,'1.4','>=')) {
2429			$graph_opts .= "--border $rrdborder " ;
2430		}
2431
2432		if (isset($rrdfonts)) {
2433			$themefonts = $rrdfonts;
2434		}
2435	}
2436
2437	/* title fonts */
2438	$graph_opts .= rrdtool_function_set_font('title', ((!empty($graph_data_array['graph_nolegend'])) ? $graph_data_array['graph_nolegend'] : ''), $themefonts);
2439
2440	/* axis fonts */
2441	$graph_opts .= rrdtool_function_set_font('axis', '', $themefonts);
2442
2443	/* legend fonts */
2444	$graph_opts .= rrdtool_function_set_font('legend', '', $themefonts);
2445
2446	/* unit fonts */
2447	$graph_opts .= rrdtool_function_set_font('unit', '', $themefonts);
2448
2449	/* watermark fonts */
2450	if (isset($rrdversion) && cacti_version_compare($rrdversion,'1.3','>')) {
2451		$graph_opts .= rrdtool_function_set_font('watermark', '', $themefonts);
2452	}
2453
2454	return $graph_opts;
2455}
2456
2457function rrdtool_set_font($type, $no_legend = '', $themefonts = array()) {
2458	return rrdtool_function_set_font($type, $no_legend, $themefonts);
2459}
2460
2461function rrdtool_function_set_font($type, $no_legend, $themefonts) {
2462	global $config;
2463
2464	if (read_config_option('font_method') == 0) {
2465		if (read_user_setting('custom_fonts') == 'on') {
2466			$font = read_user_setting($type . '_font');
2467			$size = read_user_setting($type . '_size');
2468		} else {
2469			$font = read_config_option($type . '_font');
2470			$size = read_config_option($type . '_size');
2471		}
2472	} elseif (isset($themefonts[$type]['font']) && isset($themefonts[$type]['size'])) {
2473		$font = $themefonts[$type]['font'];
2474		$size = $themefonts[$type]['size'];
2475	} else {
2476		return;
2477	}
2478
2479	if ($font != '') {
2480		/* verifying all possible pango font params is too complex to be tested here
2481		 * so we only escape the font
2482		 */
2483		$font = cacti_escapeshellarg($font);
2484	}
2485
2486	if ($type == 'title') {
2487		if (!empty($no_legend)) {
2488			$size = $size * .70;
2489		} elseif (($size <= 4) || !is_numeric($size)) {
2490			$size = 12;
2491		}
2492	} elseif (($size <= 4) || !is_numeric($size)) {
2493		$size = 8;
2494	}
2495
2496	return '--font ' . strtoupper($type) . ':' . floatval($size) . ':' . $font . RRD_NL;
2497}
2498
2499function rrd_substitute_host_query_data($txt_graph_item, $graph, $graph_item) {
2500	/* replace host variables in graph elements */
2501	$host_id = 0;
2502
2503	if (empty($graph['host_id'])) {
2504		/* if graph has no associated host determine host_id from graph item data source */
2505		if (isset($graph_item['local_data_id']) && !empty($graph_item['local_data_id'])) {
2506			$host_id = db_fetch_cell_prepared('SELECT host_id
2507				FROM data_local
2508				WHERE id = ?',
2509				array($graph_item['local_data_id']));
2510		}
2511	} else {
2512		$host_id = $graph['host_id'];
2513	}
2514
2515	$txt_graph_item = substitute_host_data($txt_graph_item, '|', '|', $host_id);
2516
2517	/* replace query variables in graph elements */
2518	if (strpos($txt_graph_item, '|query_') !== false){
2519		if(isset($graph_item['snmp_query_id'])) {
2520			$txt_graph_item = substitute_snmp_query_data($txt_graph_item, $host_id, $graph_item['snmp_query_id'], $graph_item['snmp_index']);
2521		} else if (isset($graph['snmp_query_id'])) {
2522			$txt_graph_item = substitute_snmp_query_data($txt_graph_item, $host_id, $graph['snmp_query_id'], $graph['snmp_index']);
2523		}
2524	}
2525
2526	/* replace query variables in graph elements */
2527	if (strpos($txt_graph_item, '|input_') !== false && isset($graph_item['local_data_id'])) {
2528		return substitute_data_input_data($txt_graph_item, $graph, $graph_item['local_data_id']);
2529	} else {
2530		return $txt_graph_item;
2531	}
2532}
2533
2534function rrdtool_function_get_resstep($local_data_ids, $graph_start, $graph_end, $type = 'res') {
2535	if (!is_array($local_data_ids)) {
2536		$local_data_ids = array($local_data_ids);
2537	}
2538
2539	$time = time();
2540
2541	if ($graph_start < 0) {
2542		$graph_start = $time + $graph_start;
2543	}
2544
2545	if ($graph_end < 0) {
2546		$graph_end = $time + $graph_end;
2547	}
2548
2549	if (cacti_sizeof($local_data_ids)) {
2550		foreach($local_data_ids as $local_data_id) {
2551			$data_source_info = db_fetch_assoc_prepared('SELECT dsp.step, dspr.steps, dspr.rows, dspr.timespan
2552				FROM data_source_profiles AS dsp
2553				INNER JOIN data_source_profiles_rra AS dspr
2554				ON dspr.data_source_profile_id=dsp.id
2555				INNER JOIN data_template_data AS dtd
2556				ON dtd.data_source_profile_id=dsp.id
2557				WHERE dtd.local_data_id = ?
2558				ORDER BY step, steps ASC',
2559				array($local_data_id));
2560
2561			if (cacti_sizeof($data_source_info)) {
2562				foreach($data_source_info as $resolution) {
2563					if ($graph_start > ($time - ($resolution['step'] * $resolution['steps'] * $resolution['rows']))) {
2564						if ($type == 'res') {
2565							return $resolution['step'] * $resolution['steps'];
2566						} else {
2567							return $resolution['step'];
2568						}
2569					}
2570				}
2571			}
2572		}
2573	}
2574
2575	return 0;
2576}
2577
2578/** given a data source id, return rrdtool info array
2579 * @param $local_data_id - data source id
2580 * @return - (array) an array containing all data from rrdtool info command
2581 */
2582function rrdtool_function_info($local_data_id) {
2583	/* Get the path to rrdtool file */
2584	$data_source_path = get_data_source_path($local_data_id, true);
2585
2586	/* Execute rrdtool info command */
2587	$cmd_line = ' info ' . $data_source_path;
2588	$output = rrdtool_execute($cmd_line, RRDTOOL_OUTPUT_NULL, RRDTOOL_OUTPUT_STDOUT);
2589	if ($output == '') {
2590		return false;
2591	}
2592
2593	/* Hack for i18n */
2594	if (strpos($output, ',') !== false) {
2595		$output = str_replace(',', '.', $output);
2596	}
2597
2598	/* Parse the output */
2599	$matches  = array();
2600	$rrd_info = array('rra' => array(), 'ds' => array());
2601	$output   = explode("\n", $output);
2602
2603	foreach ($output as $line) {
2604		$line = trim($line);
2605		if (preg_match('/^ds\[(\S+)\]\.(\S+) = (\S+)$/', $line, $matches)) {
2606			$rrd_info['ds'][$matches[1]][$matches[2]] = trim($matches[3], '"');
2607		} elseif (preg_match('/^rra\[(\S+)\]\.(\S+)\[(\S+)\]\.(\S+) = (\S+)$/', $line, $matches)) {
2608			$rrd_info['rra'][$matches[1]][$matches[2]][$matches[3]][$matches[4]] = trim($matches[5], '"');
2609		} elseif (preg_match('/^rra\[(\S+)\]\.(\S+) = (\S+)$/', $line, $matches)) {
2610			$rrd_info['rra'][$matches[1]][$matches[2]] = trim($matches[3], '"');
2611		} elseif (preg_match("/^(\S+) = \"(\S+)\"$/", $line, $matches)) {
2612			$rrd_info[$matches[1]] = trim($matches[2], '"');
2613		} elseif (preg_match('/^(\S+) = (\S+)$/', $line, $matches)) {
2614			$rrd_info[$matches[1]] = trim($matches[2], '"');
2615		}
2616	}
2617
2618	$output = '';
2619	$matches = array();
2620
2621	/* Return parsed values */
2622	return $rrd_info;
2623}
2624
2625/** rrdtool_function_contains_cf  verifies if the RRDfile contains the 'MAX' consolidation function
2626 * @param $local_data_id    the id of the data source
2627 * @param $cf               the consolidation function to search for
2628 * @return					boolean true or false depending on the result
2629 */
2630function rrdtool_function_contains_cf($local_data_id, $cf) {
2631	$info = rrdtool_function_info($local_data_id);
2632
2633	if (cacti_sizeof($info)) {
2634		if (isset($info['rra'])) {
2635			foreach($info['rra'] as $ds) {
2636				if ($ds['cf'] == $cf) {
2637					return true;
2638				}
2639			}
2640		}
2641	}
2642
2643	return false;
2644}
2645
2646/** rrdtool_cacti_compare 	compares cacti information to rrd file information
2647 * @param $data_source_id		the id of the data source
2648 * @param $info				rrdtool info as an array
2649 * @return					array build like $info defining html class in case of error
2650 */
2651function rrdtool_cacti_compare($data_source_id, &$info) {
2652	global $data_source_types, $consolidation_functions;
2653
2654	/* get cacti header information for given data source id */
2655	$cacti_header_array = db_fetch_row_prepared('SELECT
2656		local_data_template_data_id, rrd_step, data_source_profile_id
2657		FROM data_template_data
2658		WHERE local_data_id = ?',
2659		array($data_source_id));
2660
2661	/* get cacti DS information */
2662	$cacti_ds_array = db_fetch_assoc_prepared('SELECT data_source_name, data_source_type_id,
2663		rrd_heartbeat, rrd_maximum, rrd_minimum
2664		FROM data_template_rrd
2665		WHERE local_data_id = ?',
2666		array($data_source_id));
2667
2668	/* get cacti RRA information */
2669	$cacti_rra_array = db_fetch_assoc_prepared('SELECT
2670		dspc.consolidation_function_id AS cf,
2671		dsp.x_files_factor AS xff,
2672		dspr.steps AS steps,
2673		dspr.rows AS `rows`
2674		FROM data_source_profiles AS dsp
2675		INNER JOIN data_source_profiles_cf AS dspc
2676		ON dsp.id=dspc.data_source_profile_id
2677		INNER JOIN data_source_profiles_rra AS dspr
2678		ON dsp.id=dspr.data_source_profile_id
2679		WHERE dsp.id = ?
2680		ORDER BY dspc.consolidation_function_id, dspr.steps',
2681		array($cacti_header_array['data_source_profile_id']));
2682
2683	$diff = array();
2684	/* -----------------------------------------------------------------------------------
2685	 * header information
2686	 -----------------------------------------------------------------------------------*/
2687	if ($cacti_header_array['rrd_step'] != $info['step']) {
2688		$diff['step'] = __("Required RRD step size is '%s'", $cacti_header_array['rrd_step']);
2689	}
2690
2691	/* -----------------------------------------------------------------------------------
2692	 * data source information
2693	 -----------------------------------------------------------------------------------*/
2694	if (cacti_sizeof($cacti_ds_array) > 0) {
2695		$data_local = db_fetch_row_prepared('SELECT host_id,
2696			snmp_query_id, snmp_index
2697			FROM data_local
2698			WHERE id = ?',
2699			array($data_source_id)
2700		);
2701
2702		$speed = rrdtool_function_interface_speed($data_local);
2703
2704		foreach ($cacti_ds_array as $key => $data_source) {
2705			$ds_name = $data_source['data_source_name'];
2706
2707			/* try to print matching rrd file's ds information */
2708			if (isset($info['ds'][$ds_name]) ) {
2709				if (!isset($info['ds'][$ds_name]['seen'])) {
2710					$info['ds'][$ds_name]['seen'] = true;
2711				} else {
2712					continue;
2713				}
2714
2715				$ds_type = trim($info['ds'][$ds_name]['type'], '"');
2716				if ($data_source_types[$data_source['data_source_type_id']] != $ds_type) {
2717					$diff['ds'][$ds_name]['type'] = __("Type for Data Source '%s' should be '%s'", $ds_name, $data_source_types[$data_source['data_source_type_id']]);
2718					$diff['tune'][] = $info['filename'] . ' ' . '--data-source-type ' . $ds_name . ':' . $data_source_types[$data_source['data_source_type_id']];
2719				}
2720
2721				if ($data_source['rrd_heartbeat'] != $info['ds'][$ds_name]['minimal_heartbeat']) {
2722					$diff['ds'][$ds_name]['minimal_heartbeat'] = __("Heartbeat for Data Source '%s' should be '%s'", $ds_name, $data_source['rrd_heartbeat']);
2723					$diff['tune'][] = $info['filename'] . ' ' . '--heartbeat ' . $ds_name . ':' . $data_source['rrd_heartbeat'];
2724				}
2725
2726				if ($data_source['rrd_minimum'] != $info['ds'][$ds_name]['min']) {
2727					if (($data_source['rrd_minimum'] == '0' || $data_source['rrd_maximum'] == 'U') &&
2728						$info['ds'][$ds_name]['min'] == 'NaN') {
2729						$info['ds'][$ds_name]['min'] = 'U';
2730					}
2731				}
2732
2733				if ($data_source['rrd_minimum'] != $info['ds'][$ds_name]['min']) {
2734					$diff['ds'][$ds_name]['min'] = __("RRD minimum for Data Source '%s' should be '%s'", $ds_name, $data_source['rrd_minimum']);
2735					$diff['tune'][] = $info['filename'] . ' ' . '--minimum ' . $ds_name . ':' . $data_source['rrd_minimum'];
2736				}
2737
2738				// Trim the max value
2739				$data_source['rrd_maximum'] = trim($data_source['rrd_maximum']);
2740
2741				if ($data_source['rrd_maximum'] != $info['ds'][$ds_name]['max']) {
2742					if ($data_source['rrd_maximum'] == '|query_ifSpeed|' ||
2743						$data_source['rrd_maximum'] == '|query_ifHighSpeed|') {
2744						$data_source['rrd_maximum'] = $speed;
2745					} elseif (($data_source['rrd_maximum'] == '0' || $data_source['rrd_maximum'] == 'U') &&
2746						$info['ds'][$ds_name]['max'] == 'NaN') {
2747						$info['ds'][$ds_name]['max'] = 'U';
2748					} else {
2749						$data_source['rrd_maximum'] = substitute_snmp_query_data($data_source['rrd_maximum'], $data_local['host_id'], $data_local['snmp_query_id'], $data_local['snmp_index']);
2750					}
2751				}
2752
2753				if ($data_source['rrd_maximum'] != $info['ds'][$ds_name]['max']) {
2754					$diff['ds'][$ds_name]['max'] = __("RRD maximum for Data Source '%s' should be '%s'", $ds_name, $data_source['rrd_maximum']);
2755					$diff['tune'][] = $info['filename'] . ' ' . '--maximum ' . $ds_name . ':' . $data_source['rrd_maximum'];
2756				}
2757			} else {
2758				# cacti knows this ds, but the rrd file does not
2759				$info['ds'][$ds_name]['type'] = $data_source_types[$data_source['data_source_type_id']];
2760				$info['ds'][$ds_name]['minimal_heartbeat'] = $data_source['rrd_heartbeat'];
2761				$info['ds'][$ds_name]['min'] = $data_source['rrd_minimum'];
2762				$info['ds'][$ds_name]['max'] = $data_source['rrd_maximum'];
2763				$info['ds'][$ds_name]['seen'] = true;
2764				$diff['ds'][$ds_name]['error'] = __("DS '%s' missing in RRDfile", $ds_name);
2765			}
2766		}
2767	}
2768
2769	/* print all data sources still known to the rrd file (no match to cacti ds will happen here) */
2770	if (cacti_sizeof($info['ds']) > 0) {
2771		foreach ($info['ds'] as $ds_name => $data_source) {
2772			if (!isset($data_source['seen'])) {
2773				$diff['ds'][$ds_name]['error'] = __("DS '%s' missing in Cacti definition", $ds_name);
2774			}
2775		}
2776	}
2777
2778	/* -----------------------------------------------------------------------------------
2779	 * RRA information
2780	 -----------------------------------------------------------------------------------*/
2781	$resize = true;		# assume a resize operation as long as no rra duplicates are found
2782
2783	/* scan cacti rra information for duplicates of (CF, STEPS) */
2784	if (cacti_sizeof($cacti_rra_array) > 0) {
2785		for ($i=0; $i<= cacti_sizeof($cacti_rra_array)-1; $i++) {
2786			$cf = $cacti_rra_array[$i]['cf'];
2787			$steps = $cacti_rra_array[$i]['steps'];
2788			foreach($cacti_rra_array as $cacti_rra_id => $cacti_rra) {
2789				if ($cf == $cacti_rra['cf'] && $steps == $cacti_rra['steps'] && ($i != $cacti_rra_id)) {
2790					$diff['rra'][$i]['error'] = __("Cacti RRA '%s' has same CF/steps (%s, %s) as '%s'", $i, $consolidation_functions[$cf], $steps, $cacti_rra_id);
2791					$diff['rra'][$cacti_rra_id]['error'] = __("Cacti RRA '%s' has same CF/steps (%s, %s) as '%s'", $cacti_rra_id, $consolidation_functions[$cf], $steps, $i);
2792					$resize = false;
2793				}
2794			}
2795		}
2796	}
2797
2798	/* scan file rra information for duplicates of (CF, PDP_PER_ROWS) */
2799	if (cacti_sizeof($info['rra']) > 0) {
2800		for ($i=0; $i<= cacti_sizeof($info['rra'])-1; $i++) {
2801			$cf = $info['rra'][$i]['cf'];
2802			$steps = $info['rra'][$i]['pdp_per_row'];
2803			foreach($info['rra'] as $file_rra_id => $file_rra) {
2804				if (($cf == $file_rra['cf']) && ($steps == $file_rra['pdp_per_row']) && ($i != $file_rra_id)) {
2805					$diff['rra'][$i]['error'] = __("File RRA '%s' has same CF/steps (%s, %s) as '%s'", $i, $cf, $steps, $file_rra_id);
2806					$diff['rra'][$file_rra_id]['error'] = __("File RRA '%s' has same CF/steps (%s, %s) as '%s'", $file_rra_id, $cf, $steps, $i);
2807					$resize = false;
2808				}
2809			}
2810		}
2811	}
2812
2813	/* print all RRAs known to cacti and add those from matching rrd file */
2814	if (cacti_sizeof($cacti_rra_array) > 0) {
2815		foreach($cacti_rra_array as $cacti_rra_id => $cacti_rra) {
2816			/* find matching rra info from rrd file
2817			 * do NOT assume, that rra sequence is kept ($cacti_rra_id != $file_rra_id may happen)!
2818			 * Match is assumed, if CF and STEPS/PDP_PER_ROW match; so go for it */
2819			foreach ($info['rra'] as $file_rra_id => $file_rra) {
2820				/* in case of mismatch, $file_rra['pdp_per_row'] might not be defined */
2821				if (!isset($file_rra['pdp_per_row'])) {
2822					$file_rra['pdp_per_row'] = 0;
2823				}
2824
2825				if ($consolidation_functions[$cacti_rra['cf']] == trim($file_rra['cf'], '"') &&
2826					$cacti_rra['steps'] == $file_rra['pdp_per_row']) {
2827
2828					if (isset($info['rra'][$file_rra_id]['seen'])) {
2829						continue;
2830					}
2831
2832					# mark both rra id's as seen to avoid printing them as non-matching
2833					$info['rra'][$file_rra_id]['seen'] = true;
2834					$cacti_rra_array[$cacti_rra_id]['seen'] = true;
2835
2836					if ($cacti_rra['xff'] != $file_rra['xff']) {
2837						$diff['rra'][$file_rra_id]['xff'] = __("XFF for cacti RRA id '%s' should be '%s'", $cacti_rra_id, $cacti_rra['xff']);
2838					}
2839
2840					if ($cacti_rra['rows'] != $file_rra['rows'] && $resize) {
2841						$diff['rra'][$file_rra_id]['rows'] = __("Number of rows for Cacti RRA id '%s' should be '%s'", $cacti_rra_id, $cacti_rra['rows']);
2842						if ($cacti_rra['rows'] > $file_rra['rows']) {
2843							$diff['resize'][] = $info['filename'] . ' ' . $cacti_rra_id . ' GROW ' . ($cacti_rra['rows'] - $file_rra['rows']);
2844						} else {
2845							$diff['resize'][] = $info['filename'] . ' ' . $cacti_rra_id . ' SHRINK ' . ($file_rra['rows'] - $cacti_rra['rows']);
2846						}
2847					}
2848				}
2849			}
2850			# if cacti knows an rra that has no match, consider this as an error
2851			if (!isset($cacti_rra_array[$cacti_rra_id]['seen'])) {
2852				# add to info array for printing, the index $cacti_rra_id has no real meaning
2853				$info['rra']['cacti_' . $cacti_rra_id]['cf']    = $consolidation_functions[$cacti_rra['cf']];
2854				$info['rra']['cacti_' . $cacti_rra_id]['steps'] = $cacti_rra['steps'];
2855				$info['rra']['cacti_' . $cacti_rra_id]['xff']   = $cacti_rra['xff'];
2856				$info['rra']['cacti_' . $cacti_rra_id]['rows']  = $cacti_rra['rows'];
2857				$diff['rra']['cacti_' . $cacti_rra_id]['error'] = __("RRA '%s' missing in RRDfile", $cacti_rra_id);
2858			}
2859		}
2860	}
2861
2862	# if the rrd file has an rra that has no cacti match, consider this as an error
2863	if (cacti_sizeof($info['rra']) > 0) {
2864		foreach ($info['rra'] as $file_rra_id => $file_rra) {
2865			if (!isset($info['rra'][$file_rra_id]['seen'])) {
2866				$diff['rra'][$file_rra_id]['error'] = __("RRA '%s' missing in Cacti definition", $file_rra_id);
2867			}
2868		}
2869	}
2870
2871	return $diff;
2872
2873}
2874
2875/** take output from rrdtool info array and build html table
2876 * @param array $info_array - array of rrdtool info data
2877 * @param array $diff - array of differences between definition and current rrd file settings
2878 * @return string - html code
2879 */
2880function rrdtool_info2html($info_array, $diff=array()) {
2881	global $config;
2882
2883	include_once($config['library_path'] . '/time.php');
2884
2885	html_start_box(__('RRD File Information'), '100%', '', '3', 'center', '');
2886
2887	# header data
2888	$header_items = array(
2889		array('display' =>__('Header'), 'align' => 'left'),
2890		array('display' => '', 'align' => 'left')
2891	);
2892
2893	html_header($header_items, 1);
2894
2895	# add human readable timestamp
2896	if (isset($info_array['last_update'])) {
2897		$info_array['last_update'] .= ' [' . date(CACTI_DATE_TIME_FORMAT, $info_array['last_update']) . ']';
2898	}
2899
2900	$loop = array(
2901		'filename'    => $info_array['filename'],
2902		'rrd_version' => $info_array['rrd_version'],
2903		'step'        => $info_array['step'],
2904		'last_update' => $info_array['last_update']);
2905
2906	foreach ($loop as $key => $value) {
2907		form_alternate_row($key, true);
2908		form_selectable_cell($key, 'key');
2909		form_selectable_cell($value, 'value', '', ((isset($diff[$key]) ? 'color:red' : '')));
2910		form_end_row();
2911	}
2912
2913	html_end_box();
2914
2915	# data sources
2916	$header_items = array(
2917		array('display' => __('Data Source Items'), 'align' => 'left'),
2918		array('display' => __('Type'),              'align' => 'left'),
2919		array('display' => __('Minimal Heartbeat'), 'align' => 'right'),
2920		array('display' => __('Min'),               'align' => 'right'),
2921		array('display' => __('Max'),               'align' => 'right'),
2922		array('display' => __('Last DS'),           'align' => 'right'),
2923		array('display' => __('Value'),             'align' => 'right'),
2924		array('display' => __('Unknown Sec'),       'align' => 'right')
2925	);
2926
2927	html_start_box('', '100%', '', '3', 'center', '');
2928
2929	html_header($header_items, 1);
2930
2931	if (cacti_sizeof($info_array['ds'])) {
2932		foreach ($info_array['ds'] as $key => $value) {
2933			form_alternate_row('line' . $key, true);
2934
2935			form_selectable_cell($key, 'name', '', (isset($diff['ds'][$key]['error']) ? 'color:red' : ''));
2936			form_selectable_cell((isset($value['type']) ? $value['type'] : ''), 'type', '', (isset($diff['ds'][$key]['type']) ? 'color:red' : ''));
2937			form_selectable_cell((isset($value['minimal_heartbeat']) ? $value['minimal_heartbeat'] : ''), 'minimal_heartbeat', '', (isset($diff['ds'][$key]['minimal_heartbeat']) ? 'color:red, text-align:right' : 'text-align:right'));
2938
2939			if (isset($value['min'])) {
2940				if ($value['min'] == 'U') {
2941					form_selectable_cell($value['min'], 'min', '', 'right');
2942				} elseif (is_numeric($value['min'])) {
2943					form_selectable_cell(number_format_i18n($value['min']), 'min', '', 'right');
2944				} else {
2945					form_selectable_cell($value['min'], 'min', '', 'color:red;text-align:right');
2946				}
2947			} else {
2948				form_selectable_cell(__('Unknown'), 'min', '', 'color:red;text-align:right');
2949			}
2950
2951			if (isset($value['max'])) {
2952				if ($value['max'] == 'U') {
2953					form_selectable_cell($value['max'], 'max', '', 'right');
2954				} elseif (is_numeric($value['max'])) {
2955					form_selectable_cell(number_format_i18n($value['max']), 'max', '', 'right');
2956				} else {
2957					form_selectable_cell($value['max'], 'max', '', 'color:red;text-align:right');
2958				}
2959			} else {
2960				form_selectable_cell(__('Unknown'), 'max', '', 'color:red;text-align:right');
2961			}
2962
2963			form_selectable_cell((isset($value['last_ds']) && is_numeric($value['last_ds']) ? number_format_i18n($value['last_ds']) : (isset($value['last_ds']) ? $value['last_ds']:'')), 'last_ds', '', 'text-align:right');
2964			form_selectable_cell((isset($value['value']) ? is_numeric($value['value']) ? number_format_i18n($value['value']) : $value['value'] : ''), 'value', '', 'text-align:right');
2965			form_selectable_cell((isset($value['unknown_sec']) && is_numeric($value['unknown_sec']) ? number_format_i18n($value['unknown_sec']) : (isset($value['unknown_sec']) ? $value['unknown_sec']:'')), 'unknown_sec', '', 'text-align:right');
2966
2967			form_end_row();
2968		}
2969	}
2970
2971	html_end_box();
2972
2973	# round robin archive
2974	$header_items = array(
2975		array('display' => __('Round Robin Archive'),         'align' => 'left'),
2976		array('display' => __('Consolidation Function'),      'align' => 'left'),
2977		array('display' => __('Rows'),                        'align' => 'right'),
2978		array('display' => __('Cur Row'),                     'align' => 'right'),
2979		array('display' => __('PDP per Row'),                 'align' => 'right'),
2980		array('display' => __('X-Files Factor'),              'align' => 'right'),
2981		array('display' => __('CDP Prep Value (0)'),          'align' => 'right'),
2982		array('display' => __('CDP Unknown Data points (0)'), 'align' => 'right')
2983	);
2984
2985	html_start_box('', '100%', '', '3', 'center', '');
2986
2987	html_header($header_items, 1);
2988
2989	if (cacti_sizeof($info_array['rra'])) {
2990		foreach ($info_array['rra'] as $key => $value) {
2991			form_alternate_row('line_' . $key, true);
2992
2993			form_selectable_cell($key, 'name', '', (isset($diff['rra'][$key]['error']) ? 'color:red' : ''));
2994			form_selectable_cell((isset($value['cf']) ? $value['cf'] : ''), 'cf');
2995			form_selectable_cell((isset($value['rows']) ? $value['rows'] : ''), 'rows', '', (isset($diff['rra'][$key]['rows']) 	? 'color:red;text-align:right' : 'text-align:right'));
2996			form_selectable_cell((isset($value['cur_row']) ? $value['cur_row'] : ''), 'cur_row', '', 'text-align:right');
2997			form_selectable_cell((isset($value['pdp_per_row']) ? $value['pdp_per_row'] : ''), 'pdp_per_row', '', 'text-align:right');
2998			form_selectable_cell((isset($value['xff']) ? floatval($value['xff']) : ''), 'xff', '', (isset($diff['rra'][$key]['xff']) 	? 'color:red;text-align:right' : 'text-align:right'));
2999			form_selectable_cell((isset($value['cdp_prep'][0]['value']) ? (strtolower($value['cdp_prep'][0]['value']) == 'nan') ? $value['cdp_prep'][0]['value'] : floatval($value['cdp_prep'][0]['value']) : ''), 'value', '', 'text-align:right');
3000			form_selectable_cell((isset($value['cdp_prep'][0]['unknown_datapoints'])? $value['cdp_prep'][0]['unknown_datapoints'] : ''), 	'unknown_datapoints', '', 'text-align:right');
3001
3002			form_end_row();
3003		}
3004	}
3005
3006	html_end_box();
3007}
3008
3009/** rrdtool_tune			- create rrdtool tune/resize commands
3010 * 						  html+cli enabled
3011 * @param $rrd_file		- rrd file name
3012 * @param $diff			- array of discrepancies between cacti setttings and rrd file info
3013 * @param $show_source	- only show text+commands or execute all commands, execute is for cli mode only!
3014 */
3015function rrdtool_tune($rrd_file, $diff, $show_source = true) {
3016	function print_leaves($array, $nl) {
3017		foreach ($array as $key => $line) {
3018			if (!is_array($line)) {
3019				print $line . $nl;
3020			} else {
3021				if ($key === 'tune') continue;
3022				if ($key === 'resize') continue;
3023				print_leaves($line, $nl);
3024			}
3025		}
3026
3027	}
3028
3029
3030	$cmd = array();
3031	# for html/cli mode
3032	if (CACTI_CLI) {
3033		$nl = "\n";
3034	} else {
3035		$nl = '<br/>';
3036	}
3037
3038	if ($show_source && cacti_sizeof($diff)) {
3039		# print error descriptions
3040		print_leaves($diff, $nl);
3041	}
3042
3043	if (isset($diff['tune']) && cacti_sizeof($diff['tune'])) {
3044		# create tune commands
3045		foreach ($diff['tune'] as $line) {
3046			if ($show_source == true) {
3047				print read_config_option('path_rrdtool') . ' tune ' . $line . $nl;
3048			} else {
3049				rrdtool_execute("tune $line", true, RRDTOOL_OUTPUT_STDOUT);
3050			}
3051		}
3052	}
3053
3054	if (isset($diff['resize']) && cacti_sizeof($diff['resize'])) {
3055		# each resize goes into an extra line
3056		foreach ($diff['resize'] as $line) {
3057			if ($show_source == true) {
3058				print read_config_option('path_rrdtool') . ' resize ' . $line . $nl;
3059				print __('rename %s to %s', dirname($rrd_file) . '/resize.rrd', $rrd_file) . $nl;
3060			} else {
3061				rrdtool_execute("resize $line", true, RRDTOOL_OUTPUT_STDOUT);
3062				rename(dirname($rrd_file) . '/resize.rrd', $rrd_file);
3063			}
3064		}
3065	}
3066}
3067
3068/** Given a data source id, check the rrdtool file to the data source definition
3069 * @param $data_source_id - data source id
3070 * @return - (array) an array containing issues with the rrdtool file definition vs data source
3071 */
3072function rrd_check($data_source_id) {
3073	global $rrd_tune_array, $data_source_types;
3074
3075	$data_source_name = get_data_source_item_name($rrd_tune_array['data_source_id']);
3076	$data_source_type = $data_source_types[$rrd_tune_array['data-source-type']];
3077	$data_source_path = get_data_source_path($rrd_tune_array['data_source_id'], true);
3078}
3079
3080/** Given a data source id, update the rrdtool file to match the data source definition
3081 * @param $data_source_id - data source id
3082 * @return - 1 success, 2 false
3083 */
3084function rrd_repair($data_source_id) {
3085	global $rrd_tune_array, $data_source_types;
3086
3087	$data_source_name = get_data_source_item_name($rrd_tune_array['data_source_id']);
3088	$data_source_type = $data_source_types[$rrd_tune_array['data-source-type']];
3089	$data_source_path = get_data_source_path($rrd_tune_array['data_source_id'], true);
3090}
3091
3092/** add a (list of) datasource(s) to an (array of) rrd file(s)
3093 * @param array $file_array	- array of rrd files
3094 * @param array $ds_array	- array of datasouce parameters
3095 * @param bool $debug		- debug mode
3096 * @return mixed			- success (bool) or error message (array)
3097 */
3098function rrd_datasource_add($file_array, $ds_array, $debug) {
3099	global $data_source_types, $consolidation_functions;
3100
3101	$rrdtool_pipe = rrd_init();
3102
3103	/* iterate all given rrd files */
3104	foreach ($file_array as $file) {
3105		/* create a DOM object from an rrdtool dump */
3106		$dom = new domDocument;
3107		$dom->loadXML(rrdtool_execute("dump $file", false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'UTIL'));
3108		if (!$dom) {
3109			$check['err_msg'] = __('Error while parsing the XML of rrdtool dump');
3110			return $check;
3111		}
3112
3113		/* rrdtool dump depends on rrd file version:
3114		 * version 0001 => RRDtool 1.0.x
3115		 * version 0003 => RRDtool 1.2.x, 1.3.x, 1.4.x, 1.5.x, 1.6.x
3116		 */
3117		$version = trim($dom->getElementsByTagName('version')->item(0)->nodeValue);
3118
3119		/* now start XML processing */
3120		foreach ($ds_array as $ds) {
3121			/* first, append the <DS> strcuture in the rrd header */
3122			if ($ds['type'] === $data_source_types[DATA_SOURCE_TYPE_COMPUTE]) {
3123				rrd_append_compute_ds($dom, $version, $ds['name'], $ds['type'], $ds['cdef']);
3124			} else {
3125				rrd_append_ds($dom, $version, $ds['name'], $ds['type'], $ds['heartbeat'], $ds['min'], $ds['max']);
3126			}
3127			/* now work on the <DS> structure as part of the <cdp_prep> tree */
3128			rrd_append_cdp_prep_ds($dom, $version);
3129			/* add <V>alues to the <database> tree */
3130			rrd_append_value($dom);
3131		}
3132
3133		if ($debug) {
3134			echo $dom->saveXML();
3135		} else {
3136			/* for rrdtool restore, we need a file, so write the XML to disk */
3137			$xml_file = $file . '.xml';
3138			$rc = $dom->save($xml_file);
3139			/* verify, if write was successful */
3140			if ($rc === false) {
3141				$check['err_msg'] = __('ERROR while writing XML file: %s', $xml_file);
3142				return $check;
3143			} else {
3144				/* are we allowed to write the rrd file? */
3145				if (is_writable($file)) {
3146					/* restore the modified XML to rrd */
3147					rrdtool_execute("restore -f $xml_file $file", false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'UTIL');
3148					/* scratch that XML file to avoid filling up the disk */
3149					unlink($xml_file);
3150					cacti_log('Added Data Source(s) to RRDfile: ' . $file, false, 'UTIL');
3151				} else {
3152					$check['err_msg'] = __('ERROR: RRDfile %s not writeable', $file);
3153					return $check;
3154				}
3155			}
3156		}
3157	}
3158
3159	rrd_close($rrdtool_pipe);
3160
3161	return true;
3162}
3163
3164/** delete a (list of) rra(s) from an (array of) rrd file(s)
3165 * @param array $file_array	- array of rrd files
3166 * @param array $rra_array	- array of rra parameters
3167 * @param bool $debug		- debug mode
3168 * @return mixed			- success (bool) or error message (array)
3169 */
3170function rrd_rra_delete($file_array, $rra_array, $debug) {
3171	$rrdtool_pipe = rrd_init();
3172
3173	/* iterate all given rrd files */
3174	foreach ($file_array as $file) {
3175		/* create a DOM document from an rrdtool dump */
3176		$dom = new domDocument;
3177		$dom->loadXML(rrdtool_execute("dump $file", false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'UTIL'));
3178		if (!$dom) {
3179			$check['err_msg'] = __('Error while parsing the XML of RRDtool dump');
3180			return $check;
3181		}
3182
3183		/* now start XML processing */
3184		foreach ($rra_array as $rra) {
3185			rrd_delete_rra($dom, $rra, $debug);
3186		}
3187
3188		if ($debug) {
3189			echo $dom->saveXML();
3190		} else {
3191			/* for rrdtool restore, we need a file, so write the XML to disk */
3192			$xml_file = $file . '.xml';
3193			$rc = $dom->save($xml_file);
3194			/* verify, if write was successful */
3195			if ($rc === false) {
3196				$check['err_msg'] = __('ERROR while writing XML file: %s', $xml_file);
3197				return $check;
3198			} else {
3199				/* are we allowed to write the rrd file? */
3200				if (is_writable($file)) {
3201					/* restore the modified XML to rrd */
3202					rrdtool_execute("restore -f $xml_file $file", false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'UTIL');
3203					/* scratch that XML file to avoid filling up the disk */
3204					unlink($xml_file);
3205					cacti_log('Deleted RRA(s) from RRDfile: ' . $file, false, 'UTIL');
3206				} else {
3207					$check['err_msg'] = __('ERROR: RRDfile %s not writeable', $file);
3208					return $check;
3209				}
3210			}
3211		}
3212	}
3213
3214	rrd_close($rrdtool_pipe);
3215
3216	return true;
3217}
3218
3219/** clone a (list of) rra(s) from an (array of) rrd file(s)
3220 * @param array $file_array	- array of rrd files
3221 * @param string $cf		- new consolidation function
3222 * @param array $rra_array	- array of rra parameters
3223 * @param bool $debug		- debug mode
3224 * @return mixed			- success (bool) or error message (array)
3225 */
3226function rrd_rra_clone($file_array, $cf, $rra_array, $debug) {
3227	$rrdtool_pipe = rrd_init();
3228
3229	/* iterate all given rrd files */
3230	foreach ($file_array as $file) {
3231		/* create a DOM document from an rrdtool dump */
3232		$dom = new domDocument;
3233		$dom->loadXML(rrdtool_execute("dump $file", false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'UTIL'));
3234		if (!$dom) {
3235			$check['err_msg'] = __('Error while parsing the XML of RRDtool dump');
3236			return $check;
3237		}
3238
3239		/* now start XML processing */
3240		foreach ($rra_array as $rra) {
3241			rrd_copy_rra($dom, $cf, $rra, $debug);
3242		}
3243
3244		if ($debug) {
3245			echo $dom->saveXML();
3246		} else {
3247			/* for rrdtool restore, we need a file, so write the XML to disk */
3248			$xml_file = $file . '.xml';
3249			$rc = $dom->save($xml_file);
3250			/* verify, if write was successful */
3251			if ($rc === false) {
3252				$check['err_msg'] = __('ERROR while writing XML file: %s', $xml_file);
3253				return $check;
3254			} else {
3255				/* are we allowed to write the rrd file? */
3256				if (is_writable($file)) {
3257					/* restore the modified XML to rrd */
3258					rrdtool_execute("restore -f $xml_file $file", false, RRDTOOL_OUTPUT_STDOUT, $rrdtool_pipe, 'UTIL');
3259					/* scratch that XML file to avoid filling up the disk */
3260					unlink($xml_file);
3261					cacti_log('Deleted RRA(s) from RRDfile: ' . $file, false, 'UTIL');
3262				} else {
3263					$check['err_msg'] = __('ERROR: RRDfile %s not writeable', $file);
3264					return $check;
3265				}
3266			}
3267		}
3268	}
3269
3270	rrd_close($rrdtool_pipe);
3271
3272	return true;
3273}
3274
3275/** appends a <DS> subtree to an RRD XML structure
3276 * @param object $dom	- the DOM object, where the RRD XML is stored
3277 * @param string $version- rrd file version
3278 * @param string $name	- name of the new ds
3279 * @param string $type	- type of the new ds
3280 * @param int $min_hb	- heartbeat of the new ds
3281 * @param string $min	- min value of the new ds or [NaN|U]
3282 * @param string $max	- max value of the new ds or [NaN|U]
3283 * @return object		- modified DOM
3284 */
3285function rrd_append_ds($dom, $version, $name, $type, $min_hb, $min, $max) {
3286	/* rrdtool version dependencies */
3287	if ($version === RRD_FILE_VERSION1) {
3288		$last_ds = 'U';
3289	}
3290	elseif ($version === RRD_FILE_VERSION3) {
3291		$last_ds = 'UNKN';
3292	}
3293
3294	/* create <DS> subtree */
3295	$new_dom = new DOMDocument;
3296	/* pretty print */
3297	$new_dom->formatOutput = true;
3298	/* this defines the new node structure */
3299	$new_dom->loadXML("
3300			<ds>
3301				<name> $name </name>
3302				<type> $type </type>
3303				<minimal_heartbeat> $min_hb </minimal_heartbeat>
3304				<min> $min </min>
3305				<max> $max </max>
3306
3307				<!-- PDP Status -->
3308				<last_ds> $last_ds </last_ds>
3309				<value> 0.0000000000e+00 </value>
3310				<unknown_sec> 0 </unknown_sec>
3311			</ds>");
3312
3313	/* create a node element from new document */
3314	$new_node = $new_dom->getElementsByTagName('ds')->item(0);
3315	#echo $new_dom->saveXML();	# print new node
3316
3317	/* get XPATH notation required for positioning */
3318	#$xpath = new DOMXPath($dom);
3319	/* get XPATH for entry where new node will be inserted
3320	 * which is the <rra> entry */
3321	#$insert = $xpath->query('/rrd/rra')->item(0);
3322	$insert = $dom->getElementsByTagName('rra')->item(0);
3323
3324	/* import the new node */
3325	$new_node = $dom->importNode($new_node, true);
3326	/* and insert it at the correct place */
3327	$insert->parentNode->insertBefore($new_node, $insert);
3328}
3329
3330/** COMPUTE DS: appends a <DS> subtree to an RRD XML structure
3331 * @param object $dom	- the DOM object, where the RRD XML is stored
3332 * @param string $version- rrd file version
3333 * @param string $name	- name of the new ds
3334 * @param string $type	- type of the new ds
3335 * @param int $cdef		- the cdef rpn used for COMPUTE
3336 * @return object		- modified DOM
3337 */
3338function rrd_append_compute_ds($dom, $version, $name, $type, $cdef) {
3339	/* rrdtool version dependencies */
3340	if ($version === RRD_FILE_VERSION1) {
3341		$last_ds = 'U';
3342	}
3343	elseif ($version === RRD_FILE_VERSION3) {
3344		$last_ds = 'UNKN';
3345	}
3346
3347	/* create <DS> subtree */
3348	$new_dom = new DOMDocument;
3349	/* pretty print */
3350	$new_dom->formatOutput = true;
3351	/* this defines the new node structure */
3352	$new_dom->loadXML("
3353			<ds>
3354				<name> $name </name>
3355				<type> $type </type>
3356				<cdef> $cdef </cdef>
3357
3358				<!-- PDP Status -->
3359				<last_ds> $last_ds </last_ds>
3360				<value> 0.0000000000e+00 </value>
3361				<unknown_sec> 0 </unknown_sec>
3362			</ds>");
3363
3364	/* create a node element from new document */
3365	$new_node = $new_dom->getElementsByTagName('ds')->item(0);
3366
3367	/* get XPATH notation required for positioning */
3368	#$xpath = new DOMXPath($dom);
3369	/* get XPATH for entry where new node will be inserted
3370	 * which is the <rra> entry */
3371	#$insert = $xpath->query('/rrd/rra')->item(0);
3372	$insert = $dom->getElementsByTagName('rra')->item(0);
3373
3374	/* import the new node */
3375	$new_node = $dom->importNode($new_node, true);
3376	/* and insert it at the correct place */
3377	$insert->parentNode->insertBefore($new_node, $insert);
3378}
3379
3380/** append a <DS> subtree to the <CDP_PREP> subtrees of a RRD XML structure
3381 * @param object $dom		- the DOM object, where the RRD XML is stored
3382 * @param string $version	- rrd file version
3383 * @return object			- the modified DOM object
3384 */
3385function rrd_append_cdp_prep_ds($dom, $version) {
3386	/* get all <cdp_prep><ds> entries */
3387	#$cdp_prep_list = $xpath->query('/rrd/rra/cdp_prep');
3388	$cdp_prep_list = $dom->getElementsByTagName('rra')->item(0)->getElementsByTagName('cdp_prep');
3389
3390	/* get XPATH notation required for positioning */
3391	#$xpath = new DOMXPath($dom);
3392
3393	/* get XPATH for source <ds> entry */
3394	#$src_ds = $xpath->query('/rrd/rra/cdp_prep/ds')->item(0);
3395	$src_ds = $dom->getElementsByTagName('rra')->item(0)->getElementsByTagName('cdp_prep')->item(0)->getElementsByTagName('ds')->item(0);
3396	/* clone the source ds entry to preserve RRDtool notation */
3397	$new_ds = $src_ds->cloneNode(true);
3398
3399	/* rrdtool version dependencies */
3400	if ($version === RRD_FILE_VERSION3) {
3401		$new_ds->getElementsByTagName('primary_value')->item(0)->nodeValue = ' NaN ';
3402		$new_ds->getElementsByTagName('secondary_value')->item(0)->nodeValue = ' NaN ';
3403	}
3404
3405	/* the new node always has default entries */
3406	$new_ds->getElementsByTagName('value')->item(0)->nodeValue = ' NaN ';
3407	$new_ds->getElementsByTagName('unknown_datapoints')->item(0)->nodeValue = ' 0 ';
3408
3409
3410	/* iterate all entries found, equals 'number of <rra>' times 'number of <ds>' */
3411	if ($cdp_prep_list->length) {
3412		foreach ($cdp_prep_list as $cdp_prep) {
3413			/* $cdp_prep now points to the next <cdp_prep> XML Element
3414			 * and append new ds entry at end of <cdp_prep> child list */
3415			$cdp_prep->appendChild($new_ds);
3416		}
3417	}
3418}
3419
3420/** append a <V>alue element to the <DATABASE> subtrees of a RRD XML structure
3421 * @param object $dom	- the DOM object, where the RRD XML is stored
3422 * @return object		- the modified DOM object
3423 */
3424function rrd_append_value($dom) {
3425	/* get XPATH notation required for positioning */
3426	#$xpath = new DOMXPath($dom);
3427
3428	/* get all <cdp_prep><ds> entries */
3429	#$itemList = $xpath->query('/rrd/rra/database/row');
3430	$itemList = $dom->getElementsByTagName('row');
3431
3432	/* create <V> entry to preserve RRDtool notation */
3433	$new_v = $dom->createElement('v', ' NaN ');
3434
3435	/* iterate all entries found, equals 'number of <rra>' times 'number of <ds>' */
3436	if ($itemList->length) {
3437		foreach ($itemList as $item) {
3438			/* $item now points to the next <cdp_prep> XML Element
3439			 * and append new ds entry at end of <cdp_prep> child list */
3440			$item->appendChild($new_v);
3441		}
3442	}
3443}
3444
3445/** delete an <RRA> subtree from the <RRD> XML structure
3446 * @param object $dom		- the DOM document, where the RRD XML is stored
3447 * @param array $rra_parm	- a single rra parameter set, given by the user
3448 * @return object			- the modified DOM object
3449 */
3450function rrd_delete_rra($dom, $rra_parm) {
3451	/* find all RRA DOMNodes */
3452	$rras = $dom->getElementsByTagName('rra');
3453
3454	/* iterate all entries found */
3455	$nb = $rras->length;
3456	for ($pos = 0; $pos < $nb; $pos++) {
3457		/* retrieve all RRA DOMNodes one by one */
3458		$rra = $rras->item($pos);
3459		$cf = $rra->getElementsByTagName('cf')->item(0)->nodeValue;
3460		$pdp_per_row = $rra->getElementsByTagName('pdp_per_row')->item(0)->nodeValue;
3461		$xff = $rra->getElementsByTagName('xff')->item(0)->nodeValue;
3462		$rows = $rra->getElementsByTagName('row')->length;
3463
3464		if ($cf 			== $rra_parm['cf'] &&
3465			$pdp_per_row 	== $rra_parm['pdp_per_row'] &&
3466			$xff 			== $rra_parm['xff'] &&
3467			$rows 			== $rra_parm['rows']) {
3468			print(__("RRA (CF=%s, ROWS=%d, PDP_PER_ROW=%d, XFF=%1.2f) removed from RRD file", $cf, $rows, $pdp_per_row, $xff)) . PHP_EOL;
3469			/* we need the parentNode for removal operation */
3470			$parent = $rra->parentNode;
3471			$parent->removeChild($rra);
3472			break; /* do NOT accidentally remove more than one element, else loop back to forth */
3473		}
3474	}
3475	return $dom;
3476}
3477
3478/** clone an <RRA> subtree of the <RRD> XML structure, replacing cf
3479 * @param object $dom		- the DOM document, where the RRD XML is stored
3480 * @param string $cf		- new consolidation function
3481 * @param array $rra_parm	- a single rra parameter set, given by the user
3482 * @return object			- the modified DOM object
3483 */
3484function rrd_copy_rra($dom, $cf, $rra_parm) {
3485	/* find all RRA DOMNodes */
3486	$rras = $dom->getElementsByTagName('rra');
3487
3488	/* iterate all entries found */
3489	$nb = $rras->length;
3490	for ($pos = 0; $pos < $nb; $pos++) {
3491		/* retrieve all RRA DOMNodes one by one */
3492		$rra          = $rras->item($pos);
3493		$_cf          = $rra->getElementsByTagName('cf')->item(0)->nodeValue;
3494		$_pdp_per_row = $rra->getElementsByTagName('pdp_per_row')->item(0)->nodeValue;
3495		$_xff         = $rra->getElementsByTagName('xff')->item(0)->nodeValue;
3496		$_rows        = $rra->getElementsByTagName('row')->length;
3497
3498		if ($_cf 			== $rra_parm['cf'] &&
3499			$_pdp_per_row 	== $rra_parm['pdp_per_row'] &&
3500			$_xff 			== $rra_parm['xff'] &&
3501			$_rows 			== $rra_parm['rows']) {
3502			print(__("RRA (CF=%s, ROWS=%d, PDP_PER_ROW=%d, XFF=%1.2f) adding to RRD file", $cf, $_rows, $_pdp_per_row, $_xff)) . PHP_EOL;
3503			/* we need the parentNode for append operation */
3504			$parent = $rra->parentNode;
3505
3506			/* get a clone of the matching RRA */
3507			$new_rra = $rra->cloneNode(true);
3508			/* and find the 'old' cf */
3509			#$old_cf = $new_rra->getElementsByTagName('cf')->item(0);
3510			/* now replace old cf with new one */
3511			#$old_cf->childNodes->item(0)->replaceData(0,20,$cf);
3512			$new_rra->getElementsByTagName('cf')->item(0)->nodeValue = $cf;
3513
3514			/* append new rra entry at end of the list */
3515			$parent->appendChild($new_rra);
3516			break; /* do NOT accidentally clone more than one element, else loop back to forth */
3517		}
3518	}
3519
3520	return $dom;
3521}
3522
3523function rrdtool_parse_error($string) {
3524	global $config;
3525
3526	if (preg_match('/ERROR. opening \'(.*)\': (No such|Permiss).*/', $string, $matches)) {
3527		if (cacti_sizeof($matches) >= 2) {
3528			$filename = $matches[1];
3529			$rra_name = basename($filename);
3530			$rra_path = dirname($filename) . "/";
3531			if (!is_resource_writable($rra_path)) {
3532				$message = __('Website does not have write access to %s, may be unable to create/update RRDs', 'folder');
3533				$rra_name = str_replace($config['base_path'],'', $rra_path);
3534				$rra_path = "";
3535			} else {
3536				if (stripos($filename, $config['base_path']) >= 0) {
3537					$rra_file = str_replace($config['base_path'] . '/rra/', '', $filename);
3538					$rra_name = basename($rra_file);
3539					$rra_path = dirname($rra_file);
3540				} else {
3541					$rra_name = basename($filename);
3542					$rra_path = __('(Custom)');
3543				}
3544
3545				if (!is_resource_writable($filename)) {
3546					$message = __('Website does not have write access to %s, may be unable to create/update RRDs', 'data file');
3547				} else {
3548					$message = __('Failed to open data file, poller may not have run yet');
3549				}
3550
3551				$rra_path = '(' . __('RRA Folder') . ': ' . ((empty($rra_path) || $rra_path == ".") ? __('Root') : $rra_path) . ')';
3552			}
3553
3554			$string = $message . ":\n\0x27\n" . $rra_name;
3555			if (!empty($rra_path)) {
3556				$string .= "\n" . $rra_path;
3557			}
3558		}
3559	}
3560	return $string;
3561}
3562
3563function rrdtool_create_error_image($string, $width = '', $height = '') {
3564	global $config;
3565
3566	$string = rrdtool_parse_error($string);
3567
3568	/* put image in buffer */
3569	ob_start();
3570
3571	$image_data  = false;
3572	$font_color  = '000000';
3573	$font_size   = 8;
3574	$back_color  = 'F3F3F3';
3575	$shadea      = 'CBCBCB';
3576	$shadeb      = '999999';
3577
3578	if ($config['cacti_server_os'] == 'unix') {
3579		$font_file = '/usr/share/fonts/dejavu/DejaVuSans.ttf';
3580	} else {
3581		$font_file = 'C:/Windows/Fonts/Arial.ttf';
3582	}
3583
3584	$themefile  = $config['base_path'] . '/include/themes/' . get_selected_theme() . '/rrdtheme.php';
3585
3586	if (file_exists($themefile) && is_readable($themefile)) {
3587		include($themefile);
3588
3589		if (isset($rrdfonts['legend']['size'])) {
3590			$font_size   = $rrdfonts['legend']['size'];
3591		}
3592
3593		if (isset($rrdcolors['font'])) {
3594			$font_color  = $rrdcolors['font'];
3595		}
3596
3597		if (isset($rrdcolors['canvas'])) {
3598			$back_color  = $rrdcolors['canvas'];
3599		}
3600
3601		if (isset($rrdcolors['shadea'])) {
3602			$shadea = $rrdcolors['shadea'];
3603		}
3604
3605		if (isset($rrdcolors['shadeb'])) {
3606			$shadeb = $rrdcolors['shadeb'];
3607		}
3608	}
3609
3610	$image = imagecreatetruecolor(450, 200);
3611	imagesavealpha($image, true);
3612
3613	/* create a transparent color */
3614	$transparent = imagecolorallocatealpha($image, 0, 0, 0, 127);
3615	imagefill($image, 0, 0, $transparent);
3616
3617	/* background the entire image with the frame */
3618	list($red, $green, $blue) = sscanf($shadeb, '%02x%02x%02x');
3619	$shadeb = imagecolorallocate($image, $red, $green, $blue);
3620	imagefill($image, 0, 0, $shadeb);
3621
3622	/* set the background color */
3623	list($red, $green, $blue) = sscanf($shadea, '%02x%02x%02x');
3624	$shadea = imagecolorallocate($image, $red, $green, $blue);
3625	imagefilledrectangle($image, 1, 1, 448, 198, $shadea);
3626
3627	/* set the background color */
3628	list($red, $green, $blue) = sscanf($back_color, '%02x%02x%02x');
3629	$back_color = imagecolorallocate($image, $red, $green, $blue);
3630	imagefilledrectangle($image, 2, 2, 447, 197, $back_color);
3631
3632	/* allocate the image */
3633	$logo = imagecreatefrompng($config['base_path'] . '/images/cacti_error_image.png');
3634
3635	/* merge the two images */
3636	imagecopy($image, $logo, 0, 0, 0, 0, 450, 200);
3637
3638	/* set the background color */
3639	list($red, $green, $blue) = sscanf($font_color, '%02x%02x%02x');
3640	$text_color = imagecolorallocate($image, $red, $green, $blue);
3641
3642	/* see the size of the string */
3643	$string    = trim($string);
3644	$maxstring = (450 - (125 + 10)) / ($font_size / 0.9);
3645	$stringlen = strlen($string) * $font_size;
3646	$padding   = 5;
3647
3648	if ($stringlen > $maxstring) {
3649		$cstring = wordwrap($string, $maxstring, "\n", true);
3650		$strings = explode("\n", $cstring);
3651		$strings = array_reverse($strings);
3652		$lines   = cacti_sizeof($strings);
3653	} elseif (strlen(trim($string)) == 0) {
3654		$strings = array(__('Unknown RRDtool Error'));
3655		$lines   = 1;
3656	} else {
3657		$strings = array($string);
3658		$lines   = 1;
3659	}
3660
3661	/* setup the text position, image is 450x200, we start at 125 pixels from the left */
3662	$xpos  = 125;
3663	$texth = ($lines * $font_size + (($lines - 1) * $padding));
3664	$ypos  = round((200 / 2) + ($texth / 2),0);
3665
3666	/* set the font of the image */
3667	if (file_exists($font_file) && is_readable($font_file) && function_exists('imagettftext')) {
3668		foreach($strings as $string) {
3669			if (trim($string) != '') {
3670				if (@imagettftext($image, $font_size, 0, $xpos, $ypos, $text_color, $font_file, $string) === false) {
3671					cacti_log('TTF text overlay failed');
3672				}
3673				$ypos -= ($font_size + $padding);
3674			}
3675		}
3676	} else {
3677		foreach($strings as $string) {
3678			if (trim($string) != '') {
3679				if (@imagestring($image, $font_size, $xpos, $ypos, $string, $text_color) === false) {
3680					cacti_log('Text overlay failed');
3681				}
3682				$ypos -= ($font_size + $padding);
3683			}
3684		}
3685	}
3686
3687	if ($width != '' && $height != '') {
3688		$nimage = imagecreatetruecolor($width, $height);
3689		imagecopyresized($nimage, $image, 0, 0, 0, 0, $width, $height, 450, 200);
3690
3691		/* create the image */
3692		imagepng($image);
3693	} else {
3694		/* create the image */
3695		imagepng($image);
3696	}
3697
3698	/* get the image from the buffer */
3699	$image_data = ob_get_contents();
3700
3701	/* destroy the image object */
3702	imagedestroy($image);
3703	imagedestroy($logo);
3704	if (isset($nimage)) {
3705		imagedestroy($nimage);
3706	}
3707
3708	/* flush the buffer */
3709	ob_end_clean();
3710
3711	return $image_data;
3712}
3713