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