<commit_before><?php
/**
 * SimplyCast API PHP Wrapper. Usage instructions and examples are available
 * via the SimplyCast website.
 *
 * This version of the PHP wrapper differs from previous versions in that its
 * helper functions call to the new Contact Manager API, and not to the now
 * deprecated Contact List API. It is not compatible at all with existing
 * versions of the wrapper.
 *
 * The Contact Manager API allows for everything the previous Contact List API
 * did, plus support for metadata on contacts and direct manipulation of the 
 * contact database. Contact operations do not necessarily involve a list 
 * anymore.
 *
 * This wrapper uses the TLS basic authentication scheme for API auth by 
 * default.
 *
 * The PHP wrapper is only compatible with PHP version 5.3 and later.
 * It makes use of namespaces and the native JSON functions.
 * 
 * @package   SimplyCast
 * @author    SimplyCast <<EMAIL>>
 * @copyright 2012-2014 SimplyCast
 * @licence   MIT License (see included LICENCE file)
 */

namespace SimplyCast;

/**
 * SimplyCast API base class.
 *
 * @package SimplyCast
 * @author  SimplyCast <<EMAIL>>
 */
class API {
  /**
   * The API URL.
   * @var string
   */
  private $apiURL = 'api.simplycast.com';

  /**
   * Key pair storage.
   * @var array
   */
  private $keys;

  /**
   * If true, make all requests over SSL/TLS.
   * @var boolean
   */
  private $useTLS = true;

  /**
   * The type of auth scheme to use.
   * @var string
   */
  private $authScheme = 'basic';

  /**
   * The library to use for making HTTP requests. cURL by default, falls
   * back to sockets if cURL is unavailable.
   * @var string
   */
  private $lib = 'curl';

  /**
   * If true, throw an exception on error states.
   * @var boolean
   */
  private $exceptionOnError = true;

  /**
   * Store the last response.
   * @var array
   */
  private $lastResponse = array();

  /**
   * An array of API subresource handles, initialized as they're used.
   * @var array
   */
  private $apiHandles = array();

  /**
   * Constructor; accepts the public and secret keys for authentication.
   *
   * @param string $publicKey The public key (somewhat like a username).
   * @param string $secretKey The secret key (somewhat like a password).
   */
  public function __construct($publicKey, $secretKey) {
    $this->keys = array(
      'public' => $publicKey,
      'secret' => $secretKey,
    );
  }

  /**
   * Magic get. Loads API handles.
   *
   * @param string $var The handle to load.
   *
   * @return API handle.
   */
  public function __get($var) {
    $ovar = $var;
    $var = strtolower($var);

    $classes = '';
    foreach (array_reverse(get_declared_classes()) as $class) {
      if (is_subclass_of("\\$class", '\SimplyCast\APIResource')) {
        if (strtolower(str_replace('SimplyCast\\', '', $class)) == $var) {
          if (!array_key_exists($class, $this->apiHandles)) {
            $this->apiHandles[$class] = new $class($this);
          }
          return $this->apiHandles[$class];
        }

        $classes .= "\t- " . str_replace('SimplyCast\\', '', $class) . "\n";
      }
    }

    throw new \Exception("Resource class '$ovar' not found. Available classes are: \n$classes\n");
  }

  /**
   * Override the API url.
   * 
   * @param string $url The new API url.
   * 
   * @return void
   */
  public function setURL($url) {
    $this->apiURL = $url;
  }

  /**
   * Place a request to the API. This is pretty low level, but can be called
   *   directly.
   *
   * @param string $httpMethod      The HTTP method of the request (GET, POST, 
   *   DELETE).
   * @param string $resource        The resource that is going to be called.
   * @param array  $queryParameters A key / value array of query parameters 
   *   (will be urlencoded automatically.
   * @param string $data            The data to send along with the request. 
   *   This must be an array of request body data ready to be JSON encoded, 
   *   or a JSON string. Also, is only applicable for POST requests.
   *
   * @return array An array of response data, containing status & status code,
   *   the decoded response, headers and the raw response.
   */
  public function request($httpMethod, $resource, $queryParameters = array(), $data = false) {
    if ($this->authScheme == 'basic') {
      $authStr = 'Basic ' . base64_encode("{$this->keys['public']}:{$this->keys['secret']}");
    }

    $headers = array(
      "Connection: close",
      "Accept: application/json",
      "Authorization: $authStr",
    );

    if (is_array($queryParameters) && count($queryParameters) > 0) {
      $qStr = '?';
      foreach ($queryParameters as $k => $v) {
        $qStr .= urlencode($k) . '=' . urlencode($v) . '&';
      }
      $queryParameters = trim($qStr, '&');
    } else {
      $queryParameters = '';
    }
    
    if ($httpMethod == 'POST') {
      if (is_array($data)) {
        $data = json_encode($data);
      }

      $data = trim($data);

      if (strlen($data) > 0) {
        $headers[] = 'Content-Type: application/json';
        $headers[] = 'Content-Length: ' . strlen($data);
      }
    } else {
      $data = false;
    }

    $content = '';
    if ($this->lib == 'curl' && function_exists('curl_init')) {
      $content = $this->_curlRequest($httpMethod, $headers, $resource, $queryParameters, $data);
    } else {
      $content = $this->_fsockRequest($httpMethod, $headers, $resource, $queryParameters, $data);
    }

    $response = self::_decodeResponse($content);

    if ($this->exceptionOnError && $response['code'] >= 400) {
      if ($response['code'] >= 500) {
        throw new \Exception(
            "'{$response['code']} {$response['status']}' error encountered. This is a server level error; " . 
            "the API team will be notified and will resolve the issue as soon as possible."
        );
      } else {
        $errorMsg = (is_array($response['response']) && array_key_exists('error', $response['response'])) ? 
          $response['response']['error'] : '';
        throw new \Exception("'{$response['code']} {$response['status']}' error encountered: $errorMsg");
      }
    }

    $this->lastResponse = $response;

    if ($response['code'] == 204) {
      return null;
    } else if ($response['code'] < 400 && is_array($response['response'])) {
      return $response['response'];
    } else {  
      return false;
    }
  }

  /**
   * Internal function for making requests via cURL.
   *
   * @param string $httpMethod      HTTP method.
   * @param array  $headers         HTTP headers.
   * @param string $resource        Resource URI.
   * @param string $queryParameters Query parameter array.
   * @param string $data            POST request data
   *
   * @return string The raw response from the API.
   */
  private function _curlRequest($httpMethod, $headers, $resource, $queryParameters, $data) {
    $url = trim($this->apiURL, '/') . "/$resource$queryParameters";
    $url = ($this->useTLS ? 'https://' : 'http://') . preg_replace('|https?://|', '', $url);
    
    $ch = curl_init($url);
    curl_setopt_array(
        $ch, array(
          CURLOPT_CUSTOMREQUEST  => $httpMethod,
          CURLOPT_HTTPHEADER     => $headers,
          CURLOPT_HEADER         => true,
          CURLOPT_RETURNTRANSFER => true,
        )
    );

    if ($httpMethod == 'POST' && $data) {
      curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    }

    $response = curl_exec($ch);
    curl_close($ch);
    
    return $response;
  }

  /**
   * Internal function for making requests via a socket connection.
   *
   * @param string $httpMethod      HTTP method.
   * @param array  $headers         HTTP headers.
   * @param string $resource        Resource URI.
   * @param string $queryParameters Query parameter array.
   * @param string $data            POST request data
   *
   * @return string The raw response from the API.
   */
  private function _fsockRequest($httpMethod, $headers, $resource, $queryParameters, $data) {
    $apiURL = $this->apiURL;
    $base = '';
    if (strpos($apiURL, '/')) {
      list($host, $base) = explode('/', $apiURL, 2);
    }

    array_unshift($headers, "Host: {$host}");

    $port = 80;
    if ($this->useTLS) {
      $port = 443;
      $url = "tls://$host";
    } else {
      $url = "tcp://$host";
    }

    $resource = '/' . trim($base, '/') . '/' . trim($resource, '/') . $queryParameters;

    $request = "$httpMethod $resource HTTP/1.1\r\n" . implode("\r\n", $headers) . "\r\n\r\n$data"; 
    <commit_msg>_fsockRequest: host variable wasn't initialized unless the API URL had a slash in it.<commit_after><?php
/**
 * SimplyCast API PHP Wrapper. Usage instructions and examples are available
 * via the SimplyCast website.
 *
 * This version of the PHP wrapper differs from previous versions in that its
 * helper functions call to the new Contact Manager API, and not to the now
 * deprecated Contact List API. It is not compatible at all with existing
 * versions of the wrapper.
 *
 * The Contact Manager API allows for everything the previous Contact List API
 * did, plus support for metadata on contacts and direct manipulation of the 
 * contact database. Contact operations do not necessarily involve a list 
 * anymore.
 *
 * This wrapper uses the TLS basic authentication scheme for API auth by 
 * default.
 *
 * The PHP wrapper is only compatible with PHP version 5.3 and later.
 * It makes use of namespaces and the native JSON functions.
 * 
 * @package   SimplyCast
 * @author    SimplyCast <<EMAIL>>
 * @copyright 2012-2014 SimplyCast
 * @licence   MIT License (see included LICENCE file)
 */

namespace SimplyCast;

/**
 * SimplyCast API base class.
 *
 * @package SimplyCast
 * @author  SimplyCast <<EMAIL>>
 */
class API {
  /**
   * The API URL.
   * @var string
   */
  private $apiURL = 'api.simplycast.com';

  /**
   * Key pair storage.
   * @var array
   */
  private $keys;

  /**
   * If true, make all requests over SSL/TLS.
   * @var boolean
   */
  private $useTLS = true;

  /**
   * The type of auth scheme to use.
   * @var string
   */
  private $authScheme = 'basic';

  /**
   * The library to use for making HTTP requests. cURL by default, falls
   * back to sockets if cURL is unavailable.
   * @var string
   */
  private $lib = 'curl';

  /**
   * If true, throw an exception on error states.
   * @var boolean
   */
  private $exceptionOnError = true;

  /**
   * Store the last response.
   * @var array
   */
  private $lastResponse = array();

  /**
   * An array of API subresource handles, initialized as they're used.
   * @var array
   */
  private $apiHandles = array();

  /**
   * Constructor; accepts the public and secret keys for authentication.
   *
   * @param string $publicKey The public key (somewhat like a username).
   * @param string $secretKey The secret key (somewhat like a password).
   */
  public function __construct($publicKey, $secretKey) {
    $this->keys = array(
      'public' => $publicKey,
      'secret' => $secretKey,
    );
  }

  /**
   * Magic get. Loads API handles.
   *
   * @param string $var The handle to load.
   *
   * @return API handle.
   */
  public function __get($var) {
    $ovar = $var;
    $var = strtolower($var);

    $classes = '';
    foreach (array_reverse(get_declared_classes()) as $class) {
      if (is_subclass_of("\\$class", '\SimplyCast\APIResource')) {
        if (strtolower(str_replace('SimplyCast\\', '', $class)) == $var) {
          if (!array_key_exists($class, $this->apiHandles)) {
            $this->apiHandles[$class] = new $class($this);
          }
          return $this->apiHandles[$class];
        }

        $classes .= "\t- " . str_replace('SimplyCast\\', '', $class) . "\n";
      }
    }

    throw new \Exception("Resource class '$ovar' not found. Available classes are: \n$classes\n");
  }

  /**
   * Override the API url.
   * 
   * @param string $url The new API url.
   * 
   * @return void
   */
  public function setURL($url) {
    $this->apiURL = $url;
  }

  /**
   * Place a request to the API. This is pretty low level, but can be called
   *   directly.
   *
   * @param string $httpMethod      The HTTP method of the request (GET, POST, 
   *   DELETE).
   * @param string $resource        The resource that is going to be called.
   * @param array  $queryParameters A key / value array of query parameters 
   *   (will be urlencoded automatically.
   * @param string $data            The data to send along with the request. 
   *   This must be an array of request body data ready to be JSON encoded, 
   *   or a JSON string. Also, is only applicable for POST requests.
   *
   * @return array An array of response data, containing status & status code,
   *   the decoded response, headers and the raw response.
   */
  public function request($httpMethod, $resource, $queryParameters = array(), $data = false) {
    if ($this->authScheme == 'basic') {
      $authStr = 'Basic ' . base64_encode("{$this->keys['public']}:{$this->keys['secret']}");
    }

    $headers = array(
      "Connection: close",
      "Accept: application/json",
      "Authorization: $authStr",
    );

    if (is_array($queryParameters) && count($queryParameters) > 0) {
      $qStr = '?';
      foreach ($queryParameters as $k => $v) {
        $qStr .= urlencode($k) . '=' . urlencode($v) . '&';
      }
      $queryParameters = trim($qStr, '&');
    } else {
      $queryParameters = '';
    }
    
    if ($httpMethod == 'POST') {
      if (is_array($data)) {
        $data = json_encode($data);
      }

      $data = trim($data);

      if (strlen($data) > 0) {
        $headers[] = 'Content-Type: application/json';
        $headers[] = 'Content-Length: ' . strlen($data);
      }
    } else {
      $data = false;
    }

    $content = '';
    if ($this->lib == 'curl' && function_exists('curl_init')) {
      $content = $this->_curlRequest($httpMethod, $headers, $resource, $queryParameters, $data);
    } else {
      $content = $this->_fsockRequest($httpMethod, $headers, $resource, $queryParameters, $data);
    }

    $response = self::_decodeResponse($content);

    if ($this->exceptionOnError && $response['code'] >= 400) {
      if ($response['code'] >= 500) {
        throw new \Exception(
            "'{$response['code']} {$response['status']}' error encountered. This is a server level error; " . 
            "the API team will be notified and will resolve the issue as soon as possible."
        );
      } else {
        $errorMsg = (is_array($response['response']) && array_key_exists('error', $response['response'])) ? 
          $response['response']['error'] : '';
        throw new \Exception("'{$response['code']} {$response['status']}' error encountered: $errorMsg");
      }
    }

    $this->lastResponse = $response;

    if ($response['code'] == 204) {
      return null;
    } else if ($response['code'] < 400 && is_array($response['response'])) {
      return $response['response'];
    } else {  
      return false;
    }
  }

  /**
   * Internal function for making requests via cURL.
   *
   * @param string $httpMethod      HTTP method.
   * @param array  $headers         HTTP headers.
   * @param string $resource        Resource URI.
   * @param string $queryParameters Query parameter array.
   * @param string $data            POST request data
   *
   * @return string The raw response from the API.
   */
  private function _curlRequest($httpMethod, $headers, $resource, $queryParameters, $data) {
    $url = trim($this->apiURL, '/') . "/$resource$queryParameters";
    $url = ($this->useTLS ? 'https://' : 'http://') . preg_replace('|https?://|', '', $url);
    
    $ch = curl_init($url);
    curl_setopt_array(
        $ch, array(
          CURLOPT_CUSTOMREQUEST  => $httpMethod,
          CURLOPT_HTTPHEADER     => $headers,
          CURLOPT_HEADER         => true,
          CURLOPT_RETURNTRANSFER => true,
        )
    );

    if ($httpMethod == 'POST' && $data) {
      curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    }

    $response = curl_exec($ch);
    curl_close($ch);
    
    return $response;
  }

  /**
   * Internal function for making requests via a socket connection.
   *
   * @param string $httpMethod      HTTP method.
   * @param array  $headers         HTTP headers.
   * @param string $resource        Resource URI.
   * @param string $queryParameters Query parameter array.
   * @param string $data            POST request data
   *
   * @return string The raw response from the API.
   */
  private function _fsockRequest($httpMethod, $headers, $resource, $queryParameters, $data) {
    $host = $apiURL = $this->apiURL;
    $base = '';
    if (strpos($apiURL, '/')) {
      list($host, $base) = explode('/', $apiURL, 2);
    }

    array_unshift($headers, "Host: {$host}");

    $port = 80;
    if ($this->useTLS) {
      $port = 443;
      $url = "tls://$host";
    } else {
      $url = "tcp://$host";
    }

    $resource = '/' . trim($base, '/') . '/' . trim($resource, '/') . $queryParameters;

    $request = "$httpMethod $resource HTTP/1.1\r\n" . implode("\r\n", $headers) . "\r\n\r\n$data"; 
    
{'fraction_non_alphanumeric': 0.14404998248277473, 'fraction_numerical': 0.003970571061543852, 'mean_word_length': 2.5540568582693504, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 67, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7344756', 'n_tokens_mistral': 5274, 'n_tokens_neox': 5093, 'n_words': 1600}