<?php

	$help = "Available parameters:
-f: (required) name of file containing domains to transfer
-d: (optional) how to handle DNS zones; allowed values:
    0: (default) do not create DNS zones in Openprovider; use existing
       nameservers
    1: create DNS zones in Openprovider based on Versio DNS records;
       put the domain on the Openprovider nameservers after transfer;
       existing zones will not be overwritten
    overwrite: create DNS zones, overwrite any existing zones
-v: (optional) whether or not to show extended information during runtime;
    allowed values:
    0: (default) don't show additional ouput
    1: show additional ouput
-t: (optional) whether to run in test mode or not; allowed values:
    0: run live
    1: (default) run in test mode / read-only; no contacts or zones will
       be created, no domains will be transferred

Examples:
  Run in test mode, full debugging output:
    php transfer-from-versio.php -f domains.txt -v1 -t1

  Run in live mode: transfer domains while create DNS zones (but do not
  overwrite existing ones), show limited output:
    php transfer-from-versio.php -f domains.txt -t0 -d1

Full documentation is available at https://support.openprovider.eu/hc/en-us/articles/360012057280
";

	// API credentials Versio and Openprovider
	$authVersio = array('username' => '', 'password' => '');
	$authOp     = array('username' => '', 'hash' => '');

	// Optional array with extensions that cannot be locked; feel free to update this array
	$extensionsWithoutLock = array(
		'nl',
		'eu',
		'uk',
		'co.uk',
		'de',
	);

	require_once('API.php');
	$api = new OP_API ('https://api.openprovider.eu');

	// Read runtime parameters
	$opts = getopt('f:d:v:t:', array('help'));
	$domainFile      = $opts['f']; // filename (required)
	$withDns         = $opts['d']; // 0, 1 or overwrite (default = 0)
	$verbose         = $opts['v']; // 0 or 1 (default = 0)
	$testMode        = $opts['t']; // 0 or 1 (default = 1)

	if (isset($opts['help'])) {
		echo $help;
		exit;
	}


	// Get list with domains to transfer
	if (file_exists($domainFile)) {
		$domains = file($domainFile, FILE_IGNORE_NEW_LINES);
	}
	else {
		die("Cannot find domain list; file [$f] does not exist\n");
	}

	// Check other parameters
	if (!in_array($withDns, array(0, 1, 'overwrite'))) {
		$withDns = 0;
	}
	if (!in_array($testMode, array(0, 1))) {
		$testMode = 1;
	}
	if (!in_array($verbose, array(0, 1))) {
		$verbose = 0;
	}

	// Keep track of handles that were already created
	$handleRelations = array();
	if (file_exists('versio-handle-relations.dat')) {
		if ($f = fopen('versio-handle-relations.dat', 'r')) {
			while (!feof($f)) {
				list($versioHandle, $opHandle) = fgetcsv($f);
				$handleRelations[$versioHandle] = $opHandle;
			}
			fclose($f);
		}
		else {
			die("Cannot open file versio-handle-relations.dat\n");
		}
	}
	$fHandles = fopen('versio-handle-relations.dat', 'a');

	foreach ($domains as $dom) {
		$continue = true;

		if ($verbose) {
			echo "Starting domain $dom\n";
			echo "Retrieve data for domain $dom from Versio API\n";
		}
		if ($domain = sendApiCommand('domains/'.$dom.'?show_epp_code=true&show_dns_records=true')) {
			if ($verbose) {
				echo "Versio returned the following data for domain $dom:\n";
				print_r($domain);
			}
		}
		else {
			// Error message already thrown by sendApiCommand
			continue;
		}
		$domain = $domain->domainInfo;

		$args = array();
		
		// Nameservers
		if ($verbose) {
			echo "Preparing nameservers for domain [$dom]\n";
		}
		if ($withDns) {
			if (empty($domain->dns_records)) {
				echo "@@ERROR: no DNS entries found for import in Openprovider for $dom; skipping this domain\n";
				continue;
			}
			if (createOpenproviderZone($dom, $domain->dns_records)) {
				$args['nsGroup'] = 'dns-openprovider';
			}
			else {
				echo "@@ERROR: while creating DNS zone for $dom; skipping this domain\n";
				continue;
			}
		}
		else {
			if (isset($domain->ns)) {
				foreach ($domain->ns as $ns) {
					$args['nameServers'][] = array(
						'name' => trim($ns->ns),
						'ip'   => (isset($ns->nsip) ? $ns->nsip : NULL),
					);
				}
			}
		}

		// Contacts (handles)
		if ($verbose) {
			echo "Preparing contact handles for domain [$dom]\n";
		}
		$versioHandle = $domain->registrant_id;
		if (isset($handleRelations[$versioHandle])) {
			$opHandle = $handleRelations[$versioHandle];
		}
		else {
			if ($opHandle = createOpenproviderContact($versioHandle)) {
				if (!$testMode) {
					fwrite($fHandles, $versioHandle.','.$opHandle."\n");
					$handleRelations[$versioHandle] = $opHandle;
				}
			}
			else {
				echo "@@ERROR while creating Openprovider contact for $type of $dom\n";
				$continue = false;
				continue;
			}
		}
		$args['ownerHandle'] = $opHandle;
		$args['adminHandle'] = $opHandle;
		$args['techHandle']  = $opHandle;

		// Authcode
		$args['authCode'] = html_entity_decode(stripslashes($domain->epp_code));

		// Unlock domain
		// - as for the fact that a 'Get Domain' does not return lock info, just force an unlock command.
		// - as for the API rate limits, hard-code some extensions that do not allow for a lock
		list($d, $e) = explode('.', $dom, 2);
		if (in_array($e, $extensionsWithoutLock)) {
			if ($verbose) {
				echo "Skip unlock command for [$dom]\n";
			}
		}
		else {
			if ($verbose) {
				echo "Unlocking domain [$dom] (if applicable)\n";
			}
			if (!isset($domain->lock) || $domain->lock) {
				if ($testMode) {
					echo "TEST MODE: skipping unlocking domain\n";
				}
				else {
					sendApiCommand('domains/'.$dom.'/update', array('lock' => false));
				}
			}
		}

		$args['domain'] = array(
			'name' => $d,
			'extension' => $e,
		);
		$args['period'] = 1;

		if ($verbose) {
			echo "Start transfer for domain [$dom] in Openprovider with the following data array:\n";
			print_r($args);
		}
		if ($testMode) {
			echo "TEST MODE: skip domain transfer for [$dom]\n";
		}
		else {
			$request = new OP_Request;
			$request->setCommand('transferDomainRequest')
				->setAuth($authOp)
				->setArgs($args);
			$reply = $api->process($request);
			if ($reply->getFaultCode() != 0) {
				echo "@@ERROR on domain transfer [$dom]: ".$reply->getFaultCode().' - '.$reply->getFaultString()."\n";
				if ($verbose) {
					echo "Full data array:\n";
					print_r($args);
					print_r($reply);
				}
			}
			else {
				echo "Transfer for $dom successfully requested\n";
			}
		}
	}
	
	fclose($fHandles);
	
	function sendApiCommand($command, $postData = NULL) {
		global $authVersio;
		
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($curl, CURLOPT_TIMEOUT, 5);
		curl_setopt($curl, CURLOPT_URL, 'https://www.versio.nl/api/v1/'.$command);
		curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
		curl_setopt($curl, CURLOPT_USERPWD, $authVersio['username'].':'.$authVersio['password']);
		if ($postData) {
			curl_setopt($curl, CURLOPT_POST, true);
			curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($postData));
    }
		$res = curl_exec($curl);
		$status_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
		curl_close($curl);
		sleep(2); // Versio has rate limiting, unsure about details; sleep() may help a little bit, but even with sleep(10) I got error after ±50 requests. Need to be figured out
		if ($status_code == 200) {
			return json_decode($res);
		}
		else {
			echo "@@ERROR processing command $command: $res\n";
			return false;
		}
	}
	
	function createOpenproviderZone($domain, $dnsEntries) {
		global $api, $authOp;
		global $testMode, $withDns, $verbose;

		list($d, $e) = explode('.', $domain, 2);

		// Check if a zone already exists
		$request = new OP_Request;
		$request->setCommand('retrieveZoneDnsRequest')
			->setAuth($authOp)
			->setArgs(array(
				'name' => $domain,
				'withRecords' => false,
				'withHistory' => false
			));
		$reply = $api->process($request);
		$response = $reply->getValue();
		if ($response) {
			if ($withDns == 'overwrite') {
				if ($testMode) {
					echo "@@WARNING: TEST MODE: Zone for [$domain] already exists, skip removing of existing zone\n";
				}
				else {
					echo "@@WARNING: Zone for [$domain] already exists; removing existing zone\n";

					$request = new OP_Request;
					$request->setCommand('deleteZoneDnsRequest')
						->setAuth($authOp)
						->setArgs(array(
							'domain' => array(
								'name' => $d,
								'extension' => $e
							),
						));
					$reply = $api->process($request);
					$response = $reply->getValue();
				}
			}
			else {
				echo "@@WARNING: Zone for [$domain] already exists; skipping import\n";
			}
		}

		$records = array();
		foreach ($dnsEntries as $record) {
			if (in_array($record->type, array('NS', 'SOA'))) {
				continue;
			}

			$records[] = array(
				'type'  => $record->type,
				'name'  => $record->name,
				'value' => $record->value,
				'prio'  => $record->prio,
				'ttl'   => ($record->ttl < 600 ? 600 : $record->ttl),
			);
		}

		if ($testMode) {
			echo "TEST MODE - skip DNS zone creation for $domain\n";
			if ($verbose) {
				echo "Zone contents:\n";
				print_r($records);
			}
			return true;
		}
		else {
			$request = new OP_Request;
			$request->setCommand('createZoneDnsRequest')
				->setAuth($authOp)
				->setArgs(array(
					'domain' => array(
						'name' => $d,
						'extension' => $e,
					),
					'type' => 'master',
					'records' => $records,
				));
			$reply = $api->process($request);
			if ($reply->getFaultCode() != 0) {
				echo "@@ERROR on DNS zone creation for [$domain]: ".$reply->getFaultCode().' - '.$reply->getFaultString()."; full data array:\n";
				print_r($records);
				return false;
			}
			else {
				if ($verbose) {
					echo "DNS zone for $domain successfully created with the following records:\n";
					print_r($records);
				}
				return true;
			}
		}
	}
	
	function createOpenproviderContact($versioHandle) {
		global $api, $authOp;
		global $testMode, $verbose;

		$contact = sendApiCommand('contacts/'.$versioHandle);
		$contact = $contact->contactInfo;
		// TODO - this does not yet work
		print_r($contact);

		// Split telephone number
		// We assume that a telephone number without a country code is a Dutch number
		$matches = array();
		if (preg_match('/^0([1-9].*)$/', $contact->phone, $matches)) {
			$tel1 = '+31';
			$rest = preg_replace('/[^\d]/', '', $matches[1]);
		}
		else if (preg_match('/^([1-9].*)$/', $contact->phone, $matches)) {
			$tel1 = '+31';
			$rest = preg_replace('/[^\d]/', '', $matches[1]);
		}
		// Else consider first 4 numbers the country code
		else {
			$tel = preg_replace('/[^\d]/', '', $contact->phone);
			$tel1 = '+'.substr($tel, 2, 2);
			$rest = substr($tel, 2);
		}
		$tel2 = substr($rest, 0, 2);
    $tel3 = substr($rest, 2);

		$args = array(
			'companyName' => $contact->company,
			'name' => array(
				'firstName' => $contact->firstname,
				'lastName' => $contact->surname,
			),
			'phone' => array(
				'countryCode' => $tel1,
				'areaCode' => $tel2,
				'subscriberNumber' => $tel3,
			),
			'address' => array(
				 'street' => $contact->street,
				 'number' => $contact->number,
				 'suffix' => $contact->number_addition,
				 'zipcode' => $contact->zipcode,
				 'city' => $contact->city,
				 'country' => strtoupper($contact->country),
			),
			'email' => $contact->email,
		);

		if ($testMode) {
			echo "TEST MODE - skip handle creation\n";
			if ($verbose) {
				echo "Handle details are the following:\n";
				print_r($args);
			}
			return true;
		}
		else {
			$request = new OP_Request;
			$request->setCommand('createCustomerRequest')
				->setAuth($authOp)
				->setArgs($args);
			$reply = $api->process($request);
			if ($reply->getFaultCode() != 0) {
				echo "@@ERROR on contact creation for [$domain]: ".$reply->getFaultCode().' - '.$reply->getFaultString()."; full data array:\n";
				print_r($args);
				return false;
			}
			else {
				$response = $reply->getValue();
				if ($verbose) {
					echo "Successfully created handle ".$response['handle']."\n";
				}
				return $response['handle'];
			}
		}
	}

?>