PageRenderTime 52ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/external/podio-php/PodioAPI.php

https://github.com/fabianmu/shodio
PHP | 647 lines | 397 code | 38 blank | 212 comment | 46 complexity | 10fce23478936c624504f1a19b9134d7 MD5 | raw file
  1. <?php
  2. // PEAR Packages
  3. require_once('Log.php');
  4. require_once('HTTP/Request2.php');
  5. // Internal
  6. require_once('PodioOAuth.php');
  7. require_once('notification/notification.class.inc.php');
  8. require_once('status/status.class.inc.php');
  9. require_once('conversation/conversation.class.inc.php');
  10. require_once('task/task.class.inc.php');
  11. require_once('app/app.class.inc.php');
  12. require_once('item/item.class.inc.php');
  13. require_once('comment/comment.class.inc.php');
  14. require_once('user/user.class.inc.php');
  15. require_once('rating/rating.class.inc.php');
  16. require_once('space/space.class.inc.php');
  17. require_once('org/org.class.inc.php');
  18. require_once('contact/contact.class.inc.php');
  19. require_once('subscription/subscription.class.inc.php');
  20. require_once('file/file.class.inc.php');
  21. require_once('calendar/calendar.class.inc.php');
  22. require_once('search/search.class.inc.php');
  23. require_once('stream/stream.class.inc.php');
  24. require_once('app_store/app_store.class.inc.php');
  25. require_once('tag/tag.class.inc.php');
  26. require_once('bulletin/bulletin.class.inc.php');
  27. require_once('widget/widget.class.inc.php');
  28. require_once('filter/filter.class.inc.php');
  29. require_once('form/form.class.inc.php');
  30. require_once('integration/integration.class.inc.php');
  31. /**
  32. * Primary Podio API implementation class. This is merely a container for
  33. * the specific API areas.
  34. */
  35. class PodioAPI {
  36. /**
  37. * Reference to PodioBaseAPI instance
  38. */
  39. public $api;
  40. /**
  41. * Reference to PodioNotificationAPI instance
  42. */
  43. public $notification;
  44. /**
  45. * Reference to PodioConversationAPI instance
  46. */
  47. public $conversation;
  48. /**
  49. * Reference to PodioStatusAPI instance
  50. */
  51. public $status;
  52. /**
  53. * Reference to PodioTaskAPI instance
  54. */
  55. public $task;
  56. /**
  57. * Reference to PodioAppAPI instance
  58. */
  59. public $app;
  60. /**
  61. * Reference to PodioItemAPI instance
  62. */
  63. public $item;
  64. /**
  65. * Reference to PodioUserAPI instance
  66. */
  67. public $user;
  68. /**
  69. * Reference to PodioCommentAPI instance
  70. */
  71. public $comment;
  72. /**
  73. * Reference to PodioRatingAPI instance
  74. */
  75. public $rating;
  76. /**
  77. * Reference to PodioSpaceAPI instance
  78. */
  79. public $space;
  80. /**
  81. * Reference to PodioOrgAPI instance
  82. */
  83. public $org;
  84. /**
  85. * Reference to PodioContactAPI instance
  86. */
  87. public $contact;
  88. /**
  89. * Reference to PodioSubscriptionAPI instance
  90. */
  91. public $subscription;
  92. /**
  93. * Reference to PodioFileAPI instance
  94. */
  95. public $file;
  96. /**
  97. * Reference to PodioCalendarAPI instance
  98. */
  99. public $calendar;
  100. /**
  101. * Reference to PodioSearchAPI instance
  102. */
  103. public $search;
  104. /**
  105. * Reference to PodioStreamAPI instance
  106. */
  107. public $stream;
  108. /**
  109. * Reference to PodioAppStoreAPI instance
  110. */
  111. public $app_store;
  112. /**
  113. * Reference to PodioTagAPI instance
  114. */
  115. public $tag;
  116. /**
  117. * Reference to PodioBulletinAPI instance
  118. */
  119. public $bulletin;
  120. /**
  121. * Reference to PodioWidgetAPI instance
  122. */
  123. public $widget;
  124. /**
  125. * Reference to PodioFilterAPI instance
  126. */
  127. public $filter;
  128. /**
  129. * Reference to PodioFormAPI instance
  130. */
  131. public $form;
  132. /**
  133. * Reference to PodioIntegrationAPI instance
  134. */
  135. public $integration;
  136. public function __construct() {
  137. $this->api = PodioBaseAPI::instance();
  138. $this->notification = new PodioNotificationAPI();
  139. $this->conversation = new PodioConversationAPI();
  140. $this->status = new PodioStatusAPI();
  141. $this->task = new PodioTaskAPI();
  142. $this->app = new PodioAppAPI();
  143. $this->item = new PodioItemAPI();
  144. $this->user = new PodioUserAPI();
  145. $this->comment = new PodioCommentAPI();
  146. $this->rating = new PodioRatingAPI();
  147. $this->space = new PodioSpaceAPI();
  148. $this->org = new PodioOrgAPI();
  149. $this->contact = new PodioContactAPI();
  150. $this->subscription = new PodioSubscriptionAPI();
  151. $this->file = new PodioFileAPI();
  152. $this->calendar = new PodioCalendarAPI();
  153. $this->search = new PodioSearchAPI();
  154. $this->stream = new PodioStreamAPI();
  155. $this->app_store = new PodioAppStoreAPI();
  156. $this->tag = new PodioTagAPI();
  157. $this->bulletin = new PodioBulletinAPI();
  158. $this->widget = new PodioWidgetAPI();
  159. $this->filter = new PodioFilterAPI();
  160. $this->form = new PodioFormAPI();
  161. $this->integration = new PodioIntegrationAPI();
  162. }
  163. }
  164. /**
  165. * A Singleton class that handles all communication with the API server.
  166. */
  167. class PodioBaseAPI {
  168. /**
  169. * URL for the API server
  170. */
  171. protected $url;
  172. /**
  173. * OAuth client id
  174. */
  175. protected $client_id;
  176. /**
  177. * OAuth client secret
  178. */
  179. protected $secret;
  180. /**
  181. * Contains the last error message from the API server
  182. */
  183. protected $last_error;
  184. /**
  185. * Contains the last error status code from the API server
  186. */
  187. protected $last_error_status_code;
  188. /**
  189. * Current log handler for the API log
  190. */
  191. protected $log_handler;
  192. /**
  193. * Current log name for the API log
  194. */
  195. protected $log_name;
  196. /**
  197. * Current log identification for the API log
  198. */
  199. protected $log_ident;
  200. /**
  201. * Current log levels for the API log
  202. */
  203. protected $log_levels;
  204. /**
  205. * Current API error handler.
  206. */
  207. protected $error_handler;
  208. private static $instance;
  209. private function __construct($url, $client_id, $client_secret, $upload_end_point, $frontend_token = '') {
  210. $this->url = $url;
  211. $this->client_id = $client_id;
  212. $this->secret = $client_secret;
  213. $this->frontend_token = $frontend_token;
  214. $this->upload_end_point = $upload_end_point;
  215. $this->log_handler = 'error_log';
  216. $this->log_name = '';
  217. $this->log_ident = 'PODIO_API_CLIENT';
  218. $this->log_levels = array(
  219. 'error' => TRUE,
  220. 'GET' => FALSE,
  221. 'POST' => 'verbose',
  222. 'PUT' => 'verbose',
  223. 'DELETE' => FALSE,
  224. );
  225. $this->error_handler = NULL;
  226. }
  227. /**
  228. * Constructor for the singleton instance. Call with parameters first time,
  229. * call without parameters subsequent times.
  230. *
  231. * @param $url URL for the API server
  232. * @param $client_id OAuth Client id
  233. * @param $client_secret OAuth client secret
  234. * @param $upload_end_point Upload end point for file uploads
  235. * @param $frontend_token Special token used by Podio
  236. *
  237. * @return Singleton instance of PodioBaseAPI object
  238. */
  239. public static function instance($url = '', $client_id = '', $client_secret = '', $upload_end_point = '', $frontend_token = '') {
  240. if (!self::$instance) {
  241. self::$instance = new PodioBaseAPI($url, $client_id, $client_secret, $upload_end_point, $frontend_token);
  242. }
  243. return self::$instance;
  244. }
  245. /**
  246. * Set the API error handler.
  247. *
  248. * @param $handler
  249. */
  250. public function setErrorHandler($handler) {
  251. $this->error_handler = $handler;
  252. }
  253. /**
  254. * Log a message to the API log
  255. *
  256. * @param $message The message to log
  257. * @param $level The log level. See:
  258. * http://www.indelible.org/php/Log/guide.html#log-levels
  259. */
  260. public function log($message, $level = PEAR_LOG_INFO) {
  261. $logger = &Log::singleton($this->log_handler, $this->log_name, $this->log_ident);
  262. $logger->log('[api] ' . $message, $level);
  263. }
  264. /**
  265. * Set the log handler for the API log. See:
  266. * http://www.indelible.org/php/Log/guide.html#configuring-a-handler
  267. *
  268. * @param $handler
  269. * @param $name
  270. */
  271. public function setLogHandler($handler, $name, $ident) {
  272. $this->log_handler = $handler;
  273. $this->log_name = $name;
  274. $this->log_ident = $ident;
  275. }
  276. /**
  277. * Get the current log level for an area.
  278. *
  279. * @param $name Area to get log level for. Can be:
  280. * - error: Any error from API server
  281. * - GET: GET requests
  282. * - POST: POST requests
  283. * - PUT: PUT requests
  284. * - DELETE: DELETE requests
  285. */
  286. public function getLogLevel($name) {
  287. return $this->log_levels[$name];
  288. }
  289. /**
  290. * Set the current log level for an area.
  291. * @param $name Area to set. Can be:
  292. * - error: Any error from API server
  293. * - GET: GET requests
  294. * - POST: POST requests
  295. * - PUT: PUT requests
  296. * - DELETE: DELETE requests
  297. * @param $value New log level. Either TRUE, FALSE, "concise" or "verbose"
  298. */
  299. public function setLogLevel($name, $value) {
  300. $this->log_levels[$name] = $value;
  301. }
  302. /**
  303. * Get the current API server URL
  304. */
  305. public function getUrl() {
  306. return $this->url;
  307. }
  308. /**
  309. * Get the OAuth client id
  310. */
  311. public function getClientId() {
  312. return $this->client_id;
  313. }
  314. /**
  315. * Get the OAuth client secret
  316. */
  317. public function getClientSecret() {
  318. return $this->secret;
  319. }
  320. /**
  321. * Get the last error message from the API server
  322. */
  323. public function getError() {
  324. return $this->last_error;
  325. }
  326. /**
  327. * Get the last error status code from the API server
  328. */
  329. public function getErrorStatusCode() {
  330. return $this->last_error_status_code;
  331. }
  332. /**
  333. * Normalize filters for GET requests
  334. */
  335. public function normalizeFilters($filters) {
  336. $data = array();
  337. foreach ($filters as $filter) {
  338. if (empty($filter['values'])) {
  339. $data[$filter['key']] = '';
  340. }
  341. else if ($filter['key'] == 'created_by') {
  342. $created_bys = array();
  343. foreach ($filter['values'] as $value) {
  344. $created_bys[] = $value['type'].':'.$value['id'];
  345. }
  346. $data['created_by'] = implode(';', $created_bys);
  347. }
  348. else if (is_array($filter['values'])) {
  349. if (array_key_exists('from', $filter['values'])) {
  350. $from = isset($filter['values']['from']) ? $filter['values']['from'] : '';
  351. $to = $filter['values']['to'] ? $filter['values']['to'] : '';
  352. $data[$filter['key']] = $from.'-'.$to;
  353. }
  354. else {
  355. foreach ($filter['values'] as $k => $v) {
  356. if ($v === NULL) {
  357. $filter['values'][$k] = 'null';
  358. }
  359. }
  360. $data[$filter['key']] = implode(';', $filter['values']);
  361. }
  362. }
  363. }
  364. return $data;
  365. }
  366. /**
  367. * Upload a file for later use.
  368. *
  369. * @param $file Path to file for upload
  370. * @param $name File name to use
  371. *
  372. * @return Array with new file id
  373. */
  374. public function upload($file, $name) {
  375. $oauth = PodioOAuth::instance();
  376. $request = new HTTP_Request2($this->upload_end_point, HTTP_Request2::METHOD_POST, array(
  377. 'ssl_verify_peer' => false,
  378. 'ssl_verify_host' => false
  379. ));
  380. $request->setConfig('use_brackets', FALSE);
  381. $request->setConfig('follow_redirects', TRUE);
  382. $request->setHeader('User-Agent', 'Podio API Client/1.0');
  383. $request->setHeader('Accept', 'application/json');
  384. $request->setHeader('Authorization', 'OAuth2 '.$oauth->access_token);
  385. $request->addUpload('file', $file);
  386. $request->addPostParameter('name', $name);
  387. try {
  388. $response = $request->send();
  389. switch ($response->getStatus()) {
  390. case 200 :
  391. case 201 :
  392. case 204 :
  393. return json_decode($response->getBody(), TRUE);
  394. break;
  395. case 401 :
  396. case 400 :
  397. case 403 :
  398. case 404 :
  399. case 410 :
  400. case 500 :
  401. case 503 :
  402. if ($this->getLogLevel('error')) {
  403. $this->log($request->getMethod() .' '. $response->getStatus().' '.$response->getReasonPhrase().' '.$request->getUrl(), PEAR_LOG_WARNING);
  404. $this->log($response->getBody(), PEAR_LOG_WARNING);
  405. }
  406. $this->last_error = json_decode($response->getBody(), TRUE);
  407. $this->last_error_status_code = $response->getStatus();
  408. return FALSE;
  409. break;
  410. default :
  411. break;
  412. }
  413. } catch (HTTP_Request2_Exception $e) {
  414. if ($this->getLogLevel('error')) {
  415. $this->log($e->getMessage(), PEAR_LOG_WARNING);
  416. }
  417. }
  418. }
  419. /**
  420. * Build and perform an API request.
  421. *
  422. * @param $url URL to make call to. E.g. /user/status
  423. * @param $data Any data to send with the request
  424. * @param method HTTP method to be used for call
  425. *
  426. * @return Varies by API call
  427. */
  428. public function request($url, $data = '', $method = HTTP_Request2::METHOD_GET) {
  429. $oauth = PodioOAuth::instance();
  430. $request = new HTTP_Request2($this->url . $url, $method, array(
  431. 'ssl_verify_peer' => false,
  432. 'ssl_verify_host' => false
  433. ));
  434. $request->setConfig('use_brackets', FALSE);
  435. $request->setConfig('follow_redirects', TRUE);
  436. $request->setHeader('User-Agent', 'Podio API Client/1.0');
  437. $request->setHeader('Accept', 'application/json');
  438. $request->setHeader('Accept-Encoding', 'gzip');
  439. if ($this->frontend_token) {
  440. $request->setHeader('X-Podio-Frontend-Token', $this->frontend_token);
  441. }
  442. $location = $request->getUrl();
  443. // These URLs can be called without an access token.
  444. $no_token_list = array(
  445. '@^/$@',
  446. '@^/space/invite/status$@',
  447. '@^/user/activate_user$@',
  448. '@^/user/recover_password$@',
  449. '@^/user/reset_password$@',
  450. '@^/space/invite/decline$@',
  451. '@^/app_store/author/[0-9]+/profile$@',
  452. '@^/app_store/category/$@',
  453. '@^/app_store/category/[0-9]+$@',
  454. '@^/app_store/featured$@',
  455. '@^/app_store/[0-9]+/v2$@',
  456. '@^/app_store/author/[0-9]+/v2/$@',
  457. '@^/app_store/category/[0-9]+/$@',
  458. '@^/app_store/search/$@',
  459. '@^/app_store/top/v2/$@',
  460. '@^/app_store/org$@',
  461. '@^/app_store/org/[\w]+/$@',
  462. '@^/app_store/org/[\w]+/profile$@',
  463. );
  464. $is_on_no_token_list = FALSE;
  465. foreach ($no_token_list as $regex) {
  466. if (preg_match($regex, $url)) {
  467. $is_on_no_token_list = TRUE;
  468. break;
  469. }
  470. }
  471. if (!($url == '/user/' && $method == HTTP_Request2::METHOD_POST) && !$is_on_no_token_list) {
  472. if (!$oauth->access_token && !(substr($url, 0, 6) == '/file/' && substr($url, -9) == '/location')) {
  473. return FALSE;
  474. }
  475. }
  476. if ($oauth->access_token) {
  477. $request->setHeader('Authorization', 'OAuth2 '.$oauth->access_token);
  478. }
  479. switch ($method) {
  480. case HTTP_Request2::METHOD_GET :
  481. $request->setHeader('Content-type', 'application/x-www-form-urlencoded');
  482. if (is_array($data)) {
  483. foreach ($data as $key => $value) {
  484. $location->setQueryVariable($key, $value);
  485. }
  486. }
  487. break;
  488. case HTTP_Request2::METHOD_DELETE :
  489. $request->setHeader('Content-type', 'application/x-www-form-urlencoded');
  490. if (is_array($data)) {
  491. foreach ($data as $key => $value) {
  492. $location->setQueryVariable($key, $value);
  493. }
  494. }
  495. break;
  496. case HTTP_Request2::METHOD_POST :
  497. case HTTP_Request2::METHOD_PUT :
  498. $request->setHeader('Content-type', 'application/json');
  499. $request->setBody(json_encode($data));
  500. break;
  501. default :
  502. break;
  503. }
  504. // Log request if needed.
  505. if ($this->getLogLevel($method)) {
  506. $this->log($request->getMethod().' '.$request->getUrl());
  507. if ($this->getLogLevel($method) == 'verbose') {
  508. $this->log($request->getBody());
  509. }
  510. }
  511. try {
  512. $response = $request->send();
  513. switch ($response->getStatus()) {
  514. case 200 :
  515. return $response;
  516. break;
  517. case 201 :
  518. // Only POST requests can result in 201 Created.
  519. if ($request->getMethod() == HTTP_Request2::METHOD_POST) {
  520. return $response;
  521. }
  522. break;
  523. case 204 :
  524. return $response;
  525. break;
  526. case 401 :
  527. $body = json_decode($response->getBody(), TRUE);
  528. if (strstr($body['error_description'], 'expired_token')) {
  529. if ($oauth->refresh_token) {
  530. // Access token is expired. Try to refresh it.
  531. $refresh_token = $oauth->refresh_token;
  532. $oauth->getAccessToken('refresh_token', array('refresh_token' => $refresh_token));
  533. if ($oauth->access_token) {
  534. // Try the original request again.
  535. return $this->request($url, $data, $method);
  536. }
  537. else {
  538. // New token could not be fetched. Log user out.
  539. $oauth->access_token = '';
  540. $oauth->refresh_token = '';
  541. $oauth->throwError('refresh_failed', 'Refreshing access token failed.');
  542. }
  543. }
  544. else {
  545. // We have tried in vain to get a new access token. Log the user out.
  546. $oauth->access_token = '';
  547. $oauth->refresh_token = '';
  548. $oauth->throwError('no_refresh_token', 'No refresh token available.');
  549. }
  550. }
  551. elseif (strstr($body['error'], 'invalid_token') || strstr($body['error'], 'invalid_request')) {
  552. // Access token is invalid. Log the user out and try again.
  553. $oauth->access_token = '';
  554. $oauth->refresh_token = '';
  555. $oauth->throwError('invalid_token', 'Invalid token.');
  556. }
  557. break;
  558. case 400 :
  559. $body = json_decode($response->getBody(), TRUE);
  560. if (strstr($body['error'], 'invalid_grant')) {
  561. $oauth = PodioOAuth::instance();
  562. $oauth->access_token = '';
  563. $oauth->refresh_token = '';
  564. $oauth->throwError('invalid_grant', 'Invalid grant.');
  565. break;
  566. }
  567. case 403 :
  568. case 404 :
  569. case 410 :
  570. if ($this->getLogLevel('error')) {
  571. $this->log($request->getMethod() .' '. $response->getStatus().' '.$response->getReasonPhrase().' '.$request->getUrl(), PEAR_LOG_WARNING);
  572. $this->log($response->getBody(), PEAR_LOG_WARNING);
  573. }
  574. $this->last_error = json_decode($response->getBody(), TRUE);
  575. $this->last_error_status_code = $response->getStatus();
  576. return FALSE;
  577. break;
  578. case 500 :
  579. case 502 :
  580. case 503 :
  581. case 504 :
  582. if ($this->getLogLevel('error')) {
  583. $this->log($request->getMethod() .' '. $response->getStatus().' '.$response->getReasonPhrase().' '.$request->getUrl(), PEAR_LOG_WARNING);
  584. $this->log($response->getBody(), PEAR_LOG_WARNING);
  585. }
  586. $this->last_error = json_decode($response->getBody(), TRUE);
  587. $this->last_error_status_code = $response->getStatus();
  588. // Throw API error
  589. $this->throwError($response);
  590. return FALSE;
  591. break;
  592. default :
  593. break;
  594. }
  595. } catch (HTTP_Request2_Exception $e) {
  596. if ($this->getLogLevel('error')) {
  597. $this->log($e->getMessage(), PEAR_LOG_WARNING);
  598. }
  599. }
  600. }
  601. /**
  602. * Throws an API error. If an error callback is defined it will be called.
  603. */
  604. public function throwError($response) {
  605. if ($this->error_handler) {
  606. call_user_func($this->error_handler, $response);
  607. }
  608. }
  609. }