public static function request($method, $url, $attributes = array(), $options = array()) { if (!self::$ch) { throw new Exception('Client has not been setup with client id and client secret.'); } // Reset attributes so we can reuse curl object curl_setopt(self::$ch, CURLOPT_POSTFIELDS, null); unset(self::$headers['Content-length']); $original_url = $url; $encoded_attributes = null; if (is_object($attributes) && substr(get_class($attributes), 0, 5) == 'Podio') { $attributes = $attributes->as_json(false); } if (!is_array($attributes) && !is_object($attributes)) { throw new PodioDataIntegrityError('Attributes must be an array'); } switch ($method) { case self::GET: curl_setopt(self::$ch, CURLOPT_CUSTOMREQUEST, self::GET); self::$headers['Content-type'] = 'application/x-www-form-urlencoded'; $separator = strpos($url, '?') ? '&' : '?'; if ($attributes) { $query = self::encode_attributes($attributes); $url = $url . $separator . $query; } self::$headers['Content-length'] = "0"; break; case self::DELETE: curl_setopt(self::$ch, CURLOPT_CUSTOMREQUEST, self::DELETE); self::$headers['Content-type'] = 'application/x-www-form-urlencoded'; $separator = strpos($url, '?') ? '&' : '?'; if ($attributes) { $query = self::encode_attributes($attributes); $url = $url . $separator . $query; } self::$headers['Content-length'] = "0"; break; case self::POST: curl_setopt(self::$ch, CURLOPT_CUSTOMREQUEST, self::POST); if (!empty($options['upload'])) { curl_setopt(self::$ch, CURLOPT_POST, TRUE); curl_setopt(self::$ch, CURLOPT_SAFE_UPLOAD, FALSE); curl_setopt(self::$ch, CURLOPT_POSTFIELDS, $attributes); self::$headers['Content-type'] = 'multipart/form-data'; } elseif (empty($options['oauth_request'])) { // application/json $encoded_attributes = json_encode($attributes); curl_setopt(self::$ch, CURLOPT_POSTFIELDS, $encoded_attributes); self::$headers['Content-type'] = 'application/json'; } else { // x-www-form-urlencoded $encoded_attributes = self::encode_attributes($attributes); curl_setopt(self::$ch, CURLOPT_POSTFIELDS, $encoded_attributes); self::$headers['Content-type'] = 'application/x-www-form-urlencoded'; } break; case self::PUT: $encoded_attributes = json_encode($attributes); curl_setopt(self::$ch, CURLOPT_CUSTOMREQUEST, self::PUT); curl_setopt(self::$ch, CURLOPT_POSTFIELDS, $encoded_attributes); self::$headers['Content-type'] = 'application/json'; break; } // Add access token to request if (Cache::has('podio_oauth')) { $token = Cache::get('podio_oauth')->access_token; self::$headers['Authorization'] = "OAuth2 {$token}"; } else { unset(self::$headers['Authorization']); } // File downloads can be of any type if (empty($options['file_download'])) { self::$headers['Accept'] = 'application/json'; } else { self::$headers['Accept'] = '*/*'; } curl_setopt(self::$ch, CURLOPT_HTTPHEADER, self::curl_headers()); curl_setopt(self::$ch, CURLOPT_URL, empty($options['file_download']) ? self::$url . $url : $url); $response = new PodioResponse(); if (isset($options['return_raw_as_resource_only']) && $options['return_raw_as_resource_only'] == true) { $result_handle = fopen('php://temp', 'w'); curl_setopt(self::$ch, CURLOPT_FILE, $result_handle); curl_exec(self::$ch); if (isset(self::$stdout) && is_resource(self::$stdout)) { fclose(self::$stdout); } self::$stdout = fopen('php://stdout', 'w'); curl_setopt(self::$ch, CURLOPT_FILE, self::$stdout); curl_setopt(self::$ch, CURLOPT_RETURNTRANSFER, true); $raw_headers_size = curl_getinfo(self::$ch, CURLINFO_HEADER_SIZE); fseek($result_handle, 0); $response->status = curl_getinfo(self::$ch, CURLINFO_HTTP_CODE); $response->headers = self::parse_headers(fread($result_handle, $raw_headers_size)); self::$last_response = $response; return $result_handle; } $raw_response = curl_exec(self::$ch); if ($raw_response === false) { throw new PodioConnectionError('Connection to Podio API failed: [' . curl_errno(self::$ch) . '] ' . curl_error(self::$ch), curl_errno(self::$ch)); } $raw_headers_size = curl_getinfo(self::$ch, CURLINFO_HEADER_SIZE); $response->body = substr($raw_response, $raw_headers_size); $response->status = curl_getinfo(self::$ch, CURLINFO_HTTP_CODE); $response->headers = self::parse_headers(substr($raw_response, 0, $raw_headers_size)); self::$last_response = $response; if (!isset($options['oauth_request'])) { $curl_info = curl_getinfo(self::$ch, CURLINFO_HEADER_OUT); self::log_request($method, $url, $encoded_attributes, $response, $curl_info); } switch ($response->status) { case 200: case 201: case 204: return $response; break; case 400: // invalid_grant_error or bad_request_error $body = $response->json_body(); if (strstr($body['error'], 'invalid_grant')) { // Reset access token & refresh_token self::clear_authentication(); throw new PodioInvalidGrantError($response->body, $response->status, $url); break; } else { throw new PodioBadRequestError($response->body, $response->status, $url); } break; case 401: $body = $response->json_body(); if (strstr($body['error_description'], 'expired_token') || strstr($body['error'], 'invalid_token')) { if (self::$oauth->refresh_token) { // Access token is expired. Try to refresh it. if (self::authenticate('refresh_token', array('refresh_token' => Cache::get('podio_oauth')->refresh_token))) { // Try the original request again. return self::request($method, $original_url, $attributes); } else { self::clear_authentication(); throw new PodioAuthorizationError($response->body, $response->status, $url); } } else { // We have tried in vain to get a new access token. Log the user out. self::clear_authentication(); throw new PodioAuthorizationError($response->body, $response->status, $url); } } elseif (strstr($body['error'], 'invalid_request') || strstr($body['error'], 'unauthorized')) { // Access token is invalid. self::clear_authentication(); throw new PodioAuthorizationError($response->body, $response->status, $url); } break; case 403: throw new PodioForbiddenError($response->body, $response->status, $url); break; case 404: throw new PodioNotFoundError($response->body, $response->status, $url); break; case 409: throw new PodioConflictError($response->body, $response->status, $url); break; case 410: throw new PodioGoneError($response->body, $response->status, $url); break; case 420: throw new PodioRateLimitError($response->body, $response->status, $url); break; case 500: throw new PodioServerError($response->body, $response->status, $url); break; case 502: case 503: case 504: throw new PodioUnavailableError($response->body, $response->status, $url); break; default: throw new PodioError($response->body, $response->status, $url); break; } return false; }