/plugin/PBAPI/PBAPI.php
PHP | 689 lines | 346 code | 63 blank | 280 comment | 65 complexity | 64d29b73d279f743f9ff5d98da4dc292 MD5 | raw file
1<?php 2/** 3 * Photobucket API 4 * Fluent interface for PHP5 5 * 6 * @author jhart 7 * @package PBAPI 8 * 9 * @copyright Copyright (c) 2008, Photobucket, Inc. 10 * @license http://www.opensource.org/licenses/mit-license.php The MIT License 11 */ 12require_once dirname(__FILE__) . '/PBAPI/Exception.php'; 13/** 14 * Load Exceptions 15 */ 16/** 17 * Load Response Exceptions 18 */ 19require_once dirname(__FILE__) . '/PBAPI/Exception/Response.php'; 20 21/** 22 * PBAPI Class 23 * Main class for Photobucket API interaction 24 * 25 * @package PBAPI 26 */ 27class PBAPI 28{ 29 30 /** 31 * Request object holder 32 * 33 * @var PBAPI_Request 34 */ 35 protected $request; 36 /** 37 * Response parser holder 38 * 39 * @var PBAPI_Response 40 */ 41 protected $response; 42 /** 43 * Methods classes holder 44 * 45 * @var PBAPI_Methods 46 */ 47 protected $methods; 48 49 /** 50 * current method stack 51 * 52 * @var array 53 */ 54 protected $method_stack = array(); 55 /** 56 * Current parameter set 57 * 58 * @var array key->value pairs 59 */ 60 protected $params = array(); 61 /** 62 * Current URI 63 * 64 * @var string 65 */ 66 protected $uri; 67 68 /** 69 * Current username 70 * 71 * @var string 72 */ 73 protected $username = ''; 74 75 /** 76 * Flag to not reset after method call 77 * 78 * @var bool 79 */ 80 protected $noReset = false; 81 82 /** 83 * Method validation map 84 * 85 * @static 86 * @var array 87 */ 88 static $method_validation_map; 89 90 /** 91 * Class constructor 92 * Sets up request, consumer and methods 93 * 94 * @param string $consumer_key OAuth consumer key (scid) 95 * @param string $consumer_secret OAuth consumer secret (private key) 96 * @param string $type [optional, default=determined] Request type (one of class names in PBAPI/Request/*) 97 * @param string $subdomain [optional, default='api'] default subdomain to use in requests 98 * @param string $default_format [optional, default='xml'] response format to receive from api 99 * @param array $type_params [optional, default=none] Request class parameters 100 */ 101 public function __construct($consumer_key, $consumer_secret, $type = null, $subdomain = 'api', $default_format = 'xml', $type_params = array()) 102 { 103 $this->_loadMethodClass('base'); 104 105 $this->setRequest($type, $subdomain, $default_format, $type_params); 106 $this->setOAuthConsumer($consumer_key, $consumer_secret); 107 } 108 109 /////////////////////// Setup and Settings /////////////////////// 110 111 112 /** 113 * Set OAuth Token 114 * 115 * @param string $token oauth token 116 * @param string $token_secret oauth secret 117 * @param string $username [optional, default=nochange] username associated with this token 118 * @return PBAPI $this Fluent reference to self 119 * @throws PBAPI_Exception on missing request 120 */ 121 public function setOAuthToken($token, $token_secret, $username = '') 122 { 123 if ($this->request) 124 $this->request->setOAuthToken($token, $token_secret); 125 else 126 throw new PBAPI_Exception('Request missing - cannot set OAuth Token', $this); 127 128 if ($username) 129 $this->username = $username; 130 return $this; 131 } 132 133 /** 134 * Get OAuth Token 135 * 136 * @throws PBAPI_Exception on missing request 137 */ 138 public function getOAuthToken() 139 { 140 if ($this->request) 141 return $this->request->getOAuthToken(); 142 else 143 throw new PBAPI_Exception('Request missing - cannot get OAuth Token', $this); 144 } 145 146 /** 147 * Set OAuth Consumer info 148 * 149 * @param string $consumer_key OAuth consumer key (scid) 150 * @param string $consumer_secret OAuth consumer secret (private key) 151 * @return PBAPI $this Fluent reference to self 152 * @throws PBAPI_Exception on missing request 153 */ 154 public function setOAuthConsumer($consumer_key, $consumer_secret) 155 { 156 if ($this->request) 157 $this->request->setOAuthConsumer($consumer_key, $consumer_secret); 158 else 159 throw new PBAPI_Exception('Request missing - cannot set OAuth Token', $this); 160 161 return $this; 162 } 163 164 /** 165 * Set current subdomain 166 * 167 * @param string $subdomain 168 * @return PBAPI $this Fluent reference to self 169 * @throws PBAPI_Exception on missing request 170 */ 171 public function setSubdomain($subdomain) 172 { 173 if ($this->request) 174 $this->request->setSubdomain($subdomain); 175 else 176 throw new PBAPI_Exception('Request missing - cannot set Subdomain', $this); 177 178 return $this; 179 } 180 181 /** 182 * Get current subdomain 183 */ 184 public function getSubdomain() 185 { 186 if ($this->request) 187 return $this->request->getSubdomain(); 188 else 189 throw new PBAPI_Exception('Request missing - cannot get Subdomain', $this); 190 } 191 192 /** 193 * Get oauth token username 194 */ 195 public function getUsername() 196 { 197 return $this->username; 198 } 199 200 /** 201 * Set response parser 202 * 203 * @param string $type [optional, default=none] type of response parser (one of PBAPI/Response/*) 204 * @param array $params [optional, default=none] parameters to set up parser 205 * @return PBAPI $this Fluent reference to self 206 */ 207 public function setResponseParser($type = null, $params = array()) 208 { 209 $class = 'PBAPI_Response_' . $type; 210 if (! class_exists($class)) 211 require ('PBAPI/Response/' . $type . '.php'); 212 $this->response = new $class($params); 213 214 if (! $this->response) 215 throw new PBAPI_Exception('Could not get Response Parser', $this); 216 if (! $this->request) 217 throw new PBAPI_Exception('Request missing - cannot set OAuth Token', $this); 218 219 $this->request->setDefaultFormat($this->response->getFormat()); 220 return $this; 221 } 222 223 /** 224 * Set request method 225 * 226 * @param string $type [optional, default=determined] Request type (one of class names in PBAPI/Request/*) 227 * @param string $subdomain [optional, default='api'] default subdomain to use in requests 228 * @param string $default_format [optional, default='xml'] response format to receive from api 229 * @param array $type_params [optional, default=none] Request class parameters 230 * @return PBAPI $this Fluent reference to self 231 */ 232 public function setRequest($type = null, $subdomain = 'api', $default_format = 'xml', $request_params = array()) 233 { 234 if (! $type) 235 $type = self :: _detectRequestStrategy(); 236 $class = 'PBAPI_Request_' . $type; 237 if (! class_exists($class)) 238 require ('PBAPI/Request/' . $type . '.php'); 239 $this->request = new $class($subdomain, $default_format, $request_params); 240 return $this; 241 } 242 243 /** 244 * Attempt to detect request strategy and set the type 245 * 246 * @return string 247 */ 248 protected static function _detectRequestStrategy() 249 { 250 if (function_exists('curl_init')) 251 return 'curl'; 252 if (ini_get('allow_url_fopen')) 253 return 'fopenurl'; 254 } 255 256 /** 257 * Reset current data 258 * 259 * @param bool $uri [optional] reset URI data 260 * @param bool $methods [optional] reset method data (current method depth) 261 * @param bool $params [optional] reset all parameters 262 * @param bool $auth [optional] reset auth token 263 * @return PBAPI $this Fluent reference to self 264 */ 265 public function reset($uri = true, $methods = true, $params = true, $auth = false) 266 { 267 if ($uri) 268 $this->uri = null; 269 if ($methods) 270 { 271 $this->methods->_reset(); 272 $this->method_stack = array(); 273 } 274 if ($params) 275 $this->params = array(); 276 if ($auth && $this->request) 277 $this->resetOAuthToken(); 278 return $this; 279 } 280 281 /** 282 * Set No Reset Flag 283 * 284 * @param bool $set 285 * @return PBAPI $this Fluent reference to self 286 */ 287 public function setNoReset($set) 288 { 289 $this->noReset = $set; 290 return $this; 291 } 292 293 /////////////////////// Requests and Responses /////////////////////// 294 295 296 /** 297 * Get current parameters 298 * 299 * @return array current parameter key->values 300 */ 301 public function getParams() 302 { 303 return $this->params; 304 } 305 306 /** 307 * Get parsed response (from response parser) 308 * 309 * @param bool $onlycontent only return 'content' of response 310 * @return mixed 311 * @throws PBAPI_Exception on no response parser 312 * @throws PBAPI_Exception_Response on response exception 313 */ 314 public function getParsedResponse($onlycontent = false) 315 { 316 if (! $this->response) 317 throw new PBAPI_Exception('No response parser set up', $this); 318 319 try 320 { 321 return $this->response->parse(trim($this->response_string), $onlycontent); 322 } 323 catch (PBAPI_Exception_Response $e) 324 { 325 //set core into exception 326 throw new PBAPI_Exception_Response($e->getMessage(), $e->getCode(), $this); 327 } 328 } 329 330 /** 331 * Get raw response string 332 * 333 * @return string 334 */ 335 public function getResponseString() 336 { 337 return $this->response_string; 338 } 339 340 /**#@+ 341 * Forward current set up request to the request method and get back the response 342 * 343 * @return PBAPI $this Fluent reference to self 344 */ 345 public function get() 346 { 347 $this->_validateRequest('get'); 348 $this->_setResponse($this->request->get($this->uri, $this->params)); 349 if (! $this->noReset) 350 $this->reset(); 351 return $this; 352 } 353 354 public function post() 355 { 356 $this->_validateRequest('post'); 357 $this->_setResponse($this->request->post($this->uri, $this->params)); 358 if (! $this->noReset) 359 $this->reset(); 360 return $this; 361 } 362 363 public function put() 364 { 365 $this->_validateRequest('put'); 366 $this->_setResponse($this->request->put($this->uri, $this->params)); 367 if (! $this->noReset) 368 $this->reset(); 369 return $this; 370 } 371 372 public function delete() 373 { 374 $this->_validateRequest('delete'); 375 $this->_setResponse($this->request->delete($this->uri, $this->params)); 376 if (! $this->noReset) 377 $this->reset(); 378 return $this; 379 } 380 381 /**#@-*/ 382 383 /** 384 * Load and set the current OAuth token from the last response string 385 * 386 * @param bool $subdomain true if you want to also set the current default call subdomain to what is in the response. 387 * @return PBAPI $this Fluent reference to self 388 */ 389 public function loadTokenFromResponse($subdomain = true) 390 { 391 $string = trim($this->response_string); 392 $params = array(); 393 394 parse_str($string, $params); 395 if (empty($params) || empty($params['oauth_token']) || empty($params['oauth_token_secret'])) 396 { 397 throw new PBAPI_Exception('Token and Token Secret returned in response'); 398 } 399 400 $username = (! empty($params['username'])) ? $params['username'] : ''; 401 $this->setOAuthToken($params['oauth_token'], $params['oauth_token_secret'], $username); 402 if ($subdomain && ! empty($params['subdomain'])) 403 { 404 $this->setSubdomain($params['subdomain']); 405 } 406 return $this; 407 } 408 409 /** 410 * Go to Redirect URL 411 * does actual header() 412 * 413 * @param string $type [login|logout|...] 414 * @param string $extra [optional] set 'extra' parameter 415 * @throws PBAPI_Exception on invalid redirect 416 */ 417 public function goRedirect($type = null, $extra = null) 418 { 419 if (strpos($type, 'http://') !== 0) 420 { 421 switch ($type) 422 { 423 case 'login' : 424 $this->request->redirectLogin($extra); 425 case 'logout' : 426 $this->request->redirectLogout($extra); 427 default : 428 throw new PBAPI_Exception('Invalid redirect', $this); 429 } 430 } 431 else 432 { 433 if ($extra) 434 { 435 $sep = (strpos($type, '?')) ? '&' : '?'; 436 $url = $type . $sep . 'extra=' . $extra; 437 } 438 else 439 { 440 $url = $type; 441 } 442 header('Location: ' . $url); 443 exit(); 444 } 445 } 446 447 /////////////////////// Inter Class 'Private' Methods /////////////////////// 448 449 450 /** 451 * Set a parameter 452 * 453 * @param string $name 454 * @param string $value 455 * @return PBAPI $this Fluent reference to self 456 */ 457 public function _setParam($name, $value) 458 { 459 $this->params[$name] = $value; 460 return $this; 461 } 462 463 /** 464 * Set a list of parameters 465 * 466 * @param array $pairs parameters as key=>value (allowing empty) 467 * @return PBAPI $this Fluent reference to self 468 */ 469 public function _setParamList($pairs) 470 { 471 if (! $pairs) 472 return $this; 473 foreach ($pairs as $name => $value) 474 { 475 $this->_setParam($name, $value); 476 } 477 return $this; 478 } 479 480 /** 481 * Set current URI 482 * 483 * @param string $uri uri string to set, sprintf format 484 * @param array $replacements [optional, default=none] if uri is sprintf string, replacements from this array in array order 485 * @return PBAPI $this Fluent reference to self 486 */ 487 public function _setUri($uri, $replacements = null) 488 { 489 if ($replacements !== null && ! is_array($replacements)) 490 $replacements = array($replacements); 491 if ($replacements !== null) 492 { 493 $replacements = array_map('urlencode', $replacements); 494 $this->uri = vsprintf($uri, $replacements); 495 } 496 else 497 $this->uri = $uri; 498 return $this; 499 } 500 501 /** 502 * Append more to the current uri 503 * 504 * @param string $uri uri string to set, sprintf format 505 * @param array $replacements [optional, default=none] if uri is sprintf string, replacements from this array in array order 506 * @return PBAPI $this Fluent reference to self 507 */ 508 public function _appendUri($uri, $replacements = null) 509 { 510 if ($replacements !== null && ! is_array($replacements)) 511 $replacements = array($replacements); 512 if ($replacements !== null) 513 { 514 $replacements = array_map('urlencode', $replacements); 515 $this->uri .= vsprintf($uri, $replacements); 516 } 517 else 518 $this->uri .= $uri; 519 return $this; 520 } 521 522 /** 523 * Load a method class 524 * 525 * @param string $name Method class name (one of PBAPI/Methods/*) 526 * @return PBAPI_Methods 527 */ 528 public function _loadMethodClass($name) 529 { 530 $class = 'PBAPI_Methods_' . $name; 531 if (! class_exists($class)) 532 require_once (dirname(__FILE__) . '/PBAPI/Methods/' . $name . '.php'); 533 $classObj = new $class($this); 534 return $this->_setMethods($classObj); 535 } 536 537 /** 538 * Set Methods class 539 * 540 * @param PBAPI_Methods $class class instance 541 * @return PBAPI $this Fluent reference to self 542 */ 543 public function _setMethods($class) 544 { 545 $this->methods = $class; 546 return $this; 547 } 548 549 /** 550 * Get parameters currently in obj 551 * 552 * @return array key->value 553 */ 554 public function _getParams() 555 { 556 return $this->params; 557 } 558 559 /** 560 * Get method stack - this is the level list of the method 561 * 562 * @return array methods list (in call order) 563 */ 564 public function _getMethodStack() 565 { 566 return $this->method_stack; 567 } 568 569 /** 570 * Set the current response string 571 * 572 * @param string $string 573 * @return PBAPI $this Fluent reference to self 574 */ 575 protected function _setResponse($string) 576 { 577 $this->response_string = $string; 578 return $this; 579 } 580 581 /** 582 * Validate Request (as currently set) 583 * 584 * $dt = syck_load(file_get_contents('api-defs.yml')); 585 * file_put_contents('methods.dat', serialize($dt)); 586 * 587 * @param string $method HTTP method to check against 588 * @return PBAPI $this Fluent reference to self 589 * @throws PBAPI_Exception method or parameters don't match presets 590 */ 591 protected function _validateRequest($method) 592 { 593 //get proper map 594 $map = $this->_loadMethodValidationMap(); 595 596 //fixup stack 597 $stack = $this->method_stack; 598 if (empty($stack[1])) 599 $stack[1] = '_default'; 600 601 //get method 602 $val_methods = $map[$stack[0]][$stack[1]]; 603 if (! $val_methods || ! array_key_exists($method, $val_methods)) 604 throw new PBAPI_Exception('invalid method: ' . $method, $this); 605 606 //get parameters 607 $val_params = $val_methods[$method]; 608 if ($val_params) 609 { 610 //look for unknown parameters (if parameters are specified) 611 $unknowns = array_diff_key($this->params, $val_params); 612 if (count($unknowns)) 613 throw new PBAPI_Exception('unknown parameters: ' . implode(', ', array_keys($unknowns)), $this); 614 615 //look for missing required parameters 616 $missing = array_diff_key($val_params, $this->params); 617 if (count($missing)) 618 { 619 foreach ($missing as $key => $val) 620 { 621 if ($val != 'required') 622 unset($missing[$key]); 623 if ($key == 'aid' || $key == 'mid' || $key == 'uid' || $key == 'tagid') //todo somehow do this better 624 unset($missing[$key]); //also skip stuff we're catching already 625 } 626 if (count($missing)) 627 throw new PBAPI_Exception('missing required parameters: ' . implode(', ', array_keys($missing)), $this); 628 } 629 } 630 return $this; 631 } 632 633 /** 634 * Load validation map from data file 635 * Loads from ./PBAPI/data/methods.dat - a php serialize() file. 636 * The .yml file in the same directory is the 'source' of that dat file. 637 * 638 * @return array validation map from data file 639 */ 640 protected function _loadMethodValidationMap() 641 { 642 if (! self :: $method_validation_map) 643 { 644 $path = dirname(__FILE__) . '/PBAPI/data/methods.dat'; 645 self :: $method_validation_map = unserialize(file_get_contents($path)); 646 if (! self :: $method_validation_map) 647 throw new PBAPI_Exception('Could not load method map', $this); 648 } 649 return self :: $method_validation_map; 650 } 651 652 /////////////////////// Magics /////////////////////// 653 654 655 /** 656 * Magic function to forward other calls to the Methods 657 * This is the meat of the API, really 658 * 659 * @param string $name function name 660 * @param array $args argument array 661 * @return PBAPI $this Fluent reference to self 662 */ 663 public function __call($name, $args) 664 { 665 if (empty($args)) 666 { 667 $this->methods->$name(); 668 } 669 else 670 if (! empty($args[0]) && empty($args[1])) 671 { 672 $this->methods->$name($args[0]); 673 } 674 else 675 if (! empty($args[0]) && ! empty($args[1])) 676 { 677 $this->methods->$name($args[0], $args[1]); 678 } 679 else 680 { 681 //not currently used, but for forward compatibility 682 call_user_func_array(array($this->methods, $name), $args); 683 } 684 685 $this->method_stack[] = $name; 686 return $this; 687 } 688 689}