<?php
/* Changelog:
 *
 * 13.12.2018  - Added title for Schema 1.1
 */
require_once('eid_config.inc.php');

define (EID_VERSION, '1_1');

// --------------------------- [ PUBLIC INTERFACE ] ---------------------------

/*
  Parameters:
  ----------- 
  pstRequest is an array that can contain any of the following members 
  forming the request. 

  IdentityData    [A]         An array containing one or more parameters
                              of the person to be checked.
     FirstName    [S]         First name
     LastName     [S]         Last name
     DateOfBirth  [T]         Birthdate
     Street       [S]         Street
     ZipCode      [S]         ZIP code
     Town         [S]         Town
     Country      [S]         ISO country code of country
	 IBAN         [S]         IBAN
     MinAge       [I]         Minimum age to be verified
     MaxAge       [I]         Maximum age to be verified
	 Title		  [S]		  Person's title
  MsgId           [S 1..10]   Contains a unique message identifier for the
                              transaction. If not provided, it will be
                              assigned and returned on the function return arr.
                              You can create a unique MsgId by calling 
                              EID_NewMsgId()
  ReturnUrl       [S]     [D] The URL where to redirect the user to
  ConfirmationUrl [S]     [D] The URL where to send a confirmation to
  

  S...String, needs to be UTF8-encoded (use utf8_encode() if necessary)
  I...Integer
  A...Array
  T...Date in the format YYYY-MM-DD
  D...If not set, default from configuration file is taken

  Returns:
  --------
  array(
    'MsgId'            =>  MsgId
    'StatusReference'  =>  Status reference to be used in EID_Status()
    'CreDtTm'          =>  CreDtTm to be used in EMS_Status()
    'RedirectURL'      =>  On success, URL to redirect to
    'ResponseMessage'  =>  On failure, error description
    'ResponseCode'     =>  100 on success, otherwiser error code
  )
*/
function EID_Initiate($pstRequest)
{
    global $eid_cfg, $EID_ret;

    $EID_ret=array();
    $xmldata = EID_Header('IdentityServiceInitiationRequest');
    $EID_ret['MsgId']=$pstRequest['MsgId']?$pstRequest['MsgId']:EID_NewMsgId();
    $MsgId = sprintf("%'X-25s%s", $eid_cfg['UserID'], $EID_ret['MsgId']);
    $EID_ret['CreDtTm'] = gmdate('Y-m-d\TH:i:s\Z');  // gmdate('c') not supported in PHP 4
    foreach (array('ConfirmationUrl', 'ReturnUrl') as $a) 
        $$a=$pstRequest[$a]?$pstRequest[$a]:$eid_cfg[$a];
	$HashStr=$eid_cfg['PIN'].$MsgId.$EID_ret['CreDtTm'].$ReturnUrl.$ConfirmationUrl;
	foreach (array('FirstName', 'LastName', 'DateOfBirth', 'Street', 'ZipCode', 'Town', 'Country', 'IBAN', 'MinAge', 'MaxAge', 'Title') as $a)
		$HashStr.=$pstRequest['IdentityData'][$a];
	$HashStr.=$eid_cfg['UserID'];
    $SHA256Fingerprint=EID_SHA256(utf8_encode($HashStr));

    $xmldata .= EID_MsgHeader('eIdentity', 'MsgHeader', $MsgId, $EID_ret['CreDtTm'])."
	<eIdentity:MerchantData>
		<eIdentity:ReturnUrl>".htmlentities($ReturnUrl)."</eIdentity:ReturnUrl>
		<eIdentity:ConfirmationUrl>".htmlentities($ConfirmationUrl)."</eIdentity:ConfirmationUrl>
		<eIdentity:MerchantName>Panzerknacker AG</eIdentity:MerchantName>
	</eIdentity:MerchantData>
	<eIdentity:IdentityData>";
	foreach ($pstRequest['IdentityData'] as $k => $v) if ($v) $xmldata .= "<eIdentity:$k>$v</eIdentity:$k>\n";
	$xmldata .= '</eIdentity:IdentityData>'.EID_AuthDetails($SHA256Fingerprint)."</eIdentity:IdentityServiceInitiationRequest>\n";

//DEBUG
//	fwrite($h=fopen("test.xml", "w"), $xmldata);fclose($h); die("XML-Packet output was written to test.xml, halted.");

	return EID_ParseReply(EID_SendRequest($xmldata, 'transinit'), 'EID_characterData');
}

// ----------------------------------------------------------------------------

/*
  EID_Status checks the status of a transaction.

  Parameters:
  -----------
  pstRequest is basically the array you receive as result from
  EID_Init, it has to contain the following members:

  array(
    'MsgId'          =>  MsgId from EID_Initiate()
    'StatusRefrence' =>  Status reference from EID_Initiate()
    'CreDtTm'        =>  CreDtTm from EID_Initiate()
  )

  Returns:
  --------
  See EID_ParseReply return values
*/
function EID_Status($pstRequest)
{
    global $eid_cfg, $EID_ret;

    $EID_ret=array();
    $MsgId = sprintf("%'X-25s%s", $eid_cfg['UserID'], $pstRequest['MsgId']);
    $SHA256Fingerprint = EID_SHA256(utf8_encode($eid_cfg['PIN'].$MsgId.$pstRequest['CreDtTm'].
        $pstRequest['StatusReference'].$eid_cfg['UserID']));
    $xmldata = EID_Header('IdentityServiceStatusRequest').
        EID_MsgHeader('eIdentity', 'MsgHeader', $MsgId, $pstRequest['CreDtTm']).
        '<eIdentity:StatusReference>'.$pstRequest['StatusReference'].'</eIdentity:StatusReference>'.
        EID_AuthDetails($SHA256Fingerprint).'</eIdentity:IdentityServiceStatusRequest>';

	return EID_ParseReply(EID_SendRequest($xmldata, 'statusrequest'));
}

// ----------------------------------------------------------------------------

/*
  EID_ParseReply parses a confirmation message into an array which is easy
  to process.

  Parameters:
  -----------
  $reply  - Reply you received in confirmation which should be processed

  Returns:
  --------
  array(
    'MsgId'               [S] =>  MsgId from header
    'CreDtTm'             [S] =>  CreDtTm from header
    'ResponseMessage'     [S] =>  On execution failure, error description
    'ResponseCode'        [I] =>  100 on success, otherwise error code
    'VerificationDetails' [A] =>  Array with keys of IdentityData from request whose
                                  members are set to OK or NOK depending on verif. status
                                  Key names are all UPPERCASE due to XML parser
  )
*/
function EID_ParseReply($reply, $datahandler='EID_characterDataStatus')
{
    global $currentTag, $EID_ret, $bInVerifDetails;

	unset($currentTag);
	unset($bInVerifDetails);
	$xml_parser = xml_parser_create('UTF-8');
    xml_set_element_handler($xml_parser, 'EID_startElement', 'EID_endElement');
    xml_set_character_data_handler($xml_parser, $datahandler);

	if (!xml_parse($xml_parser, $reply))
	    $EID_ret['ResponseMessage']=sprintf("XML error: %s in line %d",xml_error_string(xml_get_error_code($xml_parser)),xml_get_current_line_number($xml_parser));
	xml_parser_free($xml_parser);

	return $EID_ret;
}

// ----------------------------------------------------------------------------

/*
  EID_Confirmation returns the confirmation message you have to send back when
  receiving a confirmation. This message has to be parsed with EID_ParseReply()
  first and the return value of this functions needs to be passed as a parameter

  Parameters:
  -----------
  $reply  - Return value you received from EID_ParseReply() of confirmation

  Returns:
  --------
  XML reply you have to send back as a reply to confirmation
*/
function EID_Confirmation($reply)
{
    $xmldata = EID_Header('IdentityServiceConfirmationResponse');
    $xmldata .= EID_MsgHeader('eIdentity', 'MsgHeader', $reply['MsgId'], $reply['CreDtTm'])."
    <eIdentity:ResponseStatus><eIdentity:ResponseCode>".sprintf('%03d', $reply['ResponseCode'])."</eIdentity:ResponseCode>";
	if ($reply['ResponseMessage']) $xmldata.="<eIdentity:ResponseMessage>".$reply['ResponseMessage']."</eIdentity:ResponseMessage>";
    $xmldata.="</eIdentity:ResponseStatus></eIdentity:IdentityServiceConfirmationResponse>";
	return $xmldata;
}

// ----------------------------------------------------------------------------

/* 
   This is an example for creating a relatively unique new message ID
   with 10 chars.
   Of course you can use your own functions instead.
 */
function EID_NewMsgId()
{
   return sprintf('%02X%08X',rand(0,255),time());
}

// --------------------------- [ STATIC FUNCTIONS ] ---------------------------

function EID_PutLog($text)
{
  global $eid_cfg;

  if ($eid_cfg['Logfile']) {
    fputs(($fp=fopen($eid_cfg['Logfile'],"a")),strftime("um: %d.%m.%Y %H:%M:%S",time()).": $text\n-------------------------------------------------------------------------------\n");
    fclose($fp);
  }
}

function EID_Header($Tag)
{
	switch (EID_VERSION)
	{
		case '1_0': $year='2014'; break;
		default:
		case '1_1': $year='2017'; break;
	}
    $ret = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>".
    "<eIdentity:$Tag xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" ".
    "xmlns:eIdentity=\"http://www.stuzza.at/namespaces/eIdentity/$year\" ";
    return $ret.
    "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ".
    "xsi:schemaLocation=\"http://www.stuzza.at/namespaces/eIdentity/$year eIdentityService_v".str_replace('_', '.', EID_VERSION).".xsd\">";
}

function EID_MsgHeader($NS, $Tag, $MsgId, $CreDtTm)
{
    return "<$NS:$Tag><$NS:MsgId>$MsgId</$NS:MsgId><$NS:CreDtTm>$CreDtTm</$NS:CreDtTm></$NS:$Tag>";
}

function EID_AuthDetails($SHA256Fingerprint)
{
    global $eid_cfg;

    return "<eIdentity:AuthenticationDetails><eIdentity:UserId>".$eid_cfg['UserID']."</eIdentity:UserId>".
        "<eIdentity:SHA256Fingerprint>$SHA256Fingerprint</eIdentity:SHA256Fingerprint></eIdentity:AuthenticationDetails>";
}

function EID_SHA256($str)
{
	EID_PutLog($str);

    if (version_compare(PHP_VERSION,'5.1.2','>='))
        return hash('sha256', $str);
    if (function_exists('mhash') && defined('MHASH_SHA256'))
        return base64_encode(bin2hex(mhash(MHASH_SHA256, $str)));
    if (function_exists('sha256'))
        return sha256($str);
    else 
    {
        include ('ems_php4_sha256.inc.php');
        return strtoupper($hexStr);
    }
}

function EID_SendRequest($xmldata, $reqtyp)
{
    global $eid_cfg;

    $Url='https://routing.e-identity.at/appl/epsSO'.($eid_cfg['System']?'-'.$eid_cfg['System']:'').'/'.$reqtyp.'/eid/v'.EID_VERSION;
    EID_PutLog("(Request to $Url) Request packet:\n$xmldata");

    // Setup CURL
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $Url);
    curl_setopt($ch, CURLOPT_POST,1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, utf8_encode($xmldata));
    curl_setopt($ch, CURLOPT_TIMEOUT, 60);
    curl_setopt($ch, CURLOPT_HEADER,0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: text/xml"));

    // Abfrage durchfuehren
    $result=curl_exec ($ch);
    EID_PutLog("(Request to EID-Server) Response:\n$result");
    curl_close ($ch);
    return $result;
}

function EID_startElement($parser, $name, $attrs)
{
    global $currentTag, $bInVerifDetails;
    $currentTag = $name;
	
    list($prefix, $tag)=explode(":", $name, 2);
	if (strtoupper($tag) == 'VERIFICATIONDETAILS')
		$bInVerifDetails=true;
}

function EID_endElement($parser, $name)
{
    global $currentTag, $bInVerifDetails;
    $currentTag = "";

    list($prefix, $tag)=explode(":", $name, 2);
	if (strtoupper($name) == 'VERIFICATIONDETAILS')
		$bInVerifDetails=false;
}

function EID_characterData($parser, $data)
{
    global $currentTag, $EID_ret;

    list($prefix, $tag)=explode(":", $currentTag, 2);
    if (!$tag) $tag=$currentTag;
    switch(strtoupper($tag))
    {
        case "RESPONSECODE": $EID_ret['ResponseCode']=$data; break;
        case "RESPONSEMESSAGE": $EID_ret['ResponseMessage'].=$data; break;
        case "STATUSREFERENCE": $EID_ret['StatusReference']=$data; break;
        case "REDIRECTURL": $EID_ret['RedirectURL'].=html_entity_decode($data); break;

    }
}

function EID_characterDataStatus($parser, $data)
{
	global $currentTag, $EID_ret, $bInVerifDetails;

	list($prefix, $tag)=explode(":", $currentTag, 2);
	if (!$tag) $tag=$currentTag;
	if ($bInVerifDetails) $EID_ret['VerificationDetails'][$tag]=$data;
    else switch(strtoupper($tag))
    {
        case "MSGID": $EID_ret['MsgId']=$data; break;
        case "CREDTTM": $EID_ret['CreDtTm']=$data; break;
        case "RESPONSECODE": $EID_ret['ResponseCode']=$data; break;
        case "RESPONSEMESSAGE": $EID_ret['ResponseMessage'].=$data; break;
    }
}
