Support for every method and parameter. Every single * method and parameter in the API is exposed in some way or another * in Stack.PHP. There is no need to ever manually specify a URL to * achieve a certain result. * - Built-in OAuth consumer. You can access authenticated methods in * the API easily with Stack.PHP's Auth class. Both explicit (server-side) * and implicit (client-side) authentication are supported. * - Built-in request throttling. Never worry about the speed at * which requests are dispatched anymore. Stack.PHP will delay your * requests to ensure that they comply with the published guidelines. * - Automatic pagination. Fetching multiple pages of content with * the API can be tricky. Stack.PHP takes all of the confusion and guesswork * out of pagination by providing you with an iterator-like object that * will take care of fetching the pages for you. * - A powerful and extensible caching system. It doesn't make sense to * hit the API servers with the same request over and over again. Stack.PHP * offers a flexible set of caching classes that make it extremely simple to * cache requests. Both an SQL and a filestore caching class are included with * Stack.PHP - and if they don't suit your needs, you can easily create your * own caching class. * * \section starting Getting Started * You can find a terrific introduction to basic usage of Stack.PHP here: * https://docs.google.com/document/pub?id=11n_dp6t2jpPcgqNuoEOO0fwLvKxYp_HH-zGoycAnUmY */ require_once 'api_exception.php'; require_once 'filter.php'; require_once 'inbox_item_request.php'; require_once 'paged_response.php'; require_once 'paged_site_response.php'; require_once 'site.php'; require_once 'url.php'; /// Main class which provides access to all sites and routes. /** * This class provides static methods for storing and retrieving * information needed for requests as well as means for creating * instances of the Site class which is used for all other entry * points. */ if( !class_exists( 'API' ) ): class API { //==================== // Public settings //==================== /// The API key to use for making requests. /** * This value is not strictly required as the API allows up to 300 requests * per day without an API key. However, for any production quailty application, * you are highly encouraged to register for one as it increases your daily quota * to 10000 requests per day. You also require an API key for authenticated requests. */ public static $key = ''; /// The TTL value to use when caching requests (in seconds). /** * This value determines the duration of time that requests will be cached for when * a cache has been set with API::SetCache(). The default is 10 minutes. Note that * certain requests will override this value when necessary to comply with the API's * best usage policy. */ public static $cache_ttl = 600; //================================== // Private settings used internally //================================== // Whether to enable verbose mode (used for debugging) private static $debug = FALSE; // The current version of the API private static $api_version = '2.0'; // We want it to be treated as a string // Request throttling variables private static $request_timeout = 0.033333333333; // 1 / 30 private static $last_request = 0; // Statistical variables private static $total_requests = 0; // the number of requests issued by request classes private static $requests_to_api = 0; // the number of requests actually made to the API // Cache variables private static $cache = null; /// Enables / disables the debug mode. /** * \param $enable_debug TRUE to enable debug mode * * Enabling the debug mode will produce a lot of extra output. This will simply * be sent to STDOUT for a CLI-based application and will be echo'd to * the client when used in a web application. If the latter behavior is not desired, * you can use PHP's output buffering methods to capture the output instead. * * Debug mode also enables additional information in the response by changing one * of the default filters that are used. */ public static function EnableDebugMode($enable_debug=TRUE) { self::$debug = $enable_debug; // Enable the debug filter Filter::$default_filter = Filter::$default_pagination_filter = '!DnBO_'; } /// Returns the version of the API that Stack.PHP is using. /** * \return the current API version */ public static function GetVersion() { return self::$api_version; } /// Fetches the raw response from the provided URL. /** * \param $url the URL to retrieve * \param $post_data an array of data to POST * \return raw data from the URL * * All HTTP requests to the API are routed through this method which * ensures that the proper encoding header is sent (this also informs * curl that we may be expecting compressed data in return). This method * also accepts an array of POST values that are converted to a properly * encoded string. */ public static function GetRawData($url, $post_data=null) { // Initialize curl $ch = curl_init(); // Set the options curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_ENCODING, 'gzip'); // Required by API curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // If POST data was supplied, add it now if($post_data !== null) { // Generate the POST string $post_array = array(); foreach($post_data as $key => $value) $post_array[] = $key . '=' . urlencode($value); curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $post_array)); } // Get the data returned by the method and close our handle $data = curl_exec($ch); // Make sure the returned data is not FALSE, indicating an error if($data === FALSE) { // Grab the error and close curl $error_desc = curl_error($ch); curl_close($ch); throw new APIException('Curl was unable to retrieve the data from the specified URL.', $url, $error_desc); } // Close the handle and return the data we retrieved curl_close($ch); return $data; } /// Fetches JSON data from the provided URL. /** * \param $url an instance of the URL class containing the URL to retrieve * \return JSON-decoded data from the URL * * This method retrieves data from an API route and decodes the JSON returned. * Invoking this method does not necessarily make an API call if a cache has been * set with API::SetCache(). Parameters that do not affect the data returned are * removed and the resulting URL is looked up in the cache. If it exists, the data * is returned immediately using the cached response. * * This method also ensures that the request rate-limit is adhered to. This method * will sleep just long enough to ensure that the request does not exceed the rate-limit. */ public static function GetJSON($url) { // Increment the request count regardless of whether we use the cache or not ++self::$total_requests; // First check the cache (if available) for this request, dropping // the API key so that we don't make extra requests with different keys. if(self::$cache !== null && $data = self::$cache->RetrieveFromCache($url->CompleteURL(FALSE))) { if(self::$debug) echo "[DEBUG]: Found '" . $url->CompleteURL() . "' in cache.\n"; // Unserialize the data and return it return unserialize($data); } // Log the request if in debug mode if(self::$debug) echo "[DEBUG]: Issuing request '" . $url->CompleteURL() . "'\n"; // It's not in the cache, so we are making a request and the request-to-api // count should be incremented ++self::$requests_to_api; // Calculate the elapsed time since the last request $elapsed_time = microtime(TRUE) - self::$last_request; // If not enough time has elapsed to comply with rate-limit, then wait until it has if($elapsed_time < self::$request_timeout) { $sleep_duration = self::$request_timeout - $elapsed_time; // If debugging is enabled, indicate how long we are pausing for if(self::$debug) printf("[DEBUG]: Pausing for %.3f seconds...\n", $sleep_duration); usleep($sleep_duration); } // Record the time that this request was made - we need to record this // before any inspection of the data takes place because the rate-limit // still applies to errors, etc. self::$last_request = microtime(TRUE); // Retrieve the data $data = self::GetRawData($url->CompleteURL(), $url->GetPOSTData()); // Try to decode the data $data = json_decode($data, TRUE); if($data === null) throw new APIException('There was an error decoding the JSON data returned by the API.', $url); // Check for the error contents if(isset($data['error_id'])) throw new APIException('An API error has occurred.', $url, $data['error_message'], $data['error_id']); // The data is valid, so add it to the cache if applicable using the default TTL if(self::$cache !== null && $url->CanCache()) self::$cache->AddToCache($url->CompleteURL(FALSE), serialize($data), self::$cache_ttl); // return the JSON data return $data; } /// Sets the internal cache to the provided instance of a cache class. /** * \param $cache_instance an instance of a class derived from CacheBase * \param $cleanup whether to perform cleanup of expired entries in the cache */ public static function SetCache($cache_instance, $cleanup=TRUE) { self::$cache = $cache_instance; // If requested, perform necessary cleanup if($cleanup) self::$cache->Cleanup(); } /// Returns the user accounts that correspond to the specified account IDs. /** * \param $ids either a single account ID or an array of account IDs * \return a PagedResponse object */ public static function AssociatedUsers($ids) { $url = new URL(); $url->SetCategory('users'); $url->AddID($ids); $url->SetMethod('associated'); return new PagedResponse($url, 'network_user'); } // De-authenticates the current application for the specified access tokens. /** * \param $access_token either a single access token or an array of access tokens * \return a PagedRequest object */ public static function DeauthenticateApplication($access_token) { $url = new URL(); $url->SetCategory('apps'); $url->AddID($access_token); $url->SetMethod('de-authenticate'); return new PagedResponse($url, 'access_token'); } /// Returns the inbox items for the specified access token. /** * \param $access_token a valid access token * \return an InboxItemRequest object */ public static function Inbox($access_token) { return new InboxItemRequest(new URL(), $access_token); } /// Invalidates the supplied access tokens. /** * \param $access_token either a single access token or an array of access tokens * \return a PagedRequest object */ public static function InvalidateAccessTokens($access_token) { $url = new URL(); $url->SetCategory('access-tokens'); $url->AddID($access_token); $url->SetMethod('invalidate'); return new PagedResponse($url, 'access_token'); } /// Retrieves information about the specified access tokens. /** * \param $access_token either a single access token or an array of access tokens * \return a PagedRequest object */ public static function ReadAccessTokens($access_token) { $url = new URL(); $url->SetCategory('access-tokens'); $url->AddID($access_token); return new PagedResponse($url, 'access_token'); } /// Creates a Site object for the provided site. /** * \param $site_domain a portion of the site's domain name * \return a Site object */ public static function Site($site_domain) { return new Site($site_domain); } /// Returns a paged response of all Stack Exchange sites. /** * \return a PagedSiteResponse object */ public static function Sites() { $url = new URL(); $url->SetMethod('sites'); return new PagedSiteResponse($url); } /// Returns the total number of requests made for API data. /** * \return the total number of requests * * This number technically represents the total number of times that * API::GetJSON() has been called. If no cache has been set, this number * is equal to the number of API requests actually made. */ public static function GetTotalRequests() { return self::$total_requests; } /// Returns the number of requests actually sent to the API. /** * \return the number of requests sent to the API * * This number represents the number of requests actually sent to * the API servers. In the absence of a cache, this number is identical * to API::GetTotalRequests(). */ public static function GetAPIRequests() { return self::$requests_to_api; } } endif;