/**
  * Set an API statistic in the DB
  * 
  * @param int $code		The HTTP status code from the request or cache
  * @param string $uri	The dynamic call URL or the static call name
  * @param string $api	The API to set the stats for
  * 
  * @return bool True if the stat was added/updated successfully, or false if it wasn't.
  */
 public static function set_stat($code, $uri, $api = null, $is_static = null)
 {
     // Save queries if we don't need the stats.
     if (\Config::get('engine.track_usage_stats', false) === false) {
         return true;
     }
     $api_name = $api === null ? \V1\APIRequest::get('api') : $api;
     $is_static = $is_static === null ? \V1\APIRequest::is_static() : $is_static;
     $api = \V1\Model\APIs::get_api($api_name);
     // Do we have a stat entry for this timeframe?
     $existing_api_stats_obj = static::query()->where('apis_id', '=', $api['id'])->where('code', '=', (int) $code)->where('call', '=', $uri)->where('created_at', '>', time() - (int) \Config::get('api_stats_increment', 30) * 60)->get_one();
     // If we have a row, update it.
     if (!empty($existing_api_stats_obj)) {
         $existing_api_stats_obj->count++;
         return $existing_api_stats_obj->save();
     }
     // Add the new entry.
     $api_stats_object = new static();
     $api_stats_object->apis_id = $api['id'];
     $api_stats_object->code = $code;
     $api_stats_object->count = 1;
     $api_stats_object->call = $uri;
     $api_stats_object->is_static = intval($is_static);
     return $api_stats_object->save();
 }
 /**
  * Set the new credentials in the DB.
  * 
  * @param array $credentials	The array of new credentials for the API
  * @param string $api_name		The name of the API to set the credentials for or empty for the current API
  * 
  * @return bool True on success or false on fail
  */
 public static function set_credentials(array $credentials, $api_name = null)
 {
     // Specific API
     if (!empty($api_name)) {
         if (($api_id = \V1\Model\APIs::get_api_id($api_name, true)) === false) {
             return false;
         }
         $credentials_encrypted = \Crypt::encode(json_encode($credentials));
         \V1\Model\APIsMetaData::set_credentials($credentials_encrypted, $api_id);
         return true;
     }
     // Figure out what to set based on the current request
     if (\V1\APIRequest::is_static() === true) {
         $credentials_encrypted = \Crypt::encode(json_encode($credentials));
         return \V1\Model\APIsMetaData::set_credentials($credentials_encrypted);
     } else {
         $account_data = \V1\Model\Account::get_account();
         // Only store them their credentials if the account holder wants us to.
         if ($account_data['store_credentials'] === 1) {
             $credentials[\V1\APIRequest::get('api')] = $credentials;
             if (!empty($account_data['credentials'])) {
                 $account_data['credentials'] = json_decode(\Crypt::decode($account_data['credentials']), true);
                 $account_data['credentials'][\V1\APIRequest::get('api')] = $credentials[\V1\APIRequest::get('api')];
                 $credentials = $account_data['credentials'];
             }
             $credentials_encrypted = \Crypt::encode(json_encode($credentials));
             return \V1\Model\AccountsMetaData::set_credentials($account_data['id'], $credentials_encrypted);
         }
         return true;
     }
 }
 /**
  * Configure a static call
  * 
  * @return mixed The \V1\APICall object or an error array if the call wasn't found
  */
 public static function configure_call()
 {
     if (is_array($static_calls = \V1\RAML::parse_static_calls())) {
         $api_data = \V1\Model\APIs::get_api();
         $account_data = \V1\Model\Account::get_account();
         foreach ($static_calls as $call => $call_data) {
             // The static call data matches the request.
             if ('/' . \V1\APIRequest::get('static-call') === $call) {
                 // If we can't run the inactive call, then error out.
                 if (isset($call_data['stability']) && $call_data['stability'] === 0 && ($account_data['can_run_inactive'] === 0 || $account_data['id'] !== $api_data['account_id'])) {
                     return \Utility::format_error(403, \V1\Err::DISABLED_STATIC, \Lang::get('v1::errors.disabled_static'));
                 }
                 // Do we have a cached entry?
                 if (is_array($cached_data = \V1\Call\StaticCall::get_call_cache())) {
                     // Set their usage stats.
                     \V1\Usage::set_usage();
                     // Set the API provider stats
                     \V1\Usage::set_api_stats($cached_data['response']['status']);
                     // Return the response-formatted data from the cached entry.
                     return $cached_data;
                 }
                 // Try to get an APICall object
                 if (($apicall = static::apicall_object($call)) !== false) {
                     return $apicall;
                 }
             }
         }
         // The static call wasn't found.
         return \Utility::format_error(404, \V1\Err::BAD_STATIC, \Lang::get('v1::errors.bad_static'));
     } else {
         // If they've requested a static call when there aren't any for the requested API, give 'em errors!
         return \Utility::format_error(404, \V1\Err::NO_STATIC, \Lang::get('v1::errors.no_static'));
     }
 }
 /**
  * Configure a dynamic call
  *
  * @return mixed The \V1\APICall object or the error array on fail
  */
 public static function configure_call()
 {
     // Dynamic calls don't work through JS.
     if (\Session::get('public', true) === true) {
         return \Utility::format_error(400, \V1\Err::NO_JS_CALLS, \Lang::get('v1::errors.no_js_calls'));
     }
     // We need API configuration data.
     if (!is_array(\V1\APIRequest::get('configure', false))) {
         return \Utility::format_error(400, \V1\Err::MISSING_CONFIGURE, \Lang::get('v1::errors.missing_configure'));
     }
     // For some reason we can't parse the RAML, so we throw a 500 error.
     if (($api_def = \V1\RAML::parse()) === false) {
         return \Utility::format_error(500);
     } elseif (is_array($api_def)) {
         // Specific error
         return $api_def;
     }
     if (is_string($uri = \V1\APIRequest::get('configure.uri')) && is_string($method = \V1\APIRequest::get('configure.method'))) {
         $api_call = null;
         // Custom calls
         if (\V1\APIRequest::get('api') === 'custom') {
             if (is_string($url = \V1\APIRequest::get('configure.url')) && !empty($url)) {
                 $api_call = \V1\APICall::forge($api_def, $method, $uri, null, true, $url);
             }
             return \Utility::format_error(400, \V1\Err::NO_URL, \Lang::get('v1::errors.no_url'));
         }
         $api_data = \V1\Model\APIs::get_api();
         try {
             // Is it a valid resource?
             $api_def->getResourceByUri($uri)->getMethod($method);
             // We'll validate the call unless both the API provider and calling script deny that protection.
             $custom_dynamic = false;
             if ($api_data['force_validation'] === 0 && \V1\APIRequest::get('no-validate', false) === true) {
                 $custom_dynamic = true;
             }
             $api_call = \V1\APICall::forge($api_def, $method, $uri, null, $custom_dynamic);
         } catch (\Raml\Exception\BadParameter\ResourceNotFoundException $e) {
             // Does the API Provider allow for unconfigured static calls on their server?
             if ($api_data['allow_custom_dynamic'] === 1) {
                 $api_call = \V1\APICall::forge($api_def, $method, $uri, null, true);
             }
         }
         if (is_object($api_call)) {
             if (!empty($api_call->get_errors())) {
                 // Errors from \APICall
                 return $api_call->get_errors();
             } else {
                 // Return the \APICall object
                 return $api_call;
             }
         }
     }
     // Not found
     return \Utility::format_error(400, \V1\Err::BAD_DYNAMIC, \Lang::get('v1::errors.bad_dynamic'));
 }
 /**
  * Set the new credentials for the remote API access on the given API
  * 
  * @param string $credentials	The encrypted credentials to set
  * @param int $api_id			The ID of the API row in the DB
  * 
  * @return boolean True if it saved, or false if it didn't
  */
 public static function set_credentials($credentials, $api_id = null)
 {
     if (empty($api_id)) {
         $apis_data = \V1\Model\APIs::get_api();
         $api_id = (int) $apis_data['id'];
     }
     $apis_meta_obj = static::query()->where('apis_id', '=', $api_id)->where('key', '=', 'credentials')->get_one();
     if (empty($apis_meta_obj)) {
         $apis_meta_obj = new static();
         $apis_meta_obj->apis_id = $api_id;
         $apis_meta_obj->key = 'credentials';
         $apis_meta_obj->value = $credentials;
         return $apis_meta_obj->save();
     }
     $apis_meta_obj->value = $credentials;
     return $apis_meta_obj->save();
 }
 /**
  * Add the array of Bit API Hub headers for the call
  * 
  * @param array $headers The array of existing headers
  * @return array $headers with the Bit API Hub headers added on
  */
 public static function get_headers(array $headers)
 {
     $api = \V1\Model\APIs::get_api();
     $account = \V1\Model\Account::get_account();
     $forwarded_for = \Input::real_ip('0.0.0.0', true);
     if ($internal_call = \Utility::is_internal_call()) {
         $forwarded_for = \Config::get('engine.call_test_ip');
     }
     $headers = array_replace($headers, array('User-Agent' => 'API Optimization Engine/V1', 'X-Forwarded-For' => $forwarded_for));
     if (\Config::get('engine.send_engine_auth', false) === true) {
         // If the API hasn't yet received a secret identity, generate one.
         if (empty($api['secret'])) {
             $secret = \V1\Model\APIs::set_api_secret($api['id']);
         } else {
             $secret = \Crypt::decode($api['secret']);
         }
         $headers = array_replace($headers, array('X-AOE-Secret' => $secret, 'X-AOE-Account' => $account['id'], 'X-AOE-Version' => 'V1'));
     }
     return $headers;
 }
 /**
  * Check the speed limits of an API to make sure that the account isn't calling the API more than it's
  * allowed to in the specified time frame.
  * 
  * @return mixed True if the call is within the speed limits, or the error array if it isn't
  */
 private static function check_usage_limits()
 {
     $api_data = \V1\Model\APIs::get_api(\V1\APIRequest::get('api'));
     $account_data = \V1\Model\Account::get_account();
     $package_limits = \V1\RAML::parse_package_limits();
     /*
      * Are there any limits on the API for our package level? API limits are imposed
      * by Bit API Hub, not the API provider. It helps to limit the amount of bandwidth
      * we allot for each call. The heavier the call, the less calls people can make.
      */
     if (is_array($package_limits) && array_key_exists('level' . $account_data['access_level'], $package_limits) && !empty($usage = \V1\Usage::get_usage(\V1\APIRequest::get('api')))) {
         // Loop the allotments of API calls per time period
         foreach ($package_limits['level' . $account_data['access_level']] as $time_period => $allotment) {
             // If we have a valid log for the time period, and it's already maxed out, fail the check.
             if (isset($usage[$time_period]) && $usage[$time_period]['count'] == $allotment && \Utility::subtract_seconds($time_period) <= $usage[$time_period]['ts'] && $usage[$time_period]['ts'] <= time()) {
                 return \Utility::format_error(429, \V1\Err::TOO_MANY_REQUESTS, \Lang::get('v1::errors.too_many_requests', array('allotment' => number_format($allotment), 'period' => $time_period)));
             }
         }
     }
     // We're not breaking the speed limit, so shush.
     return true;
 }
 /**
  * Locate the RAML string for the current API
  * 
  * @return mixed The RAML string, or boolean false if we couldn't locate it.
  */
 protected static function get_raml_string()
 {
     $api_data = \V1\Model\APIs::get_api();
     return empty($api_data['raml']) ? false : $api_data['raml'];
 }
 /**
  * Prepare the response to show the customer. We also handle caching and usage.
  * 
  * @param array $response	The response array containing data from the remote server
  * @param string $api		The name of the API we're preparing the response for
  * 
  * @return array The response formatted string
  */
 public static function prepare_response(array $response, $api_request = null, $is_static = null)
 {
     $api_request = empty($api_request) ? \V1\APIRequest::get() : $api_request;
     $api = $api_request['api'];
     $is_static = $is_static === null ? \V1\APIRequest::is_static() : $is_static;
     $api_data = \V1\Model\APIs::get_api($api);
     if ($is_static === true && $api_data['account_id'] === 0) {
         $response['headers'] = array();
     } else {
         $response['headers'] = static::sanitize_headers($response['headers']);
     }
     $response['body'] = static::convert_body($response['headers'], $response['body']);
     $response_array = \Utility::format_response(200, $response);
     $internal_call = \Utility::is_internal_call();
     /*
      * Cache the static call response if it the remote server didn't report an error.
      * To allow for proper testing, we don't cache calls from the account area or other internal
      * locations.
      */
     if ($is_static === true && $response['status'] < 300 && $internal_call === false) {
         $api_call = $is_static === true ? $api_request['static-call'] : null;
         \V1\Call\StaticCall::set_call_cache($response_array, $api, $api_call);
     }
     /*
      * Log the usage stats if we aren't running a call from the internal API testing system. (Ex. The
      * account area)
      */
     if ($internal_call === false) {
         // Set the account usage stats
         \V1\Usage::set_usage($api);
         // Set the API provider stats
         \V1\Usage::set_api_stats($response['status'], $api_request, $is_static);
     }
     return $response_array;
 }