Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f4e4bf4
Showing
17 changed files
with
586 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
defaults.php |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
// copy this file to defaults.php and provide the requested values below | ||
|
||
define("BASE_HOSTNAME", ""); | ||
define("API_KEY", ""); | ||
define("API_SECRET", ""); | ||
define("TOKEN", ""); | ||
define("TOKEN_SECRET", ""); | ||
|
||
date_default_timezone_set("America/Los_Angeles"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
|
||
/* @class Singleton | ||
* Abstract base class used to implement the Singleton pattern | ||
*/ | ||
abstract class Singleton | ||
{ | ||
private static $singleton_instance = null; | ||
private function __construct() {} | ||
|
||
protected static function getInstance() { | ||
if(is_null(self::$singleton_instance)) { | ||
$class_name = get_called_class(); | ||
self::$singleton_instance = new $class_name(); | ||
} | ||
return self::$singleton_instance; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
<?php | ||
|
||
namespace Tumblr; | ||
|
||
/* @class API | ||
* This class exposes the Tumblr API w/ convenience methods for all the API endpoints | ||
*/ | ||
class API extends \Tumblr\API\Request | ||
{ | ||
public static function getInfo() { | ||
$instance = self::getInstance(); | ||
return $instance->request("GET", "/info", array("api_key"=>$instance->api_key), false); | ||
} | ||
|
||
public static function getAvatar($size = null) { | ||
$path = "/avatar"; | ||
|
||
if(!is_null($size)) { | ||
$path .= "/" . $size; | ||
} | ||
|
||
$instance = self::getInstance(); | ||
return $instance->request("GET", $path, null, false); | ||
} | ||
|
||
public static function getFollowers($limit = null, $offset = null) { | ||
$params = array(); | ||
|
||
if(!is_null($limit)) { | ||
$params["limit"] = $limit; | ||
} | ||
|
||
if(!is_null($offset)) { | ||
$params["offset"] = $offset; | ||
} | ||
|
||
$instance = self::getInstance(); | ||
return $instance->request("GET", "/followers", $params); | ||
} | ||
|
||
public static function getPostByID($id) { | ||
return self::getPosts(array("id"=>$id)); | ||
} | ||
|
||
public static function getPostsByType($type, $limit = null, $offset = null) { | ||
return self::getPosts(array( | ||
"type" => $type, | ||
"limit" => $limit, | ||
"offset" => $offset | ||
)); | ||
} | ||
|
||
public static function getPostsByTag($tag, $limit = null, $offset = null) { | ||
return self::getPosts(array( | ||
"tag" => $tag, | ||
"limit" => $limit, | ||
"offset" => $offset | ||
)); | ||
} | ||
|
||
public static function getPosts($options = array()) { | ||
if(!is_array($options)) { | ||
throw new \Tumblr\Exception("Invalid parameter passed to \\Tumblr\\API::getPosts().", \Tumblr\Exception::INVALID_PARAMS); | ||
} | ||
|
||
$instance = self::getInstance(); | ||
$options["api_key"] = $instance->api_key; | ||
|
||
$path = "/posts"; | ||
if(isset($options["type"])) { | ||
$path .= "/" . $options["type"]; | ||
unset($options["type"]); | ||
} | ||
|
||
$decorated = true; | ||
if(isset($options["_decorated"])) { | ||
$decorated = (bool) $options["_decorated"]; | ||
unset($options["_decorated"]); | ||
} | ||
|
||
$obj = $instance->request("GET", $path, $options, false); | ||
|
||
if($obj !== FALSE && $decorated) { | ||
$obj->raw_posts = $obj->posts; | ||
$obj->posts = array(); | ||
foreach($obj->raw_posts as $key=>$post) { | ||
$class_name = "Tumblr\\Post\\" . $post->type; | ||
$obj->posts[$key] = new $class_name($post); | ||
} | ||
} | ||
|
||
return $obj; | ||
} | ||
|
||
public static function getQueuedPosts() { | ||
$instance = self::getInstance(); | ||
return $instance->request("GET", "/posts/queue"); | ||
} | ||
|
||
public static function getDraftPosts() { | ||
$instance = self::getInstance(); | ||
return $instance->request("GET", "/posts/draft"); | ||
} | ||
|
||
public static function getSubmissionPosts() { | ||
$instance = self::getInstance(); | ||
return $instance->request("GET", "/posts/submission"); | ||
} | ||
|
||
public static function reblogPost($id, $reblog_key, $comment = "") { | ||
$options = array( | ||
"id" => $id, | ||
"reblog_key" => $reblog_key, | ||
"comment" => $comment | ||
); | ||
|
||
$instance = self::getInstance(); | ||
return $instance->request("POST", "/post/reblog", $options); | ||
} | ||
|
||
public static function deletePost($id) { | ||
$instance = self::getInstance(); | ||
return $instance->request("POST", "/post/delete", array( "id" => $id )); | ||
} | ||
|
||
public static function submitPost(\Tumblr\Post\BaseClass $post, $tweet = null) { | ||
$options = array(); | ||
|
||
foreach($post->serialize() as $key=>$value) { | ||
if(!is_null($value)) { | ||
$options[$key] = $value; | ||
} | ||
} | ||
|
||
if(!is_null($tweet)) { | ||
$options["tweet"] = $tweet; | ||
} | ||
|
||
$path = isset($options["id"]) ? "/post/edit" : "/post"; | ||
|
||
$instance = self::getInstance(); | ||
return $instance->request("POST", $path, $options); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
<?php | ||
|
||
namespace Tumblr\API; | ||
|
||
/* @class Authentication | ||
* Abstract base class which sets up the needed pieces to handle OAuth authentication | ||
*/ | ||
abstract class Authentication extends \Singleton | ||
{ | ||
// OAuth request endpoints | ||
const REQUEST_TOKEN_URL = "http://www.tumblr.com/oauth/request_token"; | ||
const AUTHORIZE_URL = "http://www.tumblr.com/oauth/authorize?oauth_token=%s"; | ||
const ACCESS_TOKEN_URL = "http://www.tumblr.com/oauth/access_token"; | ||
|
||
// API configuration | ||
protected $base_hostname; | ||
protected $api_key; | ||
protected $api_secret; | ||
protected $token; | ||
protected $token_secret; | ||
|
||
// PECL OAuth resource handle | ||
protected $oauth; | ||
|
||
// Sets the oauth_token / oauth_token_secret | ||
protected function setToken($token, $token_secret) { | ||
if(!($this->oauth instanceOf \OAuth)) { | ||
throw new \Tumblr\Exception("API not configured with hostname, key, and/or secret.", \Tumblr\Exception::NOT_CONFIGURED); | ||
} | ||
|
||
$this->token = $token; | ||
$this->token_secret = $token_secret; | ||
|
||
$this->oauth->setToken($this->token, $this->token_secret); | ||
} | ||
|
||
// This method sets the API configuration | ||
// api_key / api_secret are option in case you want to change the base_hostname mid-request | ||
// so they don't have to be provided every time | ||
public static function configure($base_hostname, $api_key = null, $api_secret = null) { | ||
$instance = self::getInstance(); | ||
$instance->base_hostname = $base_hostname; | ||
|
||
if(!is_null($api_key) && !is_null($api_secret)) { | ||
$instance->api_key = $api_key; | ||
$instance->api_secret = $api_secret; | ||
$instance->oauth = new \OAuth($instance->api_key, $instance->api_secret); | ||
$instance->oauth->enableDebug(); | ||
} | ||
} | ||
|
||
// This method handles authenticating the specific calling user user for the API | ||
// This method is polymorphic and actually handles multiple invocation use cases | ||
// Invocation #1: When not authenticated at all | ||
// ::authenticate(); -> { token: "rtoken", token_secret: "rtoken_secret", authorize_url: "" } | ||
// Invocation #2: Following the user visiting the authorization URL and receiving the callback | ||
// ::authenticate(rtoken, rtoken_secret, verifier); -> { token: "ptoken", token_secret: "ptoken_secret" } | ||
// Invocation #3: Once you have the permanent tokens from #2, you can save them and call like this from then on | ||
// ::authenticate(ptoken, ptoken_secret); | ||
public static function authenticate($token = null, $token_secret = null, $verifier = null) { | ||
$instance = self::getInstance(); | ||
|
||
if(!($instance->oauth instanceOf \OAuth)) { | ||
throw new \Tumblr\Exception("API not configured with hostname, key, and/or secret.", \Tumblr\Exception::NOT_CONFIGURED); | ||
} | ||
|
||
$ret = new \stdClass(); | ||
$ret->token = $token; | ||
$ret->token_secret = $token_secret; | ||
|
||
if(is_null($token) || is_null($token_secret)) { | ||
// Invocation path #1 | ||
$rt_info = $instance->oauth->getRequestToken(self::REQUEST_TOKEN_URL); | ||
|
||
$ret->token = $rt_info["oauth_token"]; | ||
$ret->token_secret = $rt_info["oauth_token_secret"]; | ||
$ret->authorize_url = sprintf(self::AUTHORIZE_URL, $rt_info["oauth_token"]); | ||
} else { | ||
// Invocation path #2 and #3 share this branch, but only #2 goes into the verifier phase below | ||
$instance->setToken($token, $token_secret); | ||
|
||
if(!is_null($verifier)) { | ||
// Invocation path #2 | ||
$at_info = $instance->oauth->getAccessToken(self::ACCESS_TOKEN_URL, "", $verifier); | ||
|
||
$instance->setToken($at_info["oauth_token"], $at_info["oauth_token_secret"]); | ||
|
||
$ret->token = $at_info["oauth_token"]; | ||
$ret->token_secret = $at_info["oauth_token_secret"]; | ||
} | ||
} | ||
|
||
return $ret; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
<?php | ||
|
||
namespace Tumblr\API; | ||
|
||
/* @class Request | ||
* Abstract base class which extends the Authentication base class with API request helpers | ||
*/ | ||
abstract class Request extends Authentication | ||
{ | ||
// API Request endpoint | ||
const API_BASE = "http://api.tumblr.com/v2/blog/%s%s"; | ||
|
||
// This method is used for API calls which don't need OAuth signing | ||
private function makeCurlRequest($method, $url, $params = array(), $headers = array()) { | ||
$ch = curl_init($url); | ||
|
||
switch(strtoupper($method)) { | ||
case 'GET': | ||
curl_setopt($ch, CURLOPT_HTTPGET, 1); | ||
if(!empty($params)) { | ||
curl_setopt($ch, CURLOPT_URL, $url . "?" . http_build_query($params)); | ||
} | ||
break; | ||
case 'POST': | ||
curl_setopt($ch, CURLOPT_POST, 1); | ||
if(!empty($params)) { | ||
curl_setopt($ch, CURLOPT_POSTFIELDS, $params); | ||
} | ||
break; | ||
default: | ||
throw new \Tumblr\Exception(sprintf("Unsupported HTTP Method: %s", $method), \Tumblr\Exception::INVALID_PARAMS); | ||
break; | ||
} | ||
|
||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); | ||
curl_setopt($ch, CURLOPT_HEADER, 0); | ||
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); | ||
|
||
// Curl requires an array of individual header lines rather than key=>value pairs | ||
if(!empty($headers)) { | ||
$curl_headers = array(); | ||
foreach($headers as $key=>$value) { | ||
$curl_headers[] = $key . ": " . $value; | ||
} | ||
curl_setopt($ch, CURLOPT_HTTPHEADERS, $curl_headers); | ||
} | ||
|
||
$response = curl_exec($ch); | ||
|
||
// Would much prefer to limit to 200/201, but sadly the Avatar endpoint returns a 301 so making this less restrictive | ||
$response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); | ||
if($response_code >= 200 && $response_code < 400) { | ||
return $response; | ||
} | ||
|
||
return FALSE; | ||
} | ||
|
||
// This method is used to make API requests that need OAuth signing | ||
private function makeOAuthRequest($method, $url, $params = array(), $headers = array()) { | ||
switch(strtoupper($method)) { | ||
case 'GET': | ||
$method = OAUTH_HTTP_METHOD_GET; | ||
break; | ||
case 'POST': | ||
$method = OAUTH_HTTP_METHOD_POST; | ||
break; | ||
default: | ||
throw new \Tumblr\Exception(sprintf("Unsupported HTTP Method: %s", $method), \Tumblr\Exception::INVALID_PARAMS); | ||
break; | ||
} | ||
|
||
if($this->oauth->fetch($url, $params, $method, $headers)) { | ||
return $this->oauth->getLastResponse(); | ||
} | ||
|
||
return FALSE; | ||
} | ||
|
||
// This is the method you use to actually make API requests | ||
// generally you shouldn't use this directly, but rather one of the helper methods exposed by Tumblr\API | ||
public function request($method, $path, $params = array(), $oauth = true) { | ||
if(!$this->base_hostname || !$this->api_key || !$this->api_secret) { | ||
throw new \Tumblr\Exception("API not configured with hostname, key, and/or secret.", \Tumblr\Exception::NOT_CONFIGURED); | ||
} | ||
|
||
$url = sprintf(self::API_BASE, $this->base_hostname, $path); | ||
|
||
if($oauth) { | ||
$response = $this->makeOAuthRequest($method, $url, $params); | ||
} else { | ||
$response = $this->makeCurlRequest($method, $url, $params); | ||
} | ||
|
||
if($response !== FALSE) { | ||
$json = json_decode($response); | ||
|
||
if(!$json || !property_exists($json, "meta")) { | ||
throw new \Tumblr\Exception("API didn't return a valid JSON response.", \Tumblr\Exception::INVALID_JSON_RESPONSE); | ||
} | ||
|
||
// Yes, this is duplicative, but errors can return a 200 response but with an error flag set on the meta status | ||
if($json->meta->status < 200 && $json->meta->status >= 400) { | ||
throw new \Tumblr\Exception(sprintf("API returned HTTP Code #%d -- %s", $json->meta->status, $json->meta->msg), \Tumblr\Exception::API_RETURNED_ERROR); | ||
} | ||
|
||
return $json->response; | ||
} | ||
|
||
return FALSE; | ||
} | ||
} |
Oops, something went wrong.