function ProcessVirtualHostConf($VH, $VHPort, &$ReturnArray)
{
    //Find the ServerName
    $VHInfo = array('Port' => $VHPort);
    //Save for messages
    if (!preg_match('/^\\h*ServerName\\h+([-\\w\\.]+)\\h*$/iumD', $VH, $Matches)) {
        //Only allow the domain name on this line to guarantee we found a proper domain
        return OL('ServerName match not found for VirtualHost. The ServerName line may only contain the domain name' . "\n" . str_repeat('-', 80) . "\n{$VH}\n" . str_repeat('-', 80));
    }
    $VHInfo['AllDomains'] = array($ServerName = $VHInfo['ServerName'] = mb_strtolower($Matches[1]));
    //Set AllDomains and ServerName
    //Find the ServerAlias
    if (!preg_match('/^\\h*ServerAlias\\b/im', $VH)) {
        OL("Warning: ServerAlias not given for {$ServerName}:{$VHPort}", 'Warning');
    } else {
        if (!preg_match('/^\\h*ServerAlias\\h+((?:[-\\w\\.]+\\h+)*[-\\w\\.]+)\\h*$/iumD', $VH, $Matches)) {
            //Only allow domains on this apache config line
            return OL("ServerAlias is invalid for VirtualHost {$ServerName}:{$VHPort}. The ServerAlias line may only contain a whitespace separated list of domain names");
        } else {
            //Update AllDomains and set ServerAliases
            $VHInfo['AllDomains'] = array_merge($VHInfo['AllDomains'], $VHInfo['ServerAliases'] = preg_split('/\\h+/u', mb_strtolower($Matches[1])));
        }
    }
    //Find the Document Root
    if (!preg_match('/^\\h*DocumentRoot\\h+(\\S.*?)\\h*$/iumD', $VH, $Matches)) {
        return OL("DocumentRoot not found for VirtualHost {$ServerName}:{$VHPort}");
    }
    $VHInfo['DocumentRoot'] = $Matches[1];
    //Output the matches
    OL("Found {$ServerName}:{$VHPort} at {$VHInfo['DocumentRoot']}" . (!isset($VHInfo['ServerAliases']) ? '' : ' with aliases ' . implode(',', $VHInfo['ServerAliases'])), 'Success');
    //Add to the list of virtual host’s infos
    $ReturnArray[] = $VHInfo;
    return 0;
}
EndText;
//Get the last PHP error for user output
//This is required b/c PHP ~<5.3 does not allow direct array access from a function return
function GetLastError()
{
    $E = error_get_last();
    return $E['message'];
}
//Confirm PHP version
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
    return OL('Requires PHP>=5.2.4 for the \\h PREG character set; php>=5.3.0 for anonymous functions');
}
//Output the help if requested or required
global $IsCLI;
if ($IsCLI) {
    global $argc;
    require_once __DIR__ . '/CustomGetOpt.php';
}
if ($IsCLI && ($argc < 2 || count(cgetopt('h', array('help')))) || !$IsCLI && (isset($_REQUEST['Help']) || !count(array_merge($_GET, $_POST)))) {
    //Output the program description
    global $ConfigVars;
    OL(str_ireplace('?AllowConfigOverride?', $ConfigVars['AllowConfigOverride'] ? 'On' : 'Off', $ProgramDescription), 'Normal', false);
    //Output the parameters
    require_once __DIR__ . '/Parameters.php';
    foreach (GetParametersInfo($ConfigVars) as $PD) {
        OL('* ' . ($IsCLI ? "-{$PD['0']} --{$PD['1']}" : $PD[2]) . "{$PD['3']}:" . implode("\n    ", array_merge(array(''), !is_array($PD[4]) ? array($PD[4]) : $PD[4])), 'Normal', false);
    }
    //Nothing left to do after the help prompt
    return 1;
}
return 0;
function GetCertLoc($DocRoot, $LEReturn, &$CertDataPerDocRoot, $CertPathOverride, $Only1Cert)
{
    //Get the path to the “fullchain” file, which shares the path with the other certificates
    $FullChainPath = null;
    $ForDocRootStr = $Only1Cert ? '' : ' for ' . $DocRoot;
    //Add the document root to error messages
    $LEReturn = implode("\n", $LEReturn);
    if (!preg_match('/Congratulations.*?saved\\s+at\\s+(.*?)\\w+\\.pem\\s*\\.\\s+Your\\s+cert/ius', $LEReturn, $Matches)) {
        //Extract via a regex
        return OL("Cannot find path to certificate in return from letsencrypt{$ForDocRootStr} (They changed their return string):\n{$LEReturn}\n");
    }
    //If not found, throw error
    $FullChainPath = $Matches[1];
    //Transform the certificate path if parameter is given
    if (isset($CertPathOverride) && !($FullChainPath = @preg_replace($CertPathOverride[0], $CertPathOverride[1], $FullChainPath))) {
        //Transform occurs here
        return OL("Error while transforming certiciate path{$ForDocRootStr}: " . ($FullChainPath === NULL ? 'PREG Error: ' . GetLastError() : 'Result string is empty'));
    }
    //Load the certificates’ data
    $CertData = array();
    foreach (array('cert', 'privkey', 'chain') as $CertType) {
        if (!($CertData[$CertType] = cfile_get_contents("{$FullChainPath}{$CertType}.pem"))) {
            return OL("Cannot read {$CertType} certificate{$ForDocRootStr} at: {$FullChainPath}{$CertType}.pem : " . ($CertData[$CertType] === FALSE ? GetLastError() : 'File is empty'));
        }
    }
    $CertDataPerDocRoot[$DocRoot] = $CertData;
    return 0;
}
function CreateCerts(&$Config, $Params, $VirtualHostInfos)
{
    //Get the list of domains to ignore (Its keys are the domains)
    $IgnoreList = array();
    if (isset($Params['Ignore']) && strlen(trim($Params['Ignore']))) {
        $IgnoreList = preg_split('/\\h+/u', mb_strtolower(trim($Params['Ignore'])));
        $IgnoreList = array_combine($IgnoreList, array_fill(0, count($IgnoreList), 1));
    }
    //Combine domains into a list by the document root
    $PrimaryDomain = mb_strtolower($Params['Domain']);
    $PrimaryDomainDocumentRoot = null;
    //The primary domain (lowercase) and its document root
    $DocRoots = array();
    //Array(DOCROOT=>Array(DOMAIN1=>, DOMAIN2=>, ...))
    $IgnoreListCheck = $IgnoreList;
    //Domains are removed from this list as they are found. If any domains are not found, issue a warning
    foreach ($VirtualHostInfos as $VHI) {
        foreach ($VHI['AllDomains'] as $Domain) {
            if ($Domain == $PrimaryDomain) {
                //If primary domain, remember document root
                $PrimaryDomainDocumentRoot = $VHI['DocumentRoot'];
            }
            if (!isset($IgnoreList[$Domain])) {
                //If not on the ignore list, add to the DocRoots list to process
                $DocRoots[$VHI['DocumentRoot']][$Domain] = 1;
            } else {
                //If on the ignore list, mark as found
                unset($IgnoreListCheck[$Domain]);
            }
        }
    }
    if (count($IgnoreListCheck)) {
        //If any requested ignored domains are found, issue a warning
        OL('Warning: The following domains on the domain ignore list were not used: ' . implode(', ', array_keys($IgnoreListCheck)), 'Warning');
    }
    //Get the letsencrypt executable command and parameters
    $AllowConfigOverride = $Config['AllowConfigOverride'];
    $LetsEncryptCommand =& $Config['LetsEncryptCommand'];
    $LetsEncryptParams =& $Config['LetsEncryptParams'];
    if ($AllowConfigOverride && isset($Params['LECmd'])) {
        $LetsEncryptCommand = $Params['LECmd'];
    }
    if (!strlen(trim($LetsEncryptCommand))) {
        return OL('LetsEncryptCommand is empty');
    }
    if ($AllowConfigOverride && isset($Params['LEParams'])) {
        $LetsEncryptParams = $Params['LEParams'];
    }
    //Get the DistributionType
    $DistributionType =& $Config['DistributionType'];
    if (isset($Params['Distribution'])) {
        $DistributionType = $Params['Distribution'];
    }
    if (!preg_match('/^(?:GivenDomainOnly|AllInOne|SeparateVHosts)$/iD', $DistributionType)) {
        return OL('Invalid Distribution parameter: ' . $DistributionType);
    }
    $DistributionType = mb_strtolower($DistributionType);
    if (strcasecmp($DistributionType, 'GivenDomainOnly') == 0) {
        if (!isset($DocRoots[$PrimaryDomainDocumentRoot])) {
            return OL('Document root not found for primary vhost');
        }
        $DocRoots = array($PrimaryDomainDocumentRoot => $DocRoots[$PrimaryDomainDocumentRoot]);
        OL('Removed all doc roots except: ' . $PrimaryDomainDocumentRoot, 'Success');
    }
    //Gather the per-document-root params for the letsencrypt call
    $LEDocRootParams = array();
    if (!stripos($LetsEncryptCommand, '?Params?')) {
        return OL('The letsencrypt command (LECmd) requires “?Params?” to be somewhere within it');
    }
    foreach ($DocRoots as $DocRoot => $Domains) {
        //Confirm this document-root has domains
        if (!count($Domains)) {
            OL('Warning: No domains found for vhost-document-root-path: ' . $DocRoot, 'Warning');
            continue;
        }
        //Store the parameters for the docroot
        $CurDRParams = '-w ' . escapeshellarg($DocRoot);
        foreach ($Domains as $Domain => $Dummy) {
            $CurDRParams .= ' -d ' . escapeshellarg($Domain);
        }
        $LEDocRootParams[$DocRoot] = $CurDRParams;
    }
    if (!count($LEDocRootParams)) {
        return OL('No valid vhosts found! This probably mean that all found domains were set in the “ignore” parameter');
    }
    //Add the email parameter to the letsencrypt parameters
    $LetsEncryptParams .= ' --email ' . escapeshellarg($Params['Email']);
    //If DistributionType=AllInOne, combine all LEDocRootParams into 1 item
    if (strcasecmp($DistributionType, 'AllInOne') == 0) {
        $RemLEDocRootParams = $LEDocRootParams;
        $LEDocRootParams = array('AllDomains' => implode(' ', $LEDocRootParams));
        OL('Combined all doc roots into one', 'Success');
    }
    $Only1Cert = count($LEDocRootParams) == 1;
    //Execute each command and check their returns
    $LEResponses = array();
    foreach ($LEDocRootParams as $DocRoot => $DRParams) {
        //Execute the letsencrypt call
        $LECall = str_ireplace('?Params?', "{$LetsEncryptParams} {$DRParams}", $LetsEncryptCommand) . ' 2>&1';
        OL('Executing: ' . $LECall, 'Success');
        exec($LECall, $LEReturn, $LEStatusCode);
        //Check/store the response
        if ($LEStatusCode) {
            OL('Letsencrypt call failed' . ($Only1Cert ? '' : ' for ' . $DocRoot) . ':' . "\n" . implode("\n", $LEReturn));
        } else {
            $LEResponses[$DocRoot] = $LEReturn;
        }
        unset($LEReturn);
    }
    //If there was an error, exit here (if requested)
    if (count($LEResponses) != count($LEDocRootParams)) {
        //Can only continue if distribution type is SeparateVHosts
        if (strcasecmp($DistributionType, 'SeparateVHosts') != 0) {
            return OL('Required single domain not found');
        }
        //Determine if the user wants to exit early on an error
        $InstallAnyways =& $Config['InstallAnyways'];
        if (isset($Params['InstallAnyways'])) {
            $InstallAnyways = $Params['InstallAnyways'];
        }
        $InstallAnyways = preg_match('/^\\s*[ty1]/iD', $InstallAnyways);
        if (!$InstallAnyways) {
            return OL('Not all domains got a valid return (turn on InstallAnyways to bypass this error)');
        }
    }
    //If AllInOne, redistribute the certificate to all document roots
    if (strcasecmp($DistributionType, 'AllInOne') == 0) {
        $LEDocRootParams = $RemLEDocRootParams;
        foreach ($LEDocRootParams as $DocRoot => $_) {
            $LEResponses[$DocRoot] = $LEResponses['AllDomains'];
        }
        unset($LEResponses['AllDomains']);
    }
    //Output success
    OL('Certificate(s) successfully created', 'Success');
    return compact('LEResponses', 'LEDocRootParams');
}
    return OL('Domain not given. ' . ($IsCLI ? 'Add --help to see the help screen' : 'Add &help to see the help screen'));
}
if (!isset($Params['Email'])) {
    //LEEmailHelp (help information for the user’s email) is directly from the letsencrypt cli.py source
    $LEEmailHelp = 'Not using an email is strongly discouraged, because in the event of key loss or account compromise you will irrevocably lose access to your account. You will also be unable to receive notice about impending expiration or revocation of your certificates';
    return OL('Email is required. ' . $LEEmailHelp);
}
//Read the apache conf
require_once __DIR__ . '/ReadApacheConf.php';
$VirtualHostInfos = ReadApacheConf($ConfigVars, $Params);
if (!is_array($VirtualHostInfos)) {
    //If there was an error, exit here
    return $VirtualHostInfos;
}
//Create the certs
require_once __DIR__ . '/CreateCerts.php';
$FinalCertsInfo = CreateCerts($ConfigVars, $Params, $VirtualHostInfos);
if (!is_array($FinalCertsInfo)) {
    //If there was an error, exit here
    return $FinalCertsInfo;
}
//Install the certs
require_once __DIR__ . '/InstallCerts.php';
$Ret = InstallCerts($ConfigVars, $Params, $VirtualHostInfos, $FinalCertsInfo['LEResponses'], $FinalCertsInfo['LEDocRootParams']);
if ($Ret) {
    //If there was an error, exit here
    return $Ret;
}
//Script has completed
OL('Script has finished successfully', 'Complete');
return 0;