Skip to content

Commit

Permalink
initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
jsjohnst committed Apr 16, 2012
0 parents commit f4e4bf4
Show file tree
Hide file tree
Showing 17 changed files with 586 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
defaults.php
11 changes: 11 additions & 0 deletions SAMPLE-defaults.php
@@ -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");
18 changes: 18 additions & 0 deletions lib/Singleton.php
@@ -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;
}
}
144 changes: 144 additions & 0 deletions lib/Tumblr/API.php
@@ -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);
}
}
95 changes: 95 additions & 0 deletions lib/Tumblr/API/Authentication.php
@@ -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;
}
}
112 changes: 112 additions & 0 deletions lib/Tumblr/API/Request.php
@@ -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;
}
}

0 comments on commit f4e4bf4

Please sign in to comment.