* Copyright (c) 2014-2015 Gear Consulting Pty Ltd 
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.  Please see LICENSE.txt at the top level of
 * the source code distribution for details.
 */
function format_number_short($number, $sf) {
    // This formats a number so that we only send back three digits plus an optional decimal point.
    // Example: 723.42 -> 723    72.34 -> 72.3    2.23 -> 2.23
    list($whole, $decimal) = explode (".", $number);
    if (strlen($whole) >= $sf || !is_numeric($decimal)) {
        $number = $whole;
    }
    elseif(strlen($whole) < $sf) {
        $diff = $sf - strlen($whole);
        $number = $whole .".".substr($decimal, 0, $diff);
    }
    return $number;
}
function external_exec($command) {
    global $debug,$vdebug;
    if ($debug && !$vdebug) {
        $debug_command = preg_replace('/-c [\S]+/','-c COMMUNITY',$command);
        $debug_command = preg_replace('/(udp|udp6|tcp|tcp6):(.*):([\d]+)/','\1:HOSTNAME:\3',$debug_command);
        d_echo($debug_command);
    }
    elseif ($vdebug) {
        d_echo($command."\n");
    }
    $output = shell_exec($command);
    if ($debug && !$vdebug) {
        $debug_output = preg_replace('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/', '*', $output);
        d_echo("$debug_output\n");
    }
    elseif ($vdebug) {
        d_echo($output."\n");
    }
    return $output;
}
function shorthost($hostname, $len=12) {
    // IP addresses should not be shortened
    if (filter_var($hostname, FILTER_VALIDATE_IP))
        return $hostname;
    $parts = explode(".", $hostname);
    $shorthost = $parts[0];
    $i = 1;
    while ($i < count($parts) && strlen($shorthost.'.'.$parts[$i]) < $len) {
        $shorthost = $shorthost.'.'.$parts[$i];
        $i++;
    }
    return ($shorthost);
}
function isCli() {
    if (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR'])) {
        return true;
    }
    else {
        return false;
    }
}
function print_error($text) {
    global $console_color;
    if (isCli()) {
        print $console_color->convert("%r".$text."%n\n", false);
    }
    else {
        echo('

 '.$text.'

 '.$text.'
MIB polling is not enabled
Set $config[\'poller_modules\'][\'mib\'] = 1; in config.php to enable.
';
} // print_mib_poller_disabled
/**
 * Constructs the path to an RRD for the Ceph application
 * @param string $gtype The type of rrd we're looking for
 * @return string
**/
function ceph_rrd($gtype) {
    global $device;
    global $vars;
    if ($gtype == "osd") {
        $var = $vars['osd'];
    }
    else {
        $var = $vars['pool'];
    }
    return rrd_name($device['hostname'], array('app', 'ceph', $vars['id'], $gtype, $var));
} // ceph_rrd
/**
 * Parse location field for coordinates
 * @param string location The location field to look for coords in.
 * @return array Containing the lat and lng coords
**/
function parse_location($location) {
    preg_match('/(\[)(-?[0-9\. ]+),[ ]*(-?[0-9\. ]+)(\])/', $location, $tmp_loc);
    if (!empty($tmp_loc[2]) && !empty($tmp_loc[3])) {
        return array('lat' => $tmp_loc[2], 'lng' => $tmp_loc[3]);
    }
}//end parse_location()
/**
 * Returns version info
 * @return array
**/
function version_info($remote=true) {
    global $config;
    $output = array();
    if ($remote === true && $config['update_channel'] == 'master') {
        $api = curl_init();
        set_curl_proxy($api);
        curl_setopt($api, CURLOPT_USERAGENT,'LibreNMS');
        curl_setopt($api, CURLOPT_URL, $config['github_api'].'commits/master');
        curl_setopt($api, CURLOPT_RETURNTRANSFER, 1);
        $output['github'] = json_decode(curl_exec($api),true);
    }
    list($local_sha, $local_date) = explode('|', rtrim(`git show --pretty='%H|%ct' -s HEAD`));
    $output['local_sha']    = $local_sha;
    $output['local_date']   = $local_date;
    $output['local_branch'] = rtrim(`git rev-parse --abbrev-ref HEAD`);
    $output['db_schema']   = dbFetchCell('SELECT version FROM dbSchema');
    $output['php_ver']     = phpversion();
    $output['mysql_ver']   = dbFetchCell('SELECT version()');
    $output['rrdtool_ver'] = implode(' ', array_slice(explode(' ', shell_exec($config['rrdtool'].' --version |head -n1')), 1, 1));
    $output['netsnmp_ver'] = shell_exec($config['snmpget'].' --version 2>&1');
    return $output;
}//end version_info()
/**
* Convert a MySQL binary v4 (4-byte) or v6 (16-byte) IP address to a printable string.
* @param string $ip A binary string containing an IP address, as returned from MySQL's INET6_ATON function
* @return string Empty if not valid.
*/
// Fuction is from http://uk3.php.net/manual/en/function.inet-ntop.php
function inet6_ntop($ip) {
    $l = strlen($ip);
    if ($l == 4 or $l == 16) {
        return inet_ntop(pack('A' . $l, $ip));
    }
    return '';
}
/**
 * Convert IP to use sysName
 * @param array device
 * @param string ip address
 * @return string
**/
function ip_to_sysname($device,$ip) {
    global $config;
    if ($config['force_ip_to_sysname'] === true) {
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) == true || filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) == true) {
            $ip = $device['sysName'];
        }
    }
    return $ip;
}//end ip_to_sysname
/**
 * Return valid port association modes
 * @param bool $no_cache No-Cache flag (optional, default false)
 * @return array
 */
function get_port_assoc_modes ($no_cache = false) {
    global $config;
    if ($config['memcached']['enable'] && $no_cache === false) {
        $assoc_modes = $config['memcached']['resource']->get (hash ('sha512', "port_assoc_modes"));
        if (! empty ($assoc_modes))
            return $assoc_modes;
    }
    $assoc_modes = Null;
        foreach (dbFetchRows ("SELECT `name` FROM `port_association_mode` ORDER BY pom_id") as $row)
        $assoc_modes[] = $row['name'];
    if ($config['memcached']['enable'] && $no_cache === false)
        $config['memcached']['resource']->set (hash ('sha512', "port_assoc_modes"), $assoc_modes, $config['memcached']['ttl']);
    return $assoc_modes;
}
/**
 * Validate port_association_mode
 * @param string $port_assoc_mode
 * @return bool
 */
function is_valid_port_assoc_mode ($port_assoc_mode) {
    return in_array ($port_assoc_mode, get_port_assoc_modes ());
}
/**
 * Get DB id of given port association mode name
 * @param string $port_assoc_mode
 * @param bool $no_cache No-Cache flag (optional, default false)
 */
function get_port_assoc_mode_id ($port_assoc_mode, $no_cache = false) {
    global $config;
    if ($config['memcached']['enable'] && $no_cache === false) {
        $id = $config['memcached']['resource']->get (hash ('sha512', "port_assoc_mode_id|$port_assoc_mode"));
        if (! empty ($id))
            return $id;
    }
    $id = Null;
    $row = dbFetchRow ("SELECT `pom_id` FROM `port_association_mode` WHERE name = ?", array ($port_assoc_mode));
    if ($row) {
        $id = $row['pom_id'];
        if ($config['memcached']['enable'] && $no_cache === false)
            $config['memcached']['resource']->set (hash ('sha512', "port_assoc_mode_id|$port_assoc_mode"), $id, $config['memcached']['ttl']);
    }
    return $id;
}
/**
 * Get name of given port association_mode ID
 * @param int $port_assoc_mode_id Port association mode ID
 * @param bool $no_cache No-Cache flag (optional, default false)
 * @return bool
 */
function get_port_assoc_mode_name ($port_assoc_mode_id, $no_cache = false) {
    global $config;
    if ($config['memcached']['enable'] && $no_cache === false) {
        $name = $config['memcached']['resource']->get (hash ('sha512', "port_assoc_mode_name|$port_assoc_mode_id"));
        if (! empty ($name))
            return $name;
    }
    $name = Null;
    $row = dbFetchRow ("SELECT `name` FROM `port_association_mode` WHERE pom_id = ?", array ($port_assoc_mode_id));
    if ($row) {
        $name = $row['name'];
        if ($config['memcached']['enable'] && $no_cache === false)
            $config['memcached']['resource']->set (hash ('sha512', "port_assoc_mode_name|$port_assoc_mode_id"), $name, $config['memcached']['ttl']);
    }
    return $name;
}
/**
 * Query all ports of the given device (by ID) and build port array and
 * port association maps for ifIndex, ifName, ifDescr. Query port stats
 * if told to do so, too.
 * @param int $device_id ID of device to query ports for
 * @param bool $with_statistics Query port statistics, too. (optional, default false)
 * @return array
 */
function get_ports_mapped ($device_id, $with_statistics = false) {
    $ports = array();
    $maps = array(
        'ifIndex' => array(),
        'ifName'  => array(),
        'ifDescr' => array(),
    );
    /* Query all information available for ports for this device ... */
    $query = 'SELECT * FROM `ports` WHERE `device_id` = ? ORDER BY port_id';
    if ($with_statistics) {
        /* ... including any related ports_statistics if requested */
        $query = 'SELECT *, `ports_statistics`.`port_id` AS `ports_statistics_port_id`, `ports`.`port_id` AS `port_id` FROM `ports` LEFT OUTER JOIN `ports_statistics` ON `ports`.`port_id` = `ports_statistics`.`port_id` WHERE `ports`.`device_id` = ? ORDER BY ports.port_id';
    }
    // Query known ports in order of discovery to make sure the latest
    // discoverd/polled port is in the mapping tables.
    foreach (dbFetchRows ($query, array ($device_id)) as $port) {
        // Store port information by ports port_id from DB
        $ports[$port['port_id']] = $port;
        // Build maps from ifIndex, ifName, ifDescr to port_id
        $maps['ifIndex'][$port['ifIndex']] = $port['port_id'];
        $maps['ifName'][$port['ifName']]   = $port['port_id'];
        $maps['ifDescr'][$port['ifDescr']] = $port['port_id'];
    }
    return array(
        'ports' => $ports,
        'maps'  => $maps,
    );
}
/**
 * Calculate port_id of given port using given devices port information and port association mode
 * @param array $ports_mapped Port information of device queried by get_ports_mapped()
 * @param array $port Port information as fetched from DB
 * @param string $port_association_mode Port association mode to use for mapping
 * @return int port_id (or Null)
 */
function get_port_id ($ports_mapped, $port, $port_association_mode) {
    // Get port_id according to port_association_mode used for this device
    $port_id = Null;
    /*
     * Information an all ports is available through $ports_mapped['ports']
     * This might come in handy sometime in the future to add you nifty new
     * port mapping schema:
     *
     * $ports = $ports_mapped['ports'];
    */
    $maps  = $ports_mapped['maps'];
    if (in_array ($port_association_mode, array ('ifIndex', 'ifName', 'ifDescr', 'ifAlias'))) {
        $port_id = $maps[$port_association_mode][$port[$port_association_mode]];
    }
    return $port_id;
}
/**
 * Create a glue-chain
 * @param array $tables Initial Tables to construct glue-chain
 * @param string $target Glue to find (usual device_id)
 * @param int $x Recursion Anchor
 * @param array $hist History of processed tables
 * @param array $last Glues on the fringe
 * @return string|boolean
 */
function ResolveGlues($tables,$target,$x=0,$hist=array(),$last=array()) {
    if( sizeof($tables) == 1 && $x != 0 ) {
        if( dbFetchCell('SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_NAME = ? && COLUMN_NAME = ?',array($tables[0],$target)) == 1 ) {
            return array_merge($last,array($tables[0].'.'.$target));
        }
        else {
            return false;
        }
    }
    else {
        $x++;
        if( $x > 30 ) {
            //Too much recursion. Abort.
            return false;
        }
        foreach( $tables as $table ) {
            $glues = dbFetchRows('SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME = ? && COLUMN_NAME LIKE "%\_id"',array($table));
            if( sizeof($glues) == 1 && $glues[0]['COLUMN_NAME'] != $target ) {
                //Search for new candidates to expand
                $ntables = array();
                list($tmp) = explode('_',$glues[0]['COLUMN_NAME'],2);
                $ntables[] = $tmp;
                $ntables[] = $tmp.'s';
                $tmp = dbFetchRows('SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_NAME LIKE "'.substr($table,0,-1).'_%" && TABLE_NAME != "'.$table.'"');
                foreach( $tmp as $expand ) {
                    $ntables[] = $expand['TABLE_NAME'];
                }
                $tmp = ResolveGlues($ntables,$target,$x++,array_merge($tables,$ntables),array_merge($last,array($table.'.'.$glues[0]['COLUMN_NAME'])));
                if( is_array($tmp) ) {
                    return $tmp;
                }
            }
            else {
                foreach( $glues as $glue ) {
                    if( $glue['COLUMN_NAME'] == $target ) {
                        return array_merge($last,array($table.'.'.$target));
                    }
                    else {
                        list($tmp) = explode('_',$glue['COLUMN_NAME']);
                        $tmp .= 's';
                        if( !in_array($tmp,$tables) && !in_array($tmp,$hist) ) {
                            //Expand table
                            $tmp = ResolveGlues(array($tmp),$target,$x++,array_merge($tables,array($tmp)),array_merge($last,array($table.'.'.$glue['COLUMN_NAME'])));
                            if( is_array($tmp) ) {
                                return $tmp;
                            }
                        }
                    }
                }
            }
        }
    }
    //You should never get here.
    return false;
}