<?php

    /* Webhook Example

       This webhook returns simplified member info for a particular user in JSON format. Either a member id or username must be provided as query parameter.
       Here's an example how a typical request and response will look like: 

       GET https://www.somesite.com/member_info.php?username=happyuser

       {"firstname":"Martin","username":"happyuser","email":"martin123@gmail.com","address":"","city":"Seoul","zip":"100-141","country":"KR","join_ip":"211.195.13.4","joined":"1565396069","sale_amount":"29.95"}

       This particular example implementation of the webhook uses the NATS REST API to obtain the member details (see: xvid_make_nats_api_call() function). 
       But any other method (like a direct database lookup) could be implemented instead too.

       Access to this webhook is limited by IP address to just the IPs of api.xvid.com servers.
       Further, requests to this webhook must be HMAC signed by the client_id/client_secret of the configured app (see below). Incorrectly signed requests are rejected.

       If you have more than one site (and so more than one NATS site id), create one MediaHub application and one separate webhook per site.
    */

    /* Config Section - please edit to match your setup! */

    $xvid_config = array();

    $xvid_config['app_client_id'] = ''; // <-- Add client_id / client_secret of your Xvid MediaHub application
    $xvid_config['app_client_secret'] = ''; // <-- If you have more than one site, put a copy of this file into each site and
    $xvid_config['nats_api_site_id'] = 1;   //     update site_id and client_id/client_secret per site accordingly
    $xvid_config['nats_api_version'] = 4;   // Your NATS API version

    $xvid_config['nats_api_base_url'] = 'https://crazycash.com/api'; // <-- Base URL of the NATS API on your NATS server
    $xvid_config['nats_api_key'] = ''; // <-- NATS API key
    $xvid_config['nats_api_user'] = ''; // <-- NATS API user

    $xvid_config['trusted_proxies'] = ['127.0.0.1']; // <-- IPs of reverse proxies who's FORWARDED_FOR header we can trust
    $xvid_config['proxy_header'] = 'HTTP_X_FORWARDED_FOR'; // <-- Name of the FORWARDED_FOR header that the trusted proxy will set

    /* End of Config Section */


    // Constants - please do not modify!

    define('API_DOMAIN', 'api.xvid.com');
    define('SIGNATURE_MAX_AGE', 7200); // 2 hrs
    define('HMAC_SHA1_SIGNATURE_LENGTH', 40);
    define('HMAC_SHA256_SIGNATURE_LENGTH', 64);


    // Parse query params

    $params = NULL;
    if (isset($_SERVER['QUERY_STRING'])) {
      parse_str($_SERVER['QUERY_STRING'], $params);
    }


    // Check for mandatory query params (timestamp, signature) - if not there, exit right away!
    // Check if timestamp is still fresh enough, validate signature string

    $expiry_time = time() - SIGNATURE_MAX_AGE;
    if (!$params || !isset($params['timestamp']) || !isset($params['signature']) || (intval($params['timestamp']) <= $expiry_time) ||
        (strlen($params['signature']) != HMAC_SHA1_SIGNATURE_LENGTH && strlen($params['signature']) != HMAC_SHA256_SIGNATURE_LENGTH) || !ctype_xdigit($params['signature'])) {
      xvid_error(400, NULL);
    }


    // Get Xvid API Server IPs that are allowed access

    $allowed_ips = xvid_cache_get(API_DOMAIN);
    if (!isset($allowed_ips)) {
      $allowed_ips = xvid_get_ips_for_domain_name(API_DOMAIN);
      xvid_cache_set(API_DOMAIN, $allowed_ips, 300);
    }


    // Check whether client IP is an allowed IP
    $ip_addr = xvid_get_client_ip($xvid_config['proxy_header'], $xvid_config['trusted_proxies']);

    $client_ip_allowed = FALSE;
    if (isset($ip_addr) && is_array($allowed_ips)) {
      foreach ($allowed_ips as $value) {
        if ($ip_addr === $value) {
          $client_ip_allowed = TRUE;
        }
      }
    }
    if (!$client_ip_allowed) {
      xvid_error(403, "Forbidden. Client IP " . $ip_addr . " not permitted!");
    }


    // Check whether client_id is valid and correct

    if (!isset($xvid_config['app_client_id']) || !isset($xvid_config['app_client_secret']) ||
        empty($xvid_config['app_client_id']) || empty($xvid_config['app_client_secret'])) {
      xvid_error(500, "Internal Error. No client_id or no client_secret configured!");
    }
    if ($xvid_config['app_client_id'] !== $params['client_id']) {
      xvid_error(400, "Bad Request. Client_id in callback request does not match client_id specified in config!");
    }


    // Verify URL signature

    if (!xvid_check_signature(substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], '&signature')), base64_decode($xvid_config['app_client_secret']), $params['signature'])) {
      xvid_error(403, "Forbidden. Url signature is invalid!");
    }


    // Call NATS API to get member details

    if (!array_key_exists('memberid', $params) && !array_key_exists('memberidx', $params) &&
        !array_key_exists('username', $params) && !array_key_exists('email', $params) && !array_key_exists('subscriptionid', $params)) {
      $params['username'] = $params['autograph_tag']; // Treat autograph_tag as username if no other NATS-specific identifier is specified
    }

    unset($params['client_id']);
    unset($params['timestamp']);
    unset($params['signature']);
    unset($params['autograph_tag']);
    $params['siteid'] = $xvid_config['nats_api_site_id'];

    $params['full_info'] = 1;
    if ($xvid_config['nats_api_version'] < 5) {
      $params['full_info'] = 'true';
    }
    if (!isset($params['status'])) {
      $params['status'] = 1; // only active accounts
    }

    $member_data = xvid_make_nats_api_call($xvid_config, http_build_query($params));


    // Select out only the essential member data needed for fraud detection and return as JSON

    if ($member_data !== FALSE && is_array($member_data)) {

      // Extract the user's firstname
      $firstname = trim($member_data['firstname']);
      if (strtoupper($firstname) == "MR" || strtoupper($firstname) == "MS") {
        $firstname .= ' ' . explode(" ", trim($member_data['lastname']))[0];
      }
      echo json_encode(array('firstname' => $firstname, 'username' => $member_data['username'], 'email' => $member_data['email'], 'address' => $member_data['address1'], 
                             'city' => $member_data['city'], 'zip' => $member_data['zip'], 'country' => $member_data['country'], 'join_ip' => $member_data['ip'],
                             'joined' => $member_data['joined'], 'sale_amount' => number_format(((intval($member_data['spent'])/max(1, intval($member_data['charges'])))/100),2,'.','')));
      exit();
    }

    // Still here? Error and exit.
    xvid_error(404, "Not found");


    // Common Error handler 

    function xvid_error($error_code, $error_message) {
      if (($error_code >= 400) && ($error_message != NULL)) {
        echo $error_message;
      }
      header('X-PHP-Response-Code: ' . $error_code, TRUE, $error_code);
      exit();
    }


    // Use PHP OP-Cache as in-memory key-value store

    // Set key-value pair
    function xvid_cache_set($key, $value, $ttl = NULL) {
      $dest = sys_get_temp_dir() . '/' . md5($key);
      $val = var_export(array('expiry' => $ttl ? time() + $ttl : FALSE,
                              'data' => $value), TRUE);

      // Write to temp file first to ensure atomicity
      $tmp = $dest . '.' . uniqid('', TRUE) . '.tmp';
      file_put_contents($tmp, '<?php $xvid_cache_val = ' . $val . '; ?>', LOCK_EX);

      rename($tmp, $dest);
      if (function_exists('opcache_invalidate')) {
        @opcache_invalidate($dest, TRUE);
      }
    }

    // Get value for key
    function xvid_cache_get($key) {
      $dest = sys_get_temp_dir() . '/' . md5($key);
      $xvid_cache_val = NULL;

      if((@include($dest)) !== FALSE) {
        // Not found
        if (!isset($xvid_cache_val)) return NULL;

        // Found and not expired
        if (!$xvid_cache_val['expiry'] || $xvid_cache_val['expiry'] > time()) return $xvid_cache_val['data'];

        // Expired, clean up
        if (function_exists('opcache_invalidate')) {
          @opcache_invalidate($dest, TRUE);
        }
        @unlink($dest);

      }

      return NULL;
    }


    // DNS Lookup A-record entry for domain

    function xvid_get_ips_for_domain_name($domain) {
      $ip_list = [];
      $dnsr = dns_get_record($domain, DNS_A);
      if (is_array($dnsr)) {
        foreach ($dnsr as $value) {
          array_push($ip_list, $value['ip']);
        }
      }
      return $ip_list;
    }


    // Determine the caller's IP address

    function xvid_get_client_ip($proxy_header, $trusted_proxies) {

      // Nothing to do without any reliable information
      if (!isset($_SERVER['REMOTE_ADDR'])) {
        return NULL;
      }

      if (in_array($_SERVER['REMOTE_ADDR'], $trusted_proxies)) {

        // Get the IP address of the client behind trusted proxy
        if (array_key_exists($proxy_header, $_SERVER)) {

          // Header can contain multiple IPs of proxies that are passed through.
          // Only the IP added by the last proxy (last IP in the list) can be trusted.
          $proxy_list = explode(",", $_SERVER[$proxy_header]);
          $client_ip = trim(end($proxy_list));

          // Validate just in case
          if (filter_var($client_ip, FILTER_VALIDATE_IP)) {
            return $client_ip;
          } else {
            // Validation failed - beat the guy who configured the proxy or
            // the guy who created the trusted proxy list?
          }
        }
      }

      // In all other cases, REMOTE_ADDR is the ONLY IP we can trust.
      return $_SERVER['REMOTE_ADDR'];
    }


    // Check whether signature is valid

    function xvid_check_signature($msg, $key, $signature) {
      $check_signature = '';
      if (strlen($signature) == HMAC_SHA1_SIGNATURE_LENGTH) {
          $check_signature = hash_hmac("sha1", $msg, $key); // TODO: Remove after deprecation
      } else if (strlen($signature) == HMAC_SHA256_SIGNATURE_LENGTH) {
          $check_signature = hash_hmac("sha256", $msg, $key);
      }
      return ((strlen($check_signature) > 0) && (strtolower($check_signature) === strtolower($signature)));
    }


    // Get member details from NATS REST API 

    function xvid_make_nats_api_call($xvid_config, $query_params) {
      $member_data = NULL;

      if (!empty($xvid_config['nats_api_base_url']) && !empty($xvid_config['nats_api_key']) && !empty($xvid_config['nats_api_user'])) {
        $url = $xvid_config['nats_api_base_url'] . '/member/details?' . $query_params;
        $headers = array(
          'api-key: ' . $xvid_config['nats_api_key'],
          'api-username: ' . $xvid_config['nats_api_user']
        );

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

        $result = curl_exec($ch);
        $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        if ($httpcode == 200) {
          $member_data = json_decode($result, true);
        } 
        curl_close($ch);
      }

      return $member_data;
    }

?>
