/**
  * 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'));
     }
 }
 /**
  * Check if the account has reached the maximum number of API calls it can make globally (not API specific)
  * 
  * @return boolean True if they're within their limits, false if not
  */
 protected static function check_global_limits()
 {
     $account_data = \V1\Model\Account::get_account();
     $total_calls = \V1\Usage::get_usage(false);
     // If they've used up all of their calls, and they aren't unlimited, then they've hit their global limits.
     if ($total_calls === $account_data['max_calls'] && $account_data['max_calls'] != 0) {
         return false;
     }
     return true;
 }
 /**
  * Get the ID for a given API name
  * 
  * @param string $api_name	The name of the API
  * @param bool $public_only	Set to true to exclude account owned APIs
  * 
  * @return mixed The API ID or false on fail
  */
 public static function get_api_id($api_name, $public_only = false)
 {
     // Select the chosen one.
     $api_data = static::query()->where('name', '=', $api_name)->and_where_open()->where('private', '=', 0);
     if ($public_only === false) {
         $account_data = \V1\Model\Account::get_account();
         $api_data->or_where_open()->where('private', '=', 1)->where('account_id', '=', $account_data['id'])->or_where_close();
     }
     $row = $api_data->and_where_close()->get_one();
     if (!empty($row['id'])) {
         return $row['id'];
     }
     return false;
 }
 /**
  * Get the row of Data Call data from the DB.
  *
  * @param string $data_call The name of the Data Call to pull data for or null to grab the posted Data Call
  * @return array The array of data for the Data Call from the DB, or an empty array
  */
 public static function get_data_call($data_call = null)
 {
     $data_call = empty($data_call) ? \V1\APIRequest::get('data-call') : $data_call;
     $account_data = \V1\Model\Account::get_account();
     // Select the chosen one.
     $data_call_data = static::query()->where('name', '=', $data_call)->and_where_open()->where('account_id', '=', 0)->or_where('account_id', '=', $account_data['id'])->and_where_close()->get_one();
     if (!empty($data_call_data)) {
         $return = array();
         // Turn the object into an array (You can't just cast it to an array unfortunately.)
         foreach ($data_call_data as $key => $value) {
             $return[$key] = $value;
         }
         return $return;
     } else {
         return array();
     }
 }
 /**
  * Delete all usage statistics, or the usage statistics for an API.
  * 
  * @param string $api The API to delete the usage statistics for, or leave it empty to remove everything
  */
 public static function delete_usage($api = null)
 {
     // Delete everything
     if (empty($api)) {
         \Cache::delete('account.' . \Session::get('consumer_key') . 'usage');
         /*
          * Remove the reset time from the DB so that we aren't always resetting it.
          * Free account don't pay (obviously), so we set their reset timer to 30 days
          * from now.
          */
         $account_data = \V1\Model\Account::get_account();
         $time = null;
         if ($account_data['access_level'] === 1) {
             $time = time() + (int) \Config::get('tiers.free.reset_period', 30) * 24 * 60 * 60;
         }
         \Debug::dump($time);
         \V1\Model\Account::set_reset_usage($time);
     } else {
         try {
             // If we have an entry for the API, then delete it.
             $usage = \Cache::get('account.' . \Session::get('consumer_key') . 'usage');
             if (isset($usage[$api])) {
                 unset($usage[$api]);
             }
             // Set it without the value for the chosen API.
             \Cache::set('account.' . \Session::get('consumer_key') . 'usage', $usage);
         } catch (\CacheNotFoundException $e) {
         }
     }
 }
 /**
  * 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;
 }
 /**
  * Validate the signature for the call
  * 
  * @param array $tokens The OAuth tokens from the header
  * @return boolean True if valid, false if invalid
  */
 protected static function valid_signature($tokens)
 {
     $mt = microtime(true);
     // Decode the signature, or fail
     if (($decoded_sig = urldecode(base64_decode($tokens['oauth_signature']))) === false) {
         return false;
     }
     // Grab the account data so we have a copy of the customer's secret key.
     $account_data = \V1\Model\Account::get_account($tokens['oauth_consumer_key']);
     // If the account is invalid, fail.
     if (empty($account_data)) {
         return false;
     }
     $secret = \Crypt::decode($account_data['consumer_secret']);
     // Reconstruct the data to build the signature.
     $oauth = array('oauth_nonce' => $tokens['oauth_nonce'], 'oauth_timestamp' => $tokens['oauth_timestamp'], 'oauth_consumer_key' => $tokens['oauth_consumer_key'], 'oauth_consumer_secret' => $secret, 'body' => urlencode(urlencode(base64_encode(json_encode(\V1\APIRequest::post_data())))));
     ksort($oauth);
     $oauth_encoded = array();
     foreach ($oauth as $key => $value) {
         $oauth_encoded[] = $key . '=' . $value;
     }
     // Now we have the string to make the hash
     $signed_string = urlencode(implode('&', $oauth_encoded));
     // Final product
     $hash = hash_hmac('sha256', $signed_string, $secret);
     // If they match, it's valid.
     return $hash === $decoded_sig;
 }
 /**
  * Parse the HTML data
  * 
  * @param array $template		The array of template data from the Data Call call script
  * @param array $api_responses	The array of responses we ran with the Data Call
  * @param bool $return_html		Set to true to return only HTML, not the array of parsed template data
  * 
  * @return mixed The array of parsed template data, or a string of HTML body contents if $return_html is true
  */
 protected static function process_template(array $template, array $api_responses, $return_html = false)
 {
     // Fill out our variables if we have some. (Kind of pointless not to.)
     if (!empty($template['variables'])) {
         $variables = array();
         foreach ($template['variables'] as $variable => $value_location) {
             $variables[$variable] = null;
             // Check if it resolves.
             if (($value = \Arr::get($api_responses, $value_location, 'BAH_NO_VALUE')) !== 'BAH_NO_VALUE') {
                 // Add the value to the variable so we can replace it.
                 if (is_numeric($value) || is_string($value) || is_null($value)) {
                     $variables[$variable] = (string) $value;
                 }
                 // Boolean doesn't like to get cast to a string properly.
                 if (is_bool($value)) {
                     $variables[$variable] = $value === false ? 'false' : 'true';
                 }
             }
         }
         // We don't return these.
         unset($template['variables']);
         // Replace the variables in the template.
         if (!empty($variables)) {
             $template = str_replace(array_keys($variables), array_values($variables), $template);
         }
     }
     $account_data = \V1\Model\Account::get_account();
     /**
      * Add our link back on free accounts, or account who wish to show a link back. While free accounts
      * could just decide not to show the body, they cannot configure their own Data Calls to place body
      * content in "css" or "js". Anyone can just preg_replace() away the linkback, but in general,
      * free accounts will show our link back.
      */
     if (!empty($template['body']) && ($account_data['access_level'] === 1 || $account_data['link_back'] === 1)) {
         $template['body'] .= \View::forge('linkback', array('color' => \V1\APIRequest::data_call('linkback-color', 'dark')), false)->render();
     }
     // We'll parse the template for them.
     if ($return_html === true) {
         $html = null;
         // CSS should be in <style> tags.
         $html .= !empty($template['css']) ? $template['css'] . '<body>' : null;
         // HTML body
         $html .= !empty($template['body']) ? $template['body'] : null;
         // JS comes at the end to keep things speedy.
         $html .= !empty($template['js']) ? $template['js'] : null;
         return $html;
     }
     // Send the template data sectioned out for use.
     return $template;
 }