<?php 
namespace IPv6Helper;

if (isset(
$_GET['source']) || strpos($_SERVER['REQUEST_URI'], 'ipv6.class.php') !== false) {
    
highlight_file(__FILE__);
    exit;
}

class 
IPv6 {
    
/**
     * string expand(string ip_address, bool|int force_v6 = false, bool no_leading = false, bool no_doublecolon = false)
     * Expands an IPv6 address to its full size (adds all the zeros)
     * Will return boolean false upon:
     *  A. Invalid in IPv6 address (note that it does not perform extensive
     *     tests); or
     *  B. When $force_v6 is set to true AND it's an IPv4 address
     * If you pass an IPv4 address and force_v6 is false (the default),
     * the address is converted to IPv6 format automatically.
     * 
     * If $force_v6 is set to integer 2 (or anything other than a boolean),
     * AND an IPv4 address is passed, it will simply return the IPv4 formatted
     * address.
     * 
     * If $no_leading is set to true, it will only expand the double colon in
     * the address, if present. It will also replace the double colon with
     * ':0:' instead of ':0000:'.
     * 
     * If $no_doublecolon is set to true, it will only expand the leading zeros
     * and leave the double colon alone (if present, it won't add it either).
     **/
    
public static function expand($ip$force_v6 false$no_leading false$no_doublecolon false) {
        
// ... so, did we actually get an IPv6 address or are they tricking us?
        
if (!self::isv6($ip)) {
            if (
$force_v6 === true) {
                return 
false// Trickery! That'll teach 'em.
            
}
            else if (
$force_v6 === false) {
                
$ip self::v4tov6($ip);
            }
            else {
                return 
$ip;
            }
        }
        
        
// If we should process leading zeros in parts of the IPv6 address
        
if (!$no_leading) {
            
$ip explode(':'$ip);
            foreach (
$ip as $partindex=>$part) {
                if (empty(
$part)) {
                    
// This is probably a double colon in the address
                    
continue;
                }
                
$ip[$partindex] = str_repeat('0'strlen($part)) . $part;
            }
            
$ip implode(':'$ip);
        }
        
        
// If we should process a double colon, and there actually is one
        
if (!$no_doublecolon && strpos($ip'::') !== false) {
            
$replacement $no_leading ':0:' ':0000:';
            
            
// Create a string with the right number of :0:s or :0000:s by
            // counting the current number colons, then replace the double
            // colon with that.
            
$ip str_replace('::'str_repeat($replacementsubstr_count($ip':')), $ip);
            
            
// Remove excess colons created by the previous step.
            
$ip str_replace('::'':'$ip);
        }
        
        return 
$ip;
    }
    
    
/**
     * string collapse(string ip_address, bool force_v6 = false, no_leading = false, no_doublecolon = false)
     * Returns a shortened IPv6 address. Beware that prepending and appending a
     * double colon (::) is not possible since it is not possible to know
     * whether it should be pre- or postfixed. Given 0001:0002:0001, would you
     * prepend or append the double colon? (Hint: there is no right answer.)
     * For this reason, any such IPv6 address is seen as invalid.
     * 
     * Will return boolean false upon:
     *  A. Invalid in IPv6 address (note that it does not perform very extensive
     *     tests); or
     *  B. When $force_v6 is set to true AND it's an IPv4 address
     * If you pass an IPv4 address and force_v6 is false (the default),
     * the address is converted to IPv6 format automatically.
     * 
     * If $force_v6 is set to integer 2 (or anything other than a boolean),
     * AND an IPv4 address is passed, it will simply return the IPv4 formatted
     * address.
     * 
     * If $no_leading is set to true, it will only collapse the double colon in
     * the address, if present.
     * 
     * If $no_doublecolon is set to true, it will only collapse the leading
     * zeros and never add a double colon.
     **/
    /* TODO: Implement this
    public static function collapse($ip, $force_v6 = false, $no_leading = false, $no_doublecolon = false) {
        // ... so, did we actually get an IPv6 address or are they tricking us?
        if (!self::isv6($ip)) {
            if ($force_v6 === true) {
                return false; // Trickery! That'll teach 'em.
            }
            else if ($force_v6 === false) {
                $ip = self::v4tov6($ip);
            }
            else {
                return $ip;
            }
        }
        
        // Check validity.
        if (substr_count($ip, ':') != 7 && strpos($ip, '::') === false) {
            return false; // Invalid IPv6 address. Must contain seven parts or be expandable to that count.
        }
        
        if (!$no_leading) {
            
        }
    }*/
    
    /**
     * string v4tov6(string|int ipv4_address, bool visual_format = false)
     * Encapsulates an IPv4 address in IPv6 format. If $visual_format is set to
     * true, the address will just be converted to a technically valid format,
     * which is easier to read but I doubt whether all parsers will accept it.
     * See also: https://lucb1e.com/!ipv6#address_notation
     * 
     * Returns false when it's detected to be a valid IPv6 address or when an
     * integer of >2^32 is passed (or smaller than -2^31).
     * 
     * Examples:
     * v4tov6('192.168.123.123') = '::FFFF:C0A8:7B7B'
     * v4tov6('192.168.1.1', true) = '::FFFF:192.168.1.1'
     * v4tov6(ip2long('192.168.0.123')) = '::FFFF:C0A8:007B'
     **/
    
public static function v4tov6($ip$visual_format false) {
        if (
is_int($ip)) {
            if (
$ip pow(232) || $ip < -pow(231)) {
                return 
false;
            }
            
            
$ip long2ip($ip);
        }
        else {
            if (
self::isv6($ip)) {
                return 
false;
            }
        }
        
        if (
$visual_format) {
            return 
'::FFFF:' $ip;
        }
        
        
// Expand all pieces into an array
        
$ip explode('.'$ip);
        
        
// Then convert each to hexadecimal
        
foreach ($ip as $index=>$part) {
            
$ip[$index] = dechex($part);
        }
        
        
// Reassemble the IP address, 2 bytes at a time
        
$ip $ip[0] . $ip[1] . ':' $ip[2] . $ip[3];
        
        return 
'::FFFF:' $ip;
    }
    
    
/**
     * bool isv6(string ip_address)
     * Returns true if the given IP address is in IPv6 format.
     **/
    
public static function isv6($ip) {
        return 
strpos($ip':') !== false;
    }
    
    
// TODO: Documentation.
    
public static function chompInterface($ip) {
        return 
substr($ip0strpos($ip'%'));
    }
    
    
// TODO: Documentation.
    
public static function containsInterface($ip) {
        return 
strpos($ip'%') !== false;
    }
    
    
// TODO: Documentation.
    
public static function getInterface($ip) {
        return 
substr($ipstrpos($ip'%') + 1);
    }
}