712 lines
19 KiB
PHP
Executable File

<?php
/** editor.inc.php
*
* All the functions used by the editor.
*/
/** @function fix_gpc_string
*
* Take a string (that we got from $_REQUEST) and make it back to how the
* user TYPED it, regardless of whether magic_quotes_gpc is turned on or off.
*
* @param string $input String to fix
*
* @returns string Fixed string
*
*/
function fix_gpc_string($input)
{
if (true == function_exists('get_magic_quotes_gpc') && 1 == get_magic_quotes_gpc()) {
$input = stripslashes($input);
}
return ($input);
}
/**
* Clean up URI (function taken from Cacti) to protect against XSS
*/
function wm_editor_sanitize_uri($str) {
static $drop_char_match = array(' ','^', '$', '<', '>', '`', '\'', '"', '|', '+', '[', ']', '{', '}', ';', '!', '%');
static $drop_char_replace = array('', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '');
return str_replace($drop_char_match, $drop_char_replace, urldecode($str));
}
// much looser sanitise for general strings that shouldn't have HTML in them
function wm_editor_sanitize_string($str) {
static $drop_char_match = array('<', '>' );
static $drop_char_replace = array('', '');
return str_replace($drop_char_match, $drop_char_replace, urldecode($str));
}
function wm_editor_validate_bandwidth($bw) {
if(preg_match( '/^(\d+\.?\d*[KMGT]?)$/', $bw) ) {
return true;
}
return false;
}
function wm_editor_validate_one_of($input,$valid=array(),$case_sensitive=false) {
if(! $case_sensitive ) $input = strtolower($input);
foreach ($valid as $v) {
if(! $case_sensitive ) $v = strtolower($v);
if($v == $input) return true;
}
return false;
}
// Labels for Nodes, Links and Scales shouldn't have spaces in
function wm_editor_sanitize_name($str) {
return str_replace( array(" "), "", $str);
}
function wm_editor_sanitize_selected($str) {
$res = urldecode($str);
if( ! preg_match("/^(LINK|NODE):/",$res)) {
return "";
}
return wm_editor_sanitize_name($res);
}
function wm_editor_sanitize_file($filename,$allowed_exts=array()) {
$filename = wm_editor_sanitize_uri($filename);
if ($filename == "") return "";
$ok = false;
foreach ($allowed_exts as $ext) {
$match = ".".$ext;
if( substr($filename, -strlen($match),strlen($match)) == $match) {
$ok = true;
}
}
if(! $ok ) return "";
return $filename;
}
function wm_editor_sanitize_conffile($filename) {
$filename = wm_editor_sanitize_uri($filename);
# If we've been fed something other than a .conf filename, just pretend it didn't happen
if ( substr($filename,-5,5) != ".conf" ) {
$filename = "";
}
# on top of the url stuff, we don't ever need to see a / in a config filename (CVE-2013-3739)
if (strstr($filename,"/") !== false ) {
$filename = "";
}
return $filename;
}
function show_editor_startpage()
{
global $mapdir, $WEATHERMAP_VERSION, $config_loaded, $cacti_found, $ignore_cacti,$configerror;
$fromplug = false;
if (isset($_REQUEST['plug']) && (intval($_REQUEST['plug'])==1) ) {
$fromplug = true;
}
$matches=0;
$errormessage = "";
if ($configerror!='') {
$errormessage .= $configerror.'<p>';
}
if ( !$observium_found && !$ignore_observium) {
//$errormessage .= '$cacti_base is not set correctly. Cacti integration will be disabled in the editor.';
//$errormessage .= "$observium_found and $ignore_observium";
//if ($config_loaded != 1) {
//$errormessage .= " You might need to copy editor-config.php-dist to editor-config.php and edit it.";
//}
}
if ($errormessage != '') {
print '<div class="alert" id="nocacti">'.htmlspecialchars($errormessage).'</div>';
}
print '<div id="withjs">';
print '<div id="dlgStart" class="dlgProperties" ><div class="dlgTitlebar">Weathermap Editor</div><div class="dlgBody">';
// print 'Welcome to the PHP Weathermap '.$WEATHERMAP_VERSION.' editor.';
// print '<p><div style="border: 3px dashed red; background: #055; padding: 5px; font-size: 90%;"><b>NOTE:</b> This editor is not finished! There are many features of ';
// print 'Weathermap that you will be missing out on if you choose to use the editor only.';
// print 'These include: curves, node offsets, font definitions, colour changing, per-node/per-link settings and image uploading. You CAN use the editor without damaging these features if you added them by hand, however.</div><p>';
// print 'Do you want to:<p>';
print '<h4>Create A New Map</h4>';
print '<form method="GET">';
print 'Named: <input type="text" name="mapname" size="20">';
print '<input name="action" type="hidden" value="newmap">';
print '<input name="plug" type="hidden" value="'.$fromplug.'">';
print '<input type="submit" value="Create">';
print '<p><small><i>Note: filenames must contain no spaces and end in .conf</i></small></p>';
print '</form>';
$titles = array();
$errorstring="";
if (is_dir($mapdir)) {
$n=0;
$dh=opendir($mapdir);
if ($dh) {
while (false !== ($file = readdir($dh))) {
$realfile=$mapdir . DIRECTORY_SEPARATOR . $file;
$note = "";
// skip directories, unreadable files, .files and anything that doesn't come through the sanitiser unchanged
if ( (is_file($realfile)) && (is_readable($realfile)) && (!preg_match("/^\./",$file) ) && ( wm_editor_sanitize_conffile($file) == $file ) ) {
if (!is_writable($realfile)) {
$note .= "(read-only)";
}
$title='(no title)';
$fd=fopen($realfile, "r");
if ($fd) {
while (!feof($fd)) {
$buffer=fgets($fd, 4096);
if (preg_match('/^\s*TITLE\s+(.*)/i', $buffer, $matches)) {
$title= wm_editor_sanitize_string($matches[1]);
}
}
fclose ($fd);
$titles[$file] = $title;
$notes[$file] = $note;
$n++;
}
}
}
closedir ($dh);
} else {
$errorstring = "Can't open mapdir to read.";
}
ksort($titles);
if ($n == 0) {
$errorstring = "No files in mapdir";
}
} else {
$errorstring = "NO DIRECTORY named $mapdir";
}
print '<h4>Duplicate An Existing Map</h4>';
print '<form method="GET">';
print 'Named: <input type="text" name="mapname" size="20"> based on ';
print '<input name="action" type="hidden" value="newmapcopy">';
print '<input name="plug" type="hidden" value="'.$fromplug.'">';
print '<select name="sourcemap">';
if ($errorstring == '') {
foreach ($titles as $file=>$title) {
$nicefile = htmlspecialchars($file);
print "<option value=\"$nicefile\">$nicefile</option>\n";
}
} else {
print '<option value="">'.htmlspecialchars($errorstring).'</option>';
}
print '</select>';
print '<input type="submit" value="Create Copy">';
print '</form>';
print '<h4>Open An Existing Map</h4><ul class="filelist">';
if ($errorstring == '') {
foreach ($titles as $file=>$title) {
# $title = $titles[$file];
$note = $notes[$file];
$nicefile = htmlspecialchars($file);
$nicetitle = htmlspecialchars($title);
print '<li>'.$note.'<a href="/wmap/mapname='.$nicefile.'/">'.$nicefile.'</a> - <span class="comment">'.$nicetitle.'</span></li>';
}
} else {
print '<li>'.htmlspecialchars($errorstring).'</li>';
}
print "</ul>";
print "</div>"; // dlgbody
print '<div class="dlgHelp" id="start_help">PHP Weathermap ' . $WEATHERMAP_VERSION
. ' &copy; 2005-2019 Howard Jones - howie@thingy.com<br /> PHP Weathermap is licensed under the MIT License. This distribution also includes the Overlib library by Erik Bosrup. This version is modified for use with Observium.</div>';
print "</div>"; // dlgStart
print "</div>"; // withjs
print "</body></html>";
}
function snap($coord, $gridsnap = 0)
{
if ($gridsnap == 0) {
return ($coord);
} else {
$rest = $coord % $gridsnap;
return ($coord - $rest + round($rest/$gridsnap) * $gridsnap );
}
}
function extract_with_validation($array, $paramarray, $prefix = "")
{
$all_present=true;
$candidates=array( );
foreach ($paramarray as $var) {
$varname=$var[0];
$vartype=$var[1];
$varreqd=$var[2];
if ($varreqd == 'req' && !array_key_exists($varname, $array)) {
$all_present=false;
}
if (array_key_exists($varname, $array)) {
$varvalue=$array[$varname];
$waspresent=$all_present;
switch ($vartype)
{
case 'int':
if (!preg_match('/^\-*\d+$/', $varvalue)) {
$all_present=false;
}
break;
case 'float':
if (!preg_match('/^\d+\.\d+$/', $varvalue)) {
$all_present=false;
}
break;
case 'yesno':
if (!preg_match('/^(y|n|yes|no)$/i', $varvalue)) {
$all_present=false;
}
break;
case 'sqldate':
if (!preg_match('/^\d\d\d\d\-\d\d\-\d\d$/i', $varvalue)) {
$all_present=false;
}
break;
case 'any':
// we don't care at all
break;
case 'ip':
if (!preg_match( '/^((\d|[1-9]\d|2[0-4]\d|25[0-5]|1\d\d)(?:\.(\d|[1-9]\d|2[0-4]\d|25[0-5]|1\d\d)){3})$/', $varvalue)) {
$all_present=false;
}
break;
case 'alpha':
if (!preg_match('/^[A-Za-z]+$/', $varvalue)) {
$all_present=false;
}
break;
case 'alphanum':
if (!preg_match('/^[A-Za-z0-9]+$/', $varvalue)) {
$all_present=false;
}
break;
case 'bandwidth':
if (!preg_match('/^\d+\.?\d*[KMGT]*$/i', $varvalue)) {
$all_present=false;
}
break;
default:
// an unknown type counts as an error, really
$all_present=false;
break;
}
if ($all_present) {
$candidates["{$prefix}{$varname}"]=$varvalue;
}
}
}
if ($all_present) {
foreach ($candidates as $key => $value) {
$GLOBALS[$key]=$value;
}
}
return array($all_present,$candidates);
}
function get_imagelist($imagedir)
{
$imagelist = array();
if (is_dir($imagedir)) {
$n=0;
$dh=opendir($imagedir);
if ($dh) {
while ($file=readdir($dh)) {
$realfile=$imagedir . DIRECTORY_SEPARATOR . $file;
$uri = $imagedir . "/" . $file;
if (is_readable($realfile) && ( preg_match('/\.(gif|jpg|png)$/i',$file) )) {
$imagelist[] = $uri;
$n++;
}
}
closedir ($dh);
}
}
return ($imagelist);
}
function handle_inheritance(&$map, &$inheritables)
{
foreach ($inheritables as $inheritable) {
$fieldname = $inheritable[1];
$formname = $inheritable[2];
$validation = $inheritable[3];
$new = $_REQUEST[$formname];
if($validation != "") {
switch($validation) {
case "int":
$new = intval($new);
break;
case "float":
$new = floatval($new);
break;
}
}
$old = ($inheritable[0]=='node' ? $map->nodes['DEFAULT']->$fieldname : $map->links['DEFAULT']->$fieldname);
if ($old != $new) {
if ($inheritable[0]=='node') {
$map->nodes['DEFAULT']->$fieldname = $new;
foreach ($map->nodes as $node) {
if ($node->name != ":: DEFAULT ::" && $node->$fieldname == $old) {
$map->nodes[$node->name]->$fieldname = $new;
}
}
}
if ($inheritable[0]=='link') {
$map->links['DEFAULT']->$fieldname = $new;
foreach ($map->links as $link) {
if ($link->name != ":: DEFAULT ::" && $link->$fieldname == $old) {
$map->links[$link->name]->$fieldname = $new;
}
}
}
}
}
}
function get_fontlist(&$map,$name,$current)
{
$output = '<select class="fontcombo" name="'.$name.'">';
ksort($map->fonts);
foreach ($map->fonts as $fontnumber => $font) {
$output .= '<option ';
if ($current == $fontnumber) {
$output .= 'SELECTED';
}
$output .= ' value="'.$fontnumber.'">'.$fontnumber.' ('.$font->type.')</option>';
}
$output .= "</select>";
return($output);
}
function range_overlaps($a_min, $a_max, $b_min, $b_max)
{
if ($a_min > $b_max) {
return false;
}
if ($b_min > $a_max) {
return false;
}
return true;
}
function common_range ($a_min,$a_max, $b_min, $b_max)
{
$min_overlap = max($a_min, $b_min);
$max_overlap = min($a_max, $b_max);
return array($min_overlap,$max_overlap);
}
/* distance - find the distance between two points
*
*/
function distance ($ax,$ay, $bx,$by)
{
$dx = $bx - $ax;
$dy = $by - $ay;
return sqrt( $dx*$dx + $dy*$dy );
}
function tidy_links(&$map,$targets, $ignore_tidied=FALSE)
{
// not very efficient, but it saves looking for special cases (a->b & b->a together)
$ntargets = count($targets);
$i = 1;
foreach ($targets as $target) {
tidy_link($map, $target, $i, $ntargets, $ignore_tidied);
$i++;
}
}
/**
* tidy_link - change link offsets so that link is horizonal or vertical, if possible.
* if not possible, change offsets to the closest facing compass points
*/
function tidy_link(&$map,$target, $linknumber=1, $linktotal=1, $ignore_tidied=FALSE)
{
// print "\n-----------------------------------\nTidying $target...\n";
if(isset($map->links[$target]) and isset($map->links[$target]->a) ) {
$node_a = $map->links[$target]->a;
$node_b = $map->links[$target]->b;
$new_a_offset = "0:0";
$new_b_offset = "0:0";
// Update TODO: if the nodes are already directly left/right or up/down, then use compass-points, not pixel offsets
// (e.g. N90) so if the label changes, they won't need to be re-tidied
// First bounding box in the node's boundingbox array is the icon, if there is one, or the label if not.
$bb_a = $node_a->boundingboxes[0];
$bb_b = $node_b->boundingboxes[0];
// figure out if they share any x or y coordinates
$x_overlap = range_overlaps($bb_a[0], $bb_a[2], $bb_b[0], $bb_b[2]);
$y_overlap = range_overlaps($bb_a[1], $bb_a[3], $bb_b[1], $bb_b[3]);
$a_x_offset = 0; $a_y_offset = 0;
$b_x_offset = 0; $b_y_offset = 0;
// if they are side by side, and there's some common y coords, make link horizontal
if ( !$x_overlap && $y_overlap ) {
// print "SIDE BY SIDE\n";
// snap the X coord to the appropriate edge of the node
if ($bb_a[2] < $bb_b[0]) {
$a_x_offset = $bb_a[2] - $node_a->x;
$b_x_offset = $bb_b[0] - $node_b->x;
}
if ($bb_b[2] < $bb_a[0]) {
$a_x_offset = $bb_a[0] - $node_a->x;
$b_x_offset = $bb_b[2] - $node_b->x;
}
// this should be true whichever way around they are
list($min_overlap,$max_overlap) = common_range($bb_a[1],$bb_a[3],$bb_b[1],$bb_b[3]);
$overlap = $max_overlap - $min_overlap;
$n = $overlap/($linktotal+1);
$a_y_offset = $min_overlap + ($linknumber*$n) - $node_a->y;
$b_y_offset = $min_overlap + ($linknumber*$n) - $node_b->y;
$new_a_offset = sprintf("%d:%d", $a_x_offset,$a_y_offset);
$new_b_offset = sprintf("%d:%d", $b_x_offset,$b_y_offset);
}
// if they are above and below, and there's some common x coords, make link vertical
if ( !$y_overlap && $x_overlap ) {
// print "ABOVE/BELOW\n";
// snap the Y coord to the appropriate edge of the node
if ($bb_a[3] < $bb_b[1]) {
$a_y_offset = $bb_a[3] - $node_a->y;
$b_y_offset = $bb_b[1] - $node_b->y;
}
if ($bb_b[3] < $bb_a[1]) {
$a_y_offset = $bb_a[1] - $node_a->y;
$b_y_offset = $bb_b[3] - $node_b->y;
}
list($min_overlap,$max_overlap) = common_range($bb_a[0],$bb_a[2],$bb_b[0],$bb_b[2]);
$overlap = $max_overlap - $min_overlap;
$n = $overlap/($linktotal+1);
// move the X coord to the centre of the overlapping area
$a_x_offset = $min_overlap + ($linknumber*$n) - $node_a->x;
$b_x_offset = $min_overlap + ($linknumber*$n) - $node_b->x;
$new_a_offset = sprintf("%d:%d", $a_x_offset,$a_y_offset);
$new_b_offset = sprintf("%d:%d", $b_x_offset,$b_y_offset);
}
// if no common coordinates, figure out the best diagonal...
if ( !$y_overlap && !$x_overlap ) {
$pt_a = new WMPoint($node_a->x, $node_a->y);
$pt_b = new WMPoint($node_b->x, $node_b->y);
$line = new WMLineSegment($pt_a, $pt_b);
$tangent = $line->vector;
$tangent->normalise();
$normal = $tangent->getNormal();
$pt_a->AddVector( $normal, 15 * ($linknumber-1) );
$pt_b->AddVector( $normal, 15 * ($linknumber-1) );
$a_x_offset = $pt_a->x - $node_a->x;
$a_y_offset = $pt_a->y - $node_a->y;
$b_x_offset = $pt_b->x - $node_b->x;
$b_y_offset = $pt_b->y - $node_b->y;
$new_a_offset = sprintf("%d:%d", $a_x_offset,$a_y_offset);
$new_b_offset = sprintf("%d:%d", $b_x_offset,$b_y_offset);
}
// if no common coordinates, figure out the best diagonal...
// currently - brute force search the compass points for the shortest distance
// potentially - intersect link line with rectangles to get exact crossing point
if ( 1==0 && !$y_overlap && !$x_overlap ) {
// print "DIAGONAL\n";
$corners = array("NE","E","SE","S","SW","W","NW","N");
// start with what we have now
$best_distance = distance( $node_a->x, $node_a->y, $node_b->x, $node_b->y );
$best_offset_a = "C";
$best_offset_b = "C";
foreach ($corners as $corner1) {
list ($ax,$ay) = calc_offset($corner1, $bb_a[2] - $bb_a[0], $bb_a[3] - $bb_a[1]);
$axx = $node_a->x + $ax;
$ayy = $node_a->y + $ay;
foreach ($corners as $corner2) {
list($bx,$by) = calc_offset($corner2, $bb_b[2] - $bb_b[0], $bb_b[3] - $bb_b[1]);
$bxx = $node_b->x + $bx;
$byy = $node_b->y + $by;
$d = distance($axx,$ayy, $bxx, $byy);
if($d < $best_distance) {
// print "from $corner1 ($axx, $ayy) to $corner2 ($bxx, $byy): ";
// print "NEW BEST $d\n";
$best_distance = $d;
$best_offset_a = $corner1;
$best_offset_b = $corner2;
}
}
}
// Step back a bit from the edge, to hide the corners of the link
$new_a_offset = $best_offset_a."85";
$new_b_offset = $best_offset_b."85";
}
// unwritten/implied - if both overlap, you're doing something weird and you're on your own
// finally, update the offsets
$map->links[$target]->a_offset = $new_a_offset;
$map->links[$target]->b_offset = $new_b_offset;
// and also add a note that this link was tidied, and is eligible for automatic tidying
$map->links[$target]->add_hint("_tidied",1);
}
}
function untidy_links(&$map)
{
foreach ($map->links as $link)
{
$link->a_offset = "C";
$link->b_offset = "C";
}
}
function retidy_links(&$map, $ignore_tidied=FALSE)
{
$routes = array();
$done = array();
foreach ($map->links as $link)
{
if(isset($link->a)) {
$route = $link->a->name . " " . $link->b->name;
if(strcmp( $link->a->name, $link->b->name) > 0) {
$route = $link->b->name . " " . $link->a->name;
}
$routes[$route][] = $link->name;
}
}
foreach ($map->links as $link)
{
if(isset($link->a)) {
$route = $link->a->name . " " . $link->b->name;
if(strcmp( $link->a->name, $link->b->name) > 0) {
$route = $link->b->name . " " . $link->a->name;
}
if( ($ignore_tidied || $link->get_hint("_tidied")==1) && !isset($done[$route]) && isset( $routes[$route] ) ) {
if( sizeof($routes[$route]) == 1) {
tidy_link($map,$link->name);
$done[$route] = 1;
} else {
# handle multi-links specially...
tidy_links($map,$routes[$route]);
// mark it so we don't do it again when the other links come by
$done[$route] = 1;
}
}
}
}
}
function editor_log($str)
{
// $f = fopen("editor.log","a");
// fputs($f, $str);
// fclose($f);
}
// vim:ts=4:sw=4: