<commit_before><?php
/*--------------------------------------------------------+
| SYSTOPIA CiviProxy                                      |
|  a simple proxy solution for external access to CiviCRM |
| Copyright (C) 2019 SYSTOPIA                             |
| Author: <NAME> (endres -at- systopia.de)             |
| http://www.systopia.de/                                 |
+---------------------------------------------------------*/

require_once "config.php";
require_once "proxy.php";

// first check if webhooks are enabled
if (empty($webhook2api)) civiproxy_http_error("Feature disabled", 405);

// basic check
if (!civiproxy_security_check('webhook2api')) {
  civiproxy_http_error("Access denied", 403);
}

// find the right configuration
if (!empty($_REQUEST['id']) && isset($webhook2api['configurations'][$_REQUEST['id']])) {
  // we found the if in the configurations
  $configurations = [$webhook2api['configurations'][$_REQUEST['id']]];
} elseif (empty($_REQUEST['id']) && isset($webhook2api['configurations']['default'])) {
  // this is teh default configuration
  $configurations = [$webhook2api['configurations']['default']];
} else {
  // use all of them (first one matching is executed)
  $configurations = $webhook2api['configurations'];
}

// read some input
$post_input = @file_get_contents('php://input');

// MAIN: iterate through all (eligible) configurations
$last_error = ["No handler found", 501];
foreach ($configurations as $configuration) {
  $last_error = webhook2api_processConfiguration($configuration, $post_input);
  if ($last_error == NULL) {
    // success!
    break;
  }
}

// finally - if there was only errors, return the last one
if ($last_error) {
  civiproxy_http_error($last_error[0], $last_error[1]);
}




/**
 * Apply the given configuration. If it matches and executes,
 *  it returns NULL, otherwise
 *
 * @param $configuration array configuration/specification
 * @return null|array [status_code, error message]
 */
function webhook2api_processConfiguration($configuration, $post_input) {
  // check the IP/range restrictions
  if (!empty($configuration['ip_sources']) && is_array($configuration['ip_sources'])) {
    $ip = $_SERVER['REMOTE_ADDR'];
    $access_granted = FALSE;
    foreach ($configuration['ip_sources'] as $netmask) {
      // copied from https://secure.php.net/manual/de/ref.network.php
      list ($net, $mask) = explode("/", $netmask);
      $ip_net = ip2long ($net);
      $ip_mask = ~((1 << (32 - $mask)) - 1);
      $ip_ip = ip2long ($ip);
      $ip_ip_net = $ip_ip & $ip_mask;
      if ($ip_ip_net == $ip_net) {
        $access_granted = TRUE;
        break;
      }
    }
    if (!$access_granted) {
      // this configuration is not eligible
      return ["Access denied", 403];
    }
  }

  // gather source data
  $data = [];
  if (!empty($configuration['data_sources']) && is_array($configuration['data_sources'])) {
    foreach ($configuration['data_sources'] as $data_source) {
      switch ($data_source) {
        case 'POST/json': # JSON data in POST field
          $more_data = json_decode($post_input, TRUE);
          $data = array_merge_recursive($data, $more_data);
          break;
        case 'REQUEST': # simple request parameters
          $data = array_merge_recursive($data, $_REQUEST);
          break;
        default:
          civiproxy_log("Webhook2API[{$configuration['name']}]: unknown source '{$data_source}' in configuration. Ignored.");
      }
    }
  }

  // evaluate sentinels
  if (!empty($configuration['sentinel']) && is_array($configuration['sentinel'])) {
    foreach ($configuration['sentinel'] as $sentinel) {
      list($value_source, $check) = $sentinel;
      $value = webhook2api_getValue($data, $value_source);
      if (substr($check, 0, 6) == "equal:") {
        // check if terms a equal
        if (substr($check, 6) != $value) {
          return ["Access denied", 403];
        }
      } else {
        // unknown instruction
        civiproxy_log("Webhook2API[{$configuration['name']}]: don't understad sentinel '{$check}'. Ignored.");
      }
    }
  }

  // compile API query
  $params = [];
  if (!empty($configuration['parameter_mapping']) && is_array($configuration['parameter_mapping'])) {
    foreach ($configuration['parameter_mapping'] as $mapping) {
      $source_path = $mapping[0];
      $target_path = $mapping[1];
      $modifiers   = isset($mapping[2]) ? $mapping[2] : [];

      // get value
      $value = webhook2api_getValue($data, $source_path);

      // run modifiers
      foreach ($modifiers as $modifier) {
        // TODO:
      }

      // set to target
      webhook2api_setValue($params, $target_path, $value);
    }
  } else {
    $params = $data;
  }

  // sanitise data
  if (!empty($configuration['parameter_sanitation']) && is_array($configuration['parameter_sanitation'])) {
    // TODO: implement
  }

  // send to target REST API
  if (empty($configuration['entity']) || empty($configuration['action'])) {
    civiproxy_log("Webhook2API[{$configuration['name']}]: Missing entity/action.");
    return ["Configuration error", 403];
  }
  if (empty($configuration['api_key'])) {
    civiproxy_log("Webhook2API[{$configuration['name']}]: Missing api_key.");
    return ["Configuration error", 403];
  }
  $result = civicrm_api3($configuration['entity'], $configuration['action'], $params);

  // process result
  if (!empty($configuration['response_mapping']) && is_array($configuration['response_mapping'])) {
    // TODO: implement

  } else {
    // default behaviour:
    if (empty($result['is_error'])) {
      http_response_code(200);
    } else {
      if (!empty($result['http_code'])) {
        http_response_code($result['http_code']);
      } else {
        http_response_code(403);
      }
    }
  }

  // all done
  exit();
}

/**
 * Get the value from a multidimensional array,
 *  specified by the path
 *
 * @param $data array         multidimensional data array
 * @param $path array|string  path description
 * @return mixed value
 */<commit_msg>[#32] implementing webhook2api<commit_after><?php
/*--------------------------------------------------------+
| SYSTOPIA CiviProxy                                      |
|  a simple proxy solution for external access to CiviCRM |
| Copyright (C) 2019 SYSTOPIA                             |
| Author: <NAME> (endres -at- systopia.de)             |
| http://www.systopia.de/                                 |
+---------------------------------------------------------*/

require_once "config.php";
require_once "proxy.php";

// first check if webhooks are enabled
if (empty($webhook2api)) civiproxy_http_error("Feature disabled", 405);

// basic check
if (!civiproxy_security_check('webhook2api')) {
  civiproxy_http_error("Access denied", 403);
}

// find the right configuration
if (!empty($_REQUEST['id']) && isset($webhook2api['configurations'][$_REQUEST['id']])) {
  // we found the if in the configurations
  $configurations = [$webhook2api['configurations'][$_REQUEST['id']]];
} elseif (empty($_REQUEST['id']) && isset($webhook2api['configurations']['default'])) {
  // this is teh default configuration
  $configurations = [$webhook2api['configurations']['default']];
} else {
  // use all of them (first one matching is executed)
  $configurations = $webhook2api['configurations'];
}

// read some input
$post_input = @file_get_contents('php://input');

// MAIN: iterate through all (eligible) configurations
$last_error = ["No handler found", 501];
foreach ($configurations as $configuration) {
  $last_error = webhook2api_processConfiguration($configuration, $post_input);
  if ($last_error == NULL) {
    // success!
    break;
  }
}

// finally - if there was only errors, return the last one
if ($last_error) {
  civiproxy_http_error($last_error[0], $last_error[1]);
}




/**
 * Apply the given configuration. If it matches and executes,
 *  it returns NULL, otherwise
 *
 * @param $configuration array configuration/specification
 * @return null|array [status_code, error message]
 */
function webhook2api_processConfiguration($configuration, $post_input) {
  // check the IP/range restrictions
  if (!empty($configuration['ip_sources']) && is_array($configuration['ip_sources'])) {
    $ip = $_SERVER['REMOTE_ADDR'];
    $access_granted = FALSE;
    foreach ($configuration['ip_sources'] as $netmask) {
      // copied from https://secure.php.net/manual/de/ref.network.php
      list ($net, $mask) = explode("/", $netmask);
      $ip_net = ip2long ($net);
      $ip_mask = ~((1 << (32 - $mask)) - 1);
      $ip_ip = ip2long ($ip);
      $ip_ip_net = $ip_ip & $ip_mask;
      if ($ip_ip_net == $ip_net) {
        $access_granted = TRUE;
        break;
      }
    }
    if (!$access_granted) {
      // this configuration is not eligible
      return ["Access denied", 403];
    }
  }

  // gather source data
  $data = [];
  if (!empty($configuration['data_sources']) && is_array($configuration['data_sources'])) {
    foreach ($configuration['data_sources'] as $data_source) {
      switch ($data_source) {
        case 'POST/json': # JSON data in POST field
          $more_data = json_decode($post_input, TRUE);
          $data = array_merge_recursive($data, $more_data);
          break;
        case 'REQUEST': # simple request parameters
          $data = array_merge_recursive($data, $_REQUEST);
          break;
        default:
          civiproxy_log("Webhook2API[{$configuration['name']}]: unknown source '{$data_source}' in configuration. Ignored.");
      }
    }
  }

  // evaluate sentinels
  if (!empty($configuration['sentinel']) && is_array($configuration['sentinel'])) {
    foreach ($configuration['sentinel'] as $sentinel) {
      list($value_source, $check) = $sentinel;
      $value = webhook2api_getValue($data, $value_source);
      if (substr($check, 0, 6) == "equal:") {
        // check if terms a equal
        if (substr($check, 6) != $value) {
          return ["Access denied", 403];
        }
      } else {
        // unknown instruction
        civiproxy_log("Webhook2API[{$configuration['name']}]: don't understad sentinel '{$check}'. Ignored.");
      }
    }
  }

  // compile API query
  $params = [];
  if (!empty($configuration['parameter_mapping']) && is_array($configuration['parameter_mapping'])) {
    foreach ($configuration['parameter_mapping'] as $mapping) {
      $source_path = $mapping[0];
      $target_path = $mapping[1];
      $modifiers   = isset($mapping[2]) ? $mapping[2] : [];

      // get value
      $value = webhook2api_getValue($data, $source_path);

      // run modifiers
      foreach ($modifiers as $modifier) {
        // TODO:
      }

      // set to target
      webhook2api_setValue($params, $target_path, $value);
    }
  } else {
    $params = $data;
  }

  // sanitise data
  if (!empty($configuration['parameter_sanitation']) && is_array($configuration['parameter_sanitation'])) {
    // TODO: implement
  }

  // send to target REST API
  if (empty($configuration['entity']) || empty($configuration['action'])) {
    civiproxy_log("Webhook2API[{$configuration['name']}]: Missing entity/action.");
    return ["Configuration error", 403];
  }
  if (empty($configuration['api_key'])) {
    civiproxy_log("Webhook2API[{$configuration['name']}]: Missing api_key.");
    return ["Configuration error", 403];
  }
  $params['api_key'] = $configuration['api_key'];

  // run API call
  $result = civicrm_api3($configuration['entity'], $configuration['action'], $params);

  // process result
  if (!empty($configuration['response_mapping']) && is_array($configuration['response_mapping'])) {
    // TODO: implement

  } else {
    // default behaviour:
    if (empty($result['is_error'])) {
      http_response_code(200);
    } else {
      if (!empty($result['http_code'])) {
        http_response_code($result['http_code']);
      } else {
        http_response_code(403);
      }
    }
  }

  // all done
  exit();
}

/**
 * Get the value from a multidimensional array,
 *  specified by the path
 *
 * @param $data array         multidimensional data array
 * @param $path array|string  path description
 * @return mixed value
 */
{'fraction_non_alphanumeric': 0.17377346065195917, 'fraction_numerical': 0.010619031939413895, 'mean_word_length': 3.034872135503155, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 5, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1267108', 'n_tokens_mistral': 3696, 'n_tokens_neox': 3548, 'n_words': 827}