comparison facebook.php @ 6:077b0a0a3e6d

remaining originals according to dependency walk
author Robert Boland <robert@markup.co.uk>
date Thu, 16 Feb 2017 22:29:02 +0000
parents
children
comparison
equal deleted inserted replaced
5:55445b456ad0 6:077b0a0a3e6d
1 <?php
2
3 if (!function_exists('curl_init')) {
4 throw new Exception('Facebook needs the CURL PHP extension.');
5 }
6 if (!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 */
15 class 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 */
77 class Facebook
78 {
79 /**
80 * Version.
81 */
82 const VERSION = '2.0.5';
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(urldecode($_REQUEST['session']))
273 : urldecode($_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 curl_setopt_array($ch, $opts);
509 $result = curl_exec($ch);
510 if ($result === false) {
511 $e = new FacebookApiException(array(
512 'error_code' => curl_errno($ch),
513 'error' => array(
514 'message' => curl_error($ch),
515 'type' => 'CurlException',
516 ),
517 ));
518 curl_close($ch);
519 throw $e;
520 }
521 curl_close($ch);
522 return $result;
523 }
524
525 /**
526 * The name of the Cookie that contains the session.
527 *
528 * @return String the cookie name
529 */
530 protected function getSessionCookieName() {
531 return 'fbs_' . $this->getAppId();
532 }
533
534 /**
535 * Set a JS Cookie based on the _passed in_ session. It does not use the
536 * currently stored session -- you need to explicitly pass it in.
537 *
538 * @param Array $session the session to use for setting the cookie
539 */
540 protected function setCookieFromSession($session=null) {
541 if (!$this->useCookieSupport()) {
542 return;
543 }
544
545 $cookieName = $this->getSessionCookieName();
546 $value = 'deleted';
547 $expires = time() - 3600;
548 $domain = $this->getBaseDomain();
549 if ($session) {
550 $value = '"' . http_build_query($session, null, '&') . '"';
551 if (isset($session['base_domain'])) {
552 $domain = $session['base_domain'];
553 }
554 $expires = $session['expires'];
555 }
556
557 // prepend dot if a domain is found
558 if ($domain) {
559 $domain = '.' . $domain;
560 }
561
562 // if an existing cookie is not set, we dont need to delete it
563 if ($value == 'deleted' && empty($_COOKIE[$cookieName])) {
564 return;
565 }
566
567 if (headers_sent()) {
568 // disable error log if we are running in a CLI environment
569 // @codeCoverageIgnoreStart
570 if (php_sapi_name() != 'cli') {
571 error_log('Could not set cookie. Headers already sent.');
572 }
573 // @codeCoverageIgnoreEnd
574
575 // ignore for code coverage as we will never be able to setcookie in a CLI
576 // environment
577 // @codeCoverageIgnoreStart
578 } else {
579 setcookie($cookieName, $value, $expires, '/', $domain);
580 }
581 // @codeCoverageIgnoreEnd
582 }
583
584 /**
585 * Validates a session_version=3 style session object.
586 *
587 * @param Array $session the session object
588 * @return Array the session object if it validates, null otherwise
589 */
590 protected function validateSessionObject($session) {
591 // make sure some essential fields exist
592 if (is_array($session) &&
593 isset($session['uid']) &&
594 isset($session['session_key']) &&
595 isset($session['secret']) &&
596 isset($session['access_token']) &&
597 isset($session['sig'])) {
598 // validate the signature
599 $session_without_sig = $session;
600 unset($session_without_sig['sig']);
601 $expected_sig = self::generateSignature(
602 $session_without_sig,
603 $this->getApiSecret()
604 );
605 if ($session['sig'] != $expected_sig) {
606 // disable error log if we are running in a CLI environment
607 // @codeCoverageIgnoreStart
608 if (php_sapi_name() != 'cli') {
609 error_log('Got invalid session signature in cookie.');
610 }
611 // @codeCoverageIgnoreEnd
612 $session = null;
613 }
614 // check expiry time
615 } else {
616 $session = null;
617 }
618 return $session;
619 }
620
621 /**
622 * Build the URL for api given parameters.
623 *
624 * @param $method String the method name.
625 * @return String the URL for the given parameters
626 */
627 protected function getApiUrl($method) {
628 static $READ_ONLY_CALLS =
629 array('admin.getallocation' => 1,
630 'admin.getappproperties' => 1,
631 'admin.getbannedusers' => 1,
632 'admin.getlivestreamvialink' => 1,
633 'admin.getmetrics' => 1,
634 'admin.getrestrictioninfo' => 1,
635 'application.getpublicinfo' => 1,
636 'auth.getapppublickey' => 1,
637 'auth.getsession' => 1,
638 'auth.getsignedpublicsessiondata' => 1,
639 'comments.get' => 1,
640 'connect.getunconnectedfriendscount' => 1,
641 'dashboard.getactivity' => 1,
642 'dashboard.getcount' => 1,
643 'dashboard.getglobalnews' => 1,
644 'dashboard.getnews' => 1,
645 'dashboard.multigetcount' => 1,
646 'dashboard.multigetnews' => 1,
647 'data.getcookies' => 1,
648 'events.get' => 1,
649 'events.getmembers' => 1,
650 'fbml.getcustomtags' => 1,
651 'feed.getappfriendstories' => 1,
652 'feed.getregisteredtemplatebundlebyid' => 1,
653 'feed.getregisteredtemplatebundles' => 1,
654 'fql.multiquery' => 1,
655 'fql.query' => 1,
656 'friends.arefriends' => 1,
657 'friends.get' => 1,
658 'friends.getappusers' => 1,
659 'friends.getlists' => 1,
660 'friends.getmutualfriends' => 1,
661 'gifts.get' => 1,
662 'groups.get' => 1,
663 'groups.getmembers' => 1,
664 'intl.gettranslations' => 1,
665 'links.get' => 1,
666 'notes.get' => 1,
667 'notifications.get' => 1,
668 'pages.getinfo' => 1,
669 'pages.isadmin' => 1,
670 'pages.isappadded' => 1,
671 'pages.isfan' => 1,
672 'permissions.checkavailableapiaccess' => 1,
673 'permissions.checkgrantedapiaccess' => 1,
674 'photos.get' => 1,
675 'photos.getalbums' => 1,
676 'photos.gettags' => 1,
677 'profile.getinfo' => 1,
678 'profile.getinfooptions' => 1,
679 'stream.get' => 1,
680 'stream.getcomments' => 1,
681 'stream.getfilters' => 1,
682 'users.getinfo' => 1,
683 'users.getloggedinuser' => 1,
684 'users.getstandardinfo' => 1,
685 'users.hasapppermission' => 1,
686 'users.isappuser' => 1,
687 'users.isverified' => 1,
688 'video.getuploadlimits' => 1);
689 $name = 'api';
690 if (isset($READ_ONLY_CALLS[strtolower($method)])) {
691 $name = 'api_read';
692 }
693 return self::getUrl($name, 'restserver.php');
694 }
695
696 /**
697 * Build the URL for given domain alias, path and parameters.
698 *
699 * @param $name String the name of the domain
700 * @param $path String optional path (without a leading slash)
701 * @param $params Array optional query parameters
702 * @return String the URL for the given parameters
703 */
704 protected function getUrl($name, $path='', $params=array()) {
705 $url = self::$DOMAIN_MAP[$name];
706 if ($path) {
707 if ($path[0] === '/') {
708 $path = substr($path, 1);
709 }
710 $url .= $path;
711 }
712 if ($params) {
713 $url .= '?' . http_build_query($params);
714 }
715 return $url;
716 }
717
718 /**
719 * Returns the Current URL, stripping it of known FB parameters that should
720 * not persist.
721 *
722 * @return String the current URL
723 */
724 protected function getCurrentUrl() {
725 $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'
726 ? 'https://'
727 : 'http://';
728 $currentUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
729 $parts = parse_url($currentUrl);
730
731 // drop known fb params
732 $query = '';
733 if (!empty($parts['query'])) {
734 $params = array();
735 parse_str($parts['query'], $params);
736 foreach(self::$DROP_QUERY_PARAMS as $key) {
737 unset($params[$key]);
738 }
739 if (!empty($params)) {
740 $query = '?' . http_build_query($params);
741 }
742 }
743
744 // use port if non default
745 $port =
746 isset($parts['port']) &&
747 (($protocol === 'http://' && $parts['port'] !== 80) ||
748 ($protocol === 'https://' && $parts['port'] !== 443))
749 ? ':' . $parts['port'] : '';
750
751 // rebuild
752 return $protocol . $parts['host'] . $port . $parts['path'] . $query;
753 }
754
755 /**
756 * Generate a signature for the given params and secret.
757 *
758 * @param Array $params the parameters to sign
759 * @param String $secret the secret to sign with
760 * @return String the generated signature
761 */
762 protected static function generateSignature($params, $secret) {
763 // work with sorted data
764 ksort($params);
765
766 // generate the base string
767 $base_string = '';
768 foreach($params as $key => $value) {
769 $base_string .= $key . '=' . $value;
770 }
771 $base_string .= $secret;
772
773 return md5($base_string);
774 }
775 }