root/facebook/files/lib/data/facebook/Facebook.class.php @ 1208

Revision 1208, 21.4 kB (checked in by d0nut, 3 years ago)

facebook package fix

Line 
1<?php
2
3if (!function_exists('curl_init')) {
4  throw new Exception('Facebook needs the CURL PHP extension.');
5}
6if (!function_exists('json_decode')) {
7  throw new Exception('Facebook needs the JSON PHP extension.');
8}
9
10/**
11 * Thrown when an API call returns an exception.
12 *
13 * @author Naitik Shah <naitik@facebook.com>
14 */
15class FacebookApiException extends Exception
16{
17  /**
18   * The result from the API server that represents the exception information.
19   */
20  protected $result;
21
22  /**
23   * Make a new API Exception with the given result.
24   *
25   * @param Array $result the result from the API server
26   */
27  public function __construct($result) {
28    $this->result = $result;
29
30    $code = isset($result['error_code']) ? $result['error_code'] : 0;
31    $msg  = isset($result['error'])
32              ? $result['error']['message'] : $result['error_msg'];
33    parent::__construct($msg, $code);
34  }
35
36  /**
37   * Return the associated result object returned by the API server.
38   *
39   * @returns Array the result from the API server
40   */
41  public function getResult() {
42    return $this->result;
43  }
44
45  /**
46   * Returns the associated type for the error. This will default to
47   * 'Exception' when a type is not available.
48   *
49   * @return String
50   */
51  public function getType() {
52    return
53      isset($this->result['error']) && isset($this->result['error']['type'])
54      ? $this->result['error']['type']
55      : 'Exception';
56  }
57
58  /**
59   * To make debugging easier.
60   *
61   * @returns String the string representation of the error
62   */
63  public function __toString() {
64    $str = $this->getType() . ': ';
65    if ($this->code != 0) {
66      $str .= $this->code . ': ';
67    }
68    return $str . $this->message;
69  }
70}
71
72/**
73 * Provides access to the Facebook Platform.
74 *
75 * @author Naitik Shah <naitik@facebook.com>
76 */
77class Facebook
78{
79  /**
80   * Version.
81   */
82  const VERSION = '2.0.6';
83
84  /**
85   * Default options for curl.
86   */
87  public static $CURL_OPTS = array(
88    CURLOPT_CONNECTTIMEOUT => 10,
89    CURLOPT_RETURNTRANSFER => true,
90    CURLOPT_TIMEOUT        => 60,
91    CURLOPT_USERAGENT      => 'facebook-php-2.0',
92  );
93
94  /**
95   * List of query parameters that get automatically dropped when rebuilding
96   * the current URL.
97   */
98  protected static $DROP_QUERY_PARAMS = array(
99    'session',
100  );
101
102  /**
103   * Maps aliases to Facebook domains.
104   */
105  public static $DOMAIN_MAP = array(
106    'api'      => 'https://api.facebook.com/',
107    'api_read' => 'https://api-read.facebook.com/',
108    'graph'    => 'https://graph.facebook.com/',
109    'www'      => 'https://www.facebook.com/',
110  );
111
112  /**
113   * The Application ID.
114   */
115  protected $appId;
116
117  /**
118   * The Application API Secret.
119   */
120  protected $apiSecret;
121
122  /**
123   * The active user session, if one is available.
124   */
125  protected $session;
126
127  /**
128   * Indicates that we already loaded the session as best as we could.
129   */
130  protected $sessionLoaded = false;
131
132  /**
133   * Indicates if Cookie support should be enabled.
134   */
135  protected $cookieSupport = false;
136
137  /**
138   * Base domain for the Cookie.
139   */
140  protected $baseDomain = '';
141
142  /**
143   * Initialize a Facebook Application.
144   *
145   * The configuration:
146   * - appId: the application ID
147   * - secret: the application secret
148   * - cookie: (optional) boolean true to enable cookie support
149   * - domain: (optional) domain for the cookie
150   *
151   * @param Array $config the application configuration
152   */
153  public function __construct($config) {
154    $this->setAppId($config['appId']);
155    $this->setApiSecret($config['secret']);
156    if (isset($config['cookie'])) {
157      $this->setCookieSupport($config['cookie']);
158    }
159    if (isset($config['domain'])) {
160      $this->setBaseDomain($config['domain']);
161    }
162  }
163
164  /**
165   * Set the Application ID.
166   *
167   * @param String $appId the Application ID
168   */
169  public function setAppId($appId) {
170    $this->appId = $appId;
171    return $this;
172  }
173
174  /**
175   * Get the Application ID.
176   *
177   * @return String the Application ID
178   */
179  public function getAppId() {
180    return $this->appId;
181  }
182
183  /**
184   * Set the API Secret.
185   *
186   * @param String $appId the API Secret
187   */
188  public function setApiSecret($apiSecret) {
189    $this->apiSecret = $apiSecret;
190    return $this;
191  }
192
193  /**
194   * Get the API Secret.
195   *
196   * @return String the API Secret
197   */
198  public function getApiSecret() {
199    return $this->apiSecret;
200  }
201
202  /**
203   * Set the Cookie Support status.
204   *
205   * @param Boolean $cookieSupport the Cookie Support status
206   */
207  public function setCookieSupport($cookieSupport) {
208    $this->cookieSupport = $cookieSupport;
209    return $this;
210  }
211
212  /**
213   * Get the Cookie Support status.
214   *
215   * @return Boolean the Cookie Support status
216   */
217  public function useCookieSupport() {
218    return $this->cookieSupport;
219  }
220
221  /**
222   * Set the base domain for the Cookie.
223   *
224   * @param String $domain the base domain
225   */
226  public function setBaseDomain($domain) {
227    $this->baseDomain = $domain;
228    return $this;
229  }
230
231  /**
232   * Get the base domain for the Cookie.
233   *
234   * @return String the base domain
235   */
236  public function getBaseDomain() {
237    return $this->baseDomain;
238  }
239
240  /**
241   * Set the Session.
242   *
243   * @param Array $session the session
244   * @param Boolean $write_cookie indicate if a cookie should be written. this
245   * value is ignored if cookie support has been disabled.
246   */
247  public function setSession($session=null, $write_cookie=true) {
248    $session = $this->validateSessionObject($session);
249    $this->sessionLoaded = true;
250    $this->session = $session;
251    if ($write_cookie) {
252      $this->setCookieFromSession($session);
253    }
254    return $this;
255  }
256
257  /**
258   * Get the session object. This will automatically look for a signed session
259   * sent via the Cookie or Query Parameters if needed.
260   *
261   * @return Array the session
262   */
263  public function getSession() {
264    if (!$this->sessionLoaded) {
265      $session = null;
266      $write_cookie = true;
267
268      // try loading session from $_REQUEST
269      if (isset($_REQUEST['session'])) {
270        $session = json_decode(
271          get_magic_quotes_gpc()
272            ? stripslashes($_REQUEST['session'])
273            : $_REQUEST['session'],
274          true
275        );
276        $session = $this->validateSessionObject($session);
277      }
278
279      // try loading session from cookie if necessary
280      if (!$session && $this->useCookieSupport()) {
281        $cookieName = $this->getSessionCookieName();
282        if (isset($_COOKIE[$cookieName])) {
283          $session = array();
284          parse_str(trim(
285            get_magic_quotes_gpc()
286              ? stripslashes($_COOKIE[$cookieName])
287              : $_COOKIE[$cookieName],
288            '"'
289          ), $session);
290          $session = $this->validateSessionObject($session);
291          // write only if we need to delete a invalid session cookie
292          $write_cookie = empty($session);
293        }
294      }
295
296      $this->setSession($session, $write_cookie);
297    }
298
299    return $this->session;
300  }
301
302  /**
303   * Get the UID from the session.
304   *
305   * @return String the UID if available
306   */
307  public function getUser() {
308    $session = $this->getSession();
309    return $session ? $session['uid'] : null;
310  }
311
312  /**
313   * Get a Login URL for use with redirects. By default, full page redirect is
314   * assumed. If you are using the generated URL with a window.open() call in
315   * JavaScript, you can pass in display=popup as part of the $params.
316   *
317   * The parameters:
318   * - next: the url to go to after a successful login
319   * - cancel_url: the url to go to after the user cancels
320   * - req_perms: comma separated list of requested extended perms
321   * - display: can be "page" (default, full page) or "popup"
322   *
323   * @param Array $params provide custom parameters
324   * @return String the URL for the login flow
325   */
326  public function getLoginUrl($params=array()) {
327    $currentUrl = $this->getCurrentUrl();
328    return $this->getUrl(
329      'www',
330      'login.php',
331      array_merge(array(
332        'api_key'         => $this->getAppId(),
333        'cancel_url'      => $currentUrl,
334        'display'         => 'page',
335        'fbconnect'       => 1,
336        'next'            => $currentUrl,
337        'return_session'  => 1,
338        'session_version' => 3,
339        'v'               => '1.0',
340      ), $params)
341    );
342  }
343
344  /**
345   * Get a Logout URL suitable for use with redirects.
346   *
347   * The parameters:
348   * - next: the url to go to after a successful logout
349   *
350   * @param Array $params provide custom parameters
351   * @return String the URL for the logout flow
352   */
353  public function getLogoutUrl($params=array()) {
354    $session = $this->getSession();
355    return $this->getUrl(
356      'www',
357      'logout.php',
358      array_merge(array(
359        'api_key'     => $this->getAppId(),
360        'next'        => $this->getCurrentUrl(),
361        'session_key' => $session['session_key'],
362      ), $params)
363    );
364  }
365
366  /**
367   * Get a login status URL to fetch the status from facebook.
368   *
369   * The parameters:
370   * - ok_session: the URL to go to if a session is found
371   * - no_session: the URL to go to if the user is not connected
372   * - no_user: the URL to go to if the user is not signed into facebook
373   *
374   * @param Array $params provide custom parameters
375   * @return String the URL for the logout flow
376   */
377  public function getLoginStatusUrl($params=array()) {
378    return $this->getUrl(
379      'www',
380      'extern/login_status.php',
381      array_merge(array(
382        'api_key'         => $this->getAppId(),
383        'no_session'      => $this->getCurrentUrl(),
384        'no_user'         => $this->getCurrentUrl(),
385        'ok_session'      => $this->getCurrentUrl(),
386        'session_version' => 3,
387      ), $params)
388    );
389  }
390
391  /**
392   * Make an API call.
393   *
394   * @param Array $params the API call parameters
395   * @return the decoded response
396   */
397  public function api(/* polymorphic */) {
398    $args = func_get_args();
399    if (is_array($args[0])) {
400      return $this->_restserver($args[0]);
401    } else {
402      return call_user_func_array(array($this, '_graph'), $args);
403    }
404  }
405
406  /**
407   * Invoke the old restserver.php endpoint.
408   *
409   * @param Array $params method call object
410   * @return the decoded response object
411   * @throws FacebookApiException
412   */
413  protected function _restserver($params) {
414    // generic application level parameters
415    $params['api_key'] = $this->getAppId();
416    $params['format'] = 'json-strings';
417
418    $result = json_decode($this->_oauthRequest(
419      $this->getApiUrl($params['method']),
420      $params
421    ), true);
422
423    // results are returned, errors are thrown
424    if (is_array($result) && isset($result['error_code'])) {
425      throw new FacebookApiException($result);
426    }
427    return $result;
428  }
429
430  /**
431   * Invoke the Graph API.
432   *
433   * @param String $path the path (required)
434   * @param String $method the http method (default 'GET')
435   * @param Array $params the query/post data
436   * @return the decoded response object
437   * @throws FacebookApiException
438   */
439  protected function _graph($path, $method='GET', $params=array()) {
440    if (is_array($method) && empty($params)) {
441      $params = $method;
442      $method = 'GET';
443    }
444    $params['method'] = $method; // method override as we always do a POST
445
446    $result = json_decode($this->_oauthRequest(
447      $this->getUrl('graph', $path),
448      $params
449    ), true);
450
451    // results are returned, errors are thrown
452    if (is_array($result) && isset($result['error'])) {
453      $e = new FacebookApiException($result);
454      if ($e->getType() === 'OAuthException') {
455        $this->setSession(null);
456      }
457      throw $e;
458    }
459    return $result;
460  }
461
462  /**
463   * Make a OAuth Request
464   *
465   * @param String $path the path (required)
466   * @param Array $params the query/post data
467   * @return the decoded response object
468   * @throws FacebookApiException
469   */
470  protected function _oauthRequest($url, $params) {
471    if (!isset($params['access_token'])) {
472      $session = $this->getSession();
473      // either user session signed, or app signed
474      if ($session) {
475        $params['access_token'] = $session['access_token'];
476      } else {
477        $params['access_token'] = $this->getAppId() .'|'. $this->getApiSecret();
478      }
479    }
480
481    // json_encode all params values that are not strings
482    foreach ($params as $key => $value) {
483      if (!is_string($value)) {
484        $params[$key] = json_encode($value);
485      }
486    }
487    return $this->makeRequest($url, $params);
488  }
489
490  /**
491   * Makes an HTTP request. This method can be overriden by subclasses if
492   * developers want to do fancier things or use something other than curl to
493   * make the request.
494   *
495   * @param String $url the URL to make the request to
496   * @param Array $params the parameters to use for the POST body
497   * @param CurlHandler $ch optional initialized curl handle
498   * @return String the response text
499   */
500  protected function makeRequest($url, $params, $ch=null) {
501    if (!$ch) {
502      $ch = curl_init();
503    }
504
505    $opts = self::$CURL_OPTS;
506    $opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&');
507    $opts[CURLOPT_URL] = $url;
508
509    // disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
510    // for 2 seconds if the server does not support this header.
511    if (isset($opts[CURLOPT_HTTPHEADER])) {
512      $existing_headers = $opts[CURLOPT_HTTPHEADER];
513      $existing_headers[] = 'Expect:';
514      $opts[CURLOPT_HTTPHEADER] = $existing_headers;
515    } else {
516      $opts[CURLOPT_HTTPHEADER] = array('Expect:');
517    }
518
519    curl_setopt_array($ch, $opts);
520    $result = curl_exec($ch);
521    if ($result === false) {
522      $e = new FacebookApiException(array(
523        'error_code' => curl_errno($ch),
524        'error'      => array(
525          'message' => curl_error($ch),
526          'type'    => 'CurlException',
527        ),
528      ));
529      curl_close($ch);
530      throw $e;
531    }
532    curl_close($ch);
533    return $result;
534  }
535
536  /**
537   * The name of the Cookie that contains the session.
538   *
539   * @return String the cookie name
540   */
541  protected function getSessionCookieName() {
542    return 'fbs_' . $this->getAppId();
543  }
544
545  /**
546   * Set a JS Cookie based on the _passed in_ session. It does not use the
547   * currently stored session -- you need to explicitly pass it in.
548   *
549   * @param Array $session the session to use for setting the cookie
550   */
551  protected function setCookieFromSession($session=null) {
552    if (!$this->useCookieSupport()) {
553      return;
554    }
555
556    $cookieName = $this->getSessionCookieName();
557    $value = 'deleted';
558    $expires = time() - 3600;
559    $domain = $this->getBaseDomain();
560    if ($session) {
561      $value = '"' . http_build_query($session, null, '&') . '"';
562      if (isset($session['base_domain'])) {
563        $domain = $session['base_domain'];
564      }
565      $expires = $session['expires'];
566    }
567
568    // prepend dot if a domain is found
569    if ($domain) {
570      $domain = '.' . $domain;
571    }
572
573    // if an existing cookie is not set, we dont need to delete it
574    if ($value == 'deleted' && empty($_COOKIE[$cookieName])) {
575      return;
576    }
577
578    if (headers_sent()) {
579      self::error_log('Could not set cookie. Headers already sent.');
580
581    // ignore for code coverage as we will never be able to setcookie in a CLI
582    // environment
583    // @codeCoverageIgnoreStart
584    } else {
585      setcookie($cookieName, $value, $expires, '/', $domain);
586    }
587    // @codeCoverageIgnoreEnd
588  }
589
590  /**
591   * Validates a session_version=3 style session object.
592   *
593   * @param Array $session the session object
594   * @return Array the session object if it validates, null otherwise
595   */
596  protected function validateSessionObject($session) {
597    // make sure some essential fields exist
598    if (is_array($session) &&
599        isset($session['uid']) &&
600        isset($session['session_key']) &&
601        isset($session['secret']) &&
602        isset($session['access_token']) &&
603        isset($session['sig'])) {
604      // validate the signature
605      $session_without_sig = $session;
606      unset($session_without_sig['sig']);
607      $expected_sig = self::generateSignature(
608        $session_without_sig,
609        $this->getApiSecret()
610      );
611      if ($session['sig'] != $expected_sig) {
612        self::error_log('Got invalid session signature in cookie.');
613        $session = null;
614      }
615      // check expiry time
616    } else {
617      $session = null;
618    }
619    return $session;
620  }
621
622  /**
623   * Build the URL for api given parameters.
624   *
625   * @param $method String the method name.
626   * @return String the URL for the given parameters
627   */
628  protected function getApiUrl($method) {
629    static $READ_ONLY_CALLS =
630      array('admin.getallocation' => 1,
631            'admin.getappproperties' => 1,
632            'admin.getbannedusers' => 1,
633            'admin.getlivestreamvialink' => 1,
634            'admin.getmetrics' => 1,
635            'admin.getrestrictioninfo' => 1,
636            'application.getpublicinfo' => 1,
637            'auth.getapppublickey' => 1,
638            'auth.getsession' => 1,
639            'auth.getsignedpublicsessiondata' => 1,
640            'comments.get' => 1,
641            'connect.getunconnectedfriendscount' => 1,
642            'dashboard.getactivity' => 1,
643            'dashboard.getcount' => 1,
644            'dashboard.getglobalnews' => 1,
645            'dashboard.getnews' => 1,
646            'dashboard.multigetcount' => 1,
647            'dashboard.multigetnews' => 1,
648            'data.getcookies' => 1,
649            'events.get' => 1,
650            'events.getmembers' => 1,
651            'fbml.getcustomtags' => 1,
652            'feed.getappfriendstories' => 1,
653            'feed.getregisteredtemplatebundlebyid' => 1,
654            'feed.getregisteredtemplatebundles' => 1,
655            'fql.multiquery' => 1,
656            'fql.query' => 1,
657            'friends.arefriends' => 1,
658            'friends.get' => 1,
659            'friends.getappusers' => 1,
660            'friends.getlists' => 1,
661            'friends.getmutualfriends' => 1,
662            'gifts.get' => 1,
663            'groups.get' => 1,
664            'groups.getmembers' => 1,
665            'intl.gettranslations' => 1,
666            'links.get' => 1,
667            'notes.get' => 1,
668            'notifications.get' => 1,
669            'pages.getinfo' => 1,
670            'pages.isadmin' => 1,
671            'pages.isappadded' => 1,
672            'pages.isfan' => 1,
673            'permissions.checkavailableapiaccess' => 1,
674            'permissions.checkgrantedapiaccess' => 1,
675            'photos.get' => 1,
676            'photos.getalbums' => 1,
677            'photos.gettags' => 1,
678            'profile.getinfo' => 1,
679            'profile.getinfooptions' => 1,
680            'stream.get' => 1,
681            'stream.getcomments' => 1,
682            'stream.getfilters' => 1,
683            'users.getinfo' => 1,
684            'users.getloggedinuser' => 1,
685            'users.getstandardinfo' => 1,
686            'users.hasapppermission' => 1,
687            'users.isappuser' => 1,
688            'users.isverified' => 1,
689            'video.getuploadlimits' => 1);
690    $name = 'api';
691    if (isset($READ_ONLY_CALLS[strtolower($method)])) {
692      $name = 'api_read';
693    }
694    return self::getUrl($name, 'restserver.php');
695  }
696
697  /**
698   * Build the URL for given domain alias, path and parameters.
699   *
700   * @param $name String the name of the domain
701   * @param $path String optional path (without a leading slash)
702   * @param $params Array optional query parameters
703   * @return String the URL for the given parameters
704   */
705  protected function getUrl($name, $path='', $params=array()) {
706    $url = self::$DOMAIN_MAP[$name];
707    if ($path) {
708      if ($path[0] === '/') {
709        $path = substr($path, 1);
710      }
711      $url .= $path;
712    }
713    if ($params) {
714      $url .= '?' . http_build_query($params, null, '&');
715    }
716    return $url;
717  }
718
719  /**
720   * Returns the Current URL, stripping it of known FB parameters that should
721   * not persist.
722   *
723   * @return String the current URL
724   */
725  protected function getCurrentUrl() {
726    $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'
727      ? 'https://'
728      : 'http://';
729    $currentUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
730    $parts = parse_url($currentUrl);
731
732    // drop known fb params
733    $query = '';
734    if (!empty($parts['query'])) {
735      $params = array();
736      parse_str($parts['query'], $params);
737      foreach(self::$DROP_QUERY_PARAMS as $key) {
738        unset($params[$key]);
739      }
740      if (!empty($params)) {
741        $query = '?' . http_build_query($params, null, '&');
742      }
743    }
744
745    // use port if non default
746    $port =
747      isset($parts['port']) &&
748      (($protocol === 'http://' && $parts['port'] !== 80) ||
749       ($protocol === 'https://' && $parts['port'] !== 443))
750      ? ':' . $parts['port'] : '';
751
752    // rebuild
753    return $protocol . $parts['host'] . $port . $parts['path'] . $query;
754  }
755
756  /**
757   * Generate a signature for the given params and secret.
758   *
759   * @param Array $params the parameters to sign
760   * @param String $secret the secret to sign with
761   * @return String the generated signature
762   */
763  protected static function generateSignature($params, $secret) {
764    // work with sorted data
765    ksort($params);
766
767    // generate the base string
768    $base_string = '';
769    foreach($params as $key => $value) {
770      $base_string .= $key . '=' . $value;
771    }
772    $base_string .= $secret;
773
774    return md5($base_string);
775  }
776
777  /**
778   * Prints to the error log if you aren't in command line mode.
779   *
780   * @param String log message
781   */
782  protected static function error_log($msg) {
783    // disable error log if we are running in a CLI environment
784    // @codeCoverageIgnoreStart
785    if (php_sapi_name() != 'cli') {
786      error_log($msg);
787    }
788    // uncomment this if you want to see the errors on the page
789    // print 'error_log: '.$msg."\n";
790    // @codeCoverageIgnoreEnd
791  }
792}
Note: See TracBrowser for help on using the browser.