ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
base_facebook.php
Go to the documentation of this file.
1<?php
18if (!function_exists('curl_init')) {
19 throw new Exception('Facebook needs the CURL PHP extension.');
20}
21if (!function_exists('json_decode')) {
22 throw new Exception('Facebook needs the JSON PHP extension.');
23}
24
30class FacebookApiException extends Exception
31{
35 protected $result;
36
42 public function __construct($result) {
43 $this->result = $result;
44
45 $code = isset($result['error_code']) ? $result['error_code'] : 0;
46
47 if (isset($result['error_description'])) {
48 // OAuth 2.0 Draft 10 style
49 $msg = $result['error_description'];
50 } else if (isset($result['error']) && is_array($result['error'])) {
51 // OAuth 2.0 Draft 00 style
52 $msg = $result['error']['message'];
53 } else if (isset($result['error_msg'])) {
54 // Rest server style
55 $msg = $result['error_msg'];
56 } else {
57 $msg = 'Unknown Error. Check getResult()';
58 }
59
60 parent::__construct($msg, $code);
61 }
62
68 public function getResult() {
69 return $this->result;
70 }
71
78 public function getType() {
79 if (isset($this->result['error'])) {
80 $error = $this->result['error'];
81 if (is_string($error)) {
82 // OAuth 2.0 Draft 10 style
83 return $error;
84 } else if (is_array($error)) {
85 // OAuth 2.0 Draft 00 style
86 if (isset($error['type'])) {
87 return $error['type'];
88 }
89 }
90 }
91
92 return 'Exception';
93 }
94
100 public function __toString() {
101 $str = $this->getType() . ': ';
102 if ($this->code != 0) {
103 $str .= $this->code . ': ';
104 }
105 return $str . $this->message;
106 }
107}
108
118abstract class BaseFacebook
119{
123 const VERSION = '3.2.2';
124
128 const SIGNED_REQUEST_ALGORITHM = 'HMAC-SHA256';
129
133 public static $CURL_OPTS = array(
134 CURLOPT_CONNECTTIMEOUT => 10,
135 CURLOPT_RETURNTRANSFER => true,
136 CURLOPT_TIMEOUT => 60,
137 CURLOPT_USERAGENT => 'facebook-php-3.2',
138 );
139
144 protected static $DROP_QUERY_PARAMS = array(
145 'code',
146 'state',
147 'signed_request',
148 );
149
153 public static $DOMAIN_MAP = array(
154 'api' => 'https://api.facebook.com/',
155 'api_video' => 'https://api-video.facebook.com/',
156 'api_read' => 'https://api-read.facebook.com/',
157 'graph' => 'https://graph.facebook.com/',
158 'graph_video' => 'https://graph-video.facebook.com/',
159 'www' => 'https://www.facebook.com/',
160 );
161
167 protected $appId;
168
174 protected $appSecret;
175
181 protected $user;
182
186 protected $signedRequest;
187
191 protected $state;
192
199 protected $accessToken = null;
200
206 protected $fileUploadSupport = false;
207
213 protected $trustForwarded = false;
214
225 public function __construct($config) {
226 $this->setAppId($config['appId']);
227 $this->setAppSecret($config['secret']);
228 if (isset($config['fileUpload'])) {
229 $this->setFileUploadSupport($config['fileUpload']);
230 }
231 if (isset($config['trustForwarded']) && $config['trustForwarded']) {
232 $this->trustForwarded = true;
233 }
234 $state = $this->getPersistentData('state');
235 if (!empty($state)) {
236 $this->state = $state;
237 }
238 }
239
246 public function setAppId($appId) {
247 $this->appId = $appId;
248 return $this;
249 }
250
256 public function getAppId() {
257 return $this->appId;
258 }
259
267 public function setApiSecret($apiSecret) {
268 $this->setAppSecret($apiSecret);
269 return $this;
270 }
271
278 public function setAppSecret($appSecret) {
279 $this->appSecret = $appSecret;
280 return $this;
281 }
282
289 public function getApiSecret() {
290 return $this->getAppSecret();
291 }
292
298 public function getAppSecret() {
299 return $this->appSecret;
300 }
301
309 $this->fileUploadSupport = $fileUploadSupport;
310 return $this;
311 }
312
318 public function getFileUploadSupport() {
320 }
321
329 public function useFileUploadSupport() {
330 return $this->getFileUploadSupport();
331 }
332
341 public function setAccessToken($access_token) {
342 $this->accessToken = $access_token;
343 return $this;
344 }
345
351 public function setExtendedAccessToken() {
352 try {
353 // need to circumvent json_decode by calling _oauthRequest
354 // directly, since response isn't JSON format
355 $access_token_response = $this->_oauthRequest(
356 $this->getUrl('graph', '/oauth/access_token'),
357 $params = array(
358 'client_id' => $this->getAppId(),
359 'client_secret' => $this->getAppSecret(),
360 'grant_type' => 'fb_exchange_token',
361 'fb_exchange_token' => $this->getAccessToken(),
362 )
363 );
364 }
365 catch (FacebookApiException $e) {
366 // most likely that user very recently revoked authorization
367 // In any event, we don't have an access token, so say so
368 return false;
369 }
370
371 if (empty($access_token_response)) {
372 return false;
373 }
374
375 $response_params = array();
376 parse_str($access_token_response, $response_params);
377
378 if (!isset($response_params['access_token'])) {
379 return false;
380 }
381
382 $this->destroySession();
383
384 $this->setPersistentData(
385 'access_token', $response_params['access_token']
386 );
387 }
388
398 public function getAccessToken() {
399 if ($this->accessToken !== null) {
400 // we've done this already and cached it. Just return.
401 return $this->accessToken;
402 }
403
404 // first establish access token to be the application
405 // access token, in case we navigate to the /oauth/access_token
406 // endpoint, where SOME access token is required
408 $user_access_token = $this->getUserAccessToken();
409 if ($user_access_token) {
410 $this->setAccessToken($user_access_token);
411 }
412
413 return $this->accessToken;
414 }
415
426 protected function getUserAccessToken() {
427 // first, consider a signed request if it's supplied
428 // if there is a signed request, then it alone determines
429 // the access token
430 $signed_request = $this->getSignedRequest();
431 if ($signed_request) {
432 // apps.facebook.com hands the access_token in the signed_request
433 if (array_key_exists('oauth_token', $signed_request)) {
434 $access_token = $signed_request['oauth_token'];
435 $this->setPersistentData('access_token', $access_token);
436 return $access_token;
437 }
438
439 // the JS SDK puts a code in with the redirect_uri of ''
440 if (array_key_exists('code', $signed_request)) {
441 $code = $signed_request['code'];
442 if ($code && $code == $this->getPersistentData('code')) {
443 // short-circuit if the code we have is the same as the one presented
444 return $this->getPersistentData('access_token');
445 }
446
447 $access_token = $this->getAccessTokenFromCode($code, '');
448 if ($access_token) {
449 $this->setPersistentData('code', $code);
450 $this->setPersistentData('access_token', $access_token);
451 return $access_token;
452 }
453 }
454
455 // signed request states there's no access token, so anything
456 // stored should be cleared
457 $this->clearAllPersistentData();
458 return false; // respect the signed request's data, even
459 // if there's an authorization code or something else
460 }
461
462 $code = $this->getCode();
463 if ($code && $code != $this->getPersistentData('code')) {
464 $access_token = $this->getAccessTokenFromCode($code);
465 if ($access_token) {
466 $this->setPersistentData('code', $code);
467 $this->setPersistentData('access_token', $access_token);
468 return $access_token;
469 }
470
471 // code was bogus, so everything based on it should be invalidated
472 $this->clearAllPersistentData();
473 return false;
474 }
475
476 // as a fallback, just return whatever is in the persistent
477 // store, knowing nothing explicit (signed request, authorization
478 // code, etc.) was present to shadow it (or we saw a code in $_REQUEST,
479 // but it's the same as what's in the persistent store)
480 return $this->getPersistentData('access_token');
481 }
482
489 public function getSignedRequest() {
490 if (!$this->signedRequest) {
491 if (!empty($_REQUEST['signed_request'])) {
492 $this->signedRequest = $this->parseSignedRequest(
493 $_REQUEST['signed_request']);
494 } else if (!empty($_COOKIE[$this->getSignedRequestCookieName()])) {
495 $this->signedRequest = $this->parseSignedRequest(
497 }
498 }
500 }
501
508 public function getUser() {
509 if ($this->user !== null) {
510 // we've already determined this and cached the value
511 return $this->user;
512 }
513
514 return $this->user = $this->getUserFromAvailableData();
515 }
516
525 protected function getUserFromAvailableData() {
526 // if a signed request is supplied, then it solely determines
527 // who the user is
528 $signed_request = $this->getSignedRequest();
529 if ($signed_request) {
530 if (array_key_exists('user_id', $signed_request)) {
531 $user = $signed_request['user_id'];
532
533 if($user != $this->getPersistentData('user_id')){
534 $this->clearAllPersistentData();
535 }
536
537 $this->setPersistentData('user_id', $signed_request['user_id']);
538 return $user;
539 }
540
541 // if the signed request didn't present a user id, then invalidate
542 // all entries in any persistent store
543 $this->clearAllPersistentData();
544 return 0;
545 }
546
547 $user = $this->getPersistentData('user_id', $default = 0);
548 $persisted_access_token = $this->getPersistentData('access_token');
549
550 // use access_token to fetch user id if we have a user access_token, or if
551 // the cached access token has changed
552 $access_token = $this->getAccessToken();
553 if ($access_token &&
554 $access_token != $this->getApplicationAccessToken() &&
555 !($user && $persisted_access_token == $access_token)) {
556 $user = $this->getUserFromAccessToken();
557 if ($user) {
558 $this->setPersistentData('user_id', $user);
559 } else {
560 $this->clearAllPersistentData();
561 }
562 }
563
564 return $user;
565 }
566
579 public function getLoginUrl($params=array()) {
581 $currentUrl = $this->getCurrentUrl();
582
583 // if 'scope' is passed as an array, convert to comma separated list
584 $scopeParams = isset($params['scope']) ? $params['scope'] : null;
585 if ($scopeParams && is_array($scopeParams)) {
586 $params['scope'] = implode(',', $scopeParams);
587 }
588
589 return $this->getUrl(
590 'www',
591 'dialog/oauth',
592 array_merge(array(
593 'client_id' => $this->getAppId(),
594 'redirect_uri' => $currentUrl, // possibly overwritten
595 'state' => $this->state),
596 $params));
597 }
598
608 public function getLogoutUrl($params=array()) {
609 return $this->getUrl(
610 'www',
611 'logout.php',
612 array_merge(array(
613 'next' => $this->getCurrentUrl(),
614 'access_token' => $this->getUserAccessToken(),
615 ), $params)
616 );
617 }
618
630 public function getLoginStatusUrl($params=array()) {
631 return $this->getUrl(
632 'www',
633 'extern/login_status.php',
634 array_merge(array(
635 'api_key' => $this->getAppId(),
636 'no_session' => $this->getCurrentUrl(),
637 'no_user' => $this->getCurrentUrl(),
638 'ok_session' => $this->getCurrentUrl(),
639 'session_version' => 3,
640 ), $params)
641 );
642 }
643
649 public function api(/* polymorphic */) {
650 $args = func_get_args();
651 if (is_array($args[0])) {
652 return $this->_restserver($args[0]);
653 } else {
654 return call_user_func_array(array($this, '_graph'), $args);
655 }
656 }
657
667 protected function getSignedRequestCookieName() {
668 return 'fbsr_'.$this->getAppId();
669 }
670
678 protected function getMetadataCookieName() {
679 return 'fbm_'.$this->getAppId();
680 }
681
690 protected function getCode() {
691 if (isset($_REQUEST['code'])) {
692 if ($this->state !== null &&
693 isset($_REQUEST['state']) &&
694 $this->state === $_REQUEST['state']) {
695
696 // CSRF state has done its job, so clear it
697 $this->state = null;
698 $this->clearPersistentData('state');
699 return $_REQUEST['code'];
700 } else {
701 self::errorLog('CSRF state token does not match one provided. ' . $this->state . '!=' . $_REQUEST['state']);
702 return false;
703 }
704 }
705
706 return false;
707 }
708
719 protected function getUserFromAccessToken() {
720 try {
721 $user_info = $this->api('/me');
722 return $user_info['id'];
723 } catch (FacebookApiException $e) {
724 return 0;
725 }
726 }
727
735 protected function getApplicationAccessToken() {
736 return $this->appId.'|'.$this->appSecret;
737 }
738
744 protected function establishCSRFTokenState() {
745 if ($this->state === null) {
746 $this->state = md5(uniqid(mt_rand(), true));
747 $this->setPersistentData('state', $this->state);
748 }
749 }
750
763 protected function getAccessTokenFromCode($code, $redirect_uri = null) {
764 if (empty($code)) {
765 return false;
766 }
767
768 if ($redirect_uri === null) {
769 $redirect_uri = $this->getCurrentUrl();
770 }
771
772 try {
773 // need to circumvent json_decode by calling _oauthRequest
774 // directly, since response isn't JSON format
775 $access_token_response =
776 $this->_oauthRequest(
777 $this->getUrl('graph', '/oauth/access_token'),
778 $params = array('client_id' => $this->getAppId(),
779 'client_secret' => $this->getAppSecret(),
780 'redirect_uri' => $redirect_uri,
781 'code' => $code));
782 } catch (FacebookApiException $e) {
783 // most likely that user very recently revoked authorization.
784 // In any event, we don't have an access token, so say so.
785 return false;
786 }
787
788 if (empty($access_token_response)) {
789 return false;
790 }
791
792 $response_params = json_decode($access_token_response, true);
793 if (!isset($response_params['access_token'])) {
794 return false;
795 }
796
797 return $response_params['access_token'];
798 }
799
808 protected function _restserver($params) {
809 // generic application level parameters
810 $params['api_key'] = $this->getAppId();
811 $params['format'] = 'json-strings';
812
813 $result = json_decode($this->_oauthRequest(
814 $this->getApiUrl($params['method']),
815 $params
816 ), true);
817
818 // results are returned, errors are thrown
819 if (is_array($result) && isset($result['error_code'])) {
821 // @codeCoverageIgnoreStart
822 }
823 // @codeCoverageIgnoreEnd
824
825 $method = strtolower($params['method']);
826 if ($method === 'auth.expiresession' ||
827 $method === 'auth.revokeauthorization') {
828 $this->destroySession();
829 }
830
831 return $result;
832 }
833
842 protected function isVideoPost($path, $method = 'GET') {
843 if ($method == 'POST' && preg_match("/^(\/)(.+)(\/)(videos)$/", $path)) {
844 return true;
845 }
846 return false;
847 }
848
859 protected function _graph($path, $method = 'GET', $params = array()) {
860 if (is_array($method) && empty($params)) {
861 $params = $method;
862 $method = 'GET';
863 }
864 $params['method'] = $method; // method override as we always do a POST
865
866 if ($this->isVideoPost($path, $method)) {
867 $domainKey = 'graph_video';
868 } else {
869 $domainKey = 'graph';
870 }
871
872 $result = json_decode($this->_oauthRequest(
873 $this->getUrl($domainKey, $path),
874 $params
875 ), true);
876
877 // results are returned, errors are thrown
878 if (is_array($result) && isset($result['error'])) {
880 // @codeCoverageIgnoreStart
881 }
882 // @codeCoverageIgnoreEnd
883
884 return $result;
885 }
886
896 protected function _oauthRequest($url, $params) {
897 if (!isset($params['access_token'])) {
898 $params['access_token'] = $this->getAccessToken();
899 }
900
901 // json_encode all params values that are not strings
902 foreach ($params as $key => $value) {
903 if (!is_string($value)) {
904 $params[$key] = json_encode($value);
905 }
906 }
907
908 return $this->makeRequest($url, $params);
909 }
910
922 protected function makeRequest($url, $params, $ch=null) {
923 if (!$ch) {
924 $ch = curl_init();
925 }
926
927 $opts = self::$CURL_OPTS;
928 if ($this->getFileUploadSupport()) {
929 $opts[CURLOPT_POSTFIELDS] = $params;
930 } else {
931 $opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&');
932 }
933 $opts[CURLOPT_URL] = $url;
934
935 // disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
936 // for 2 seconds if the server does not support this header
937 if (isset($opts[CURLOPT_HTTPHEADER])) {
938 $existing_headers = $opts[CURLOPT_HTTPHEADER];
939 $existing_headers[] = 'Expect:';
940 $opts[CURLOPT_HTTPHEADER] = $existing_headers;
941 } else {
942 $opts[CURLOPT_HTTPHEADER] = array('Expect:');
943 }
944
945 curl_setopt_array($ch, $opts);
946 $result = curl_exec($ch);
947
948 if (curl_errno($ch) == 60) { // CURLE_SSL_CACERT
949 self::errorLog('Invalid or no certificate authority found, '.
950 'using bundled information');
951 curl_setopt($ch, CURLOPT_CAINFO,
952 dirname(__FILE__) . '/fb_ca_chain_bundle.crt');
953 $result = curl_exec($ch);
954 }
955
956 // With dual stacked DNS responses, it's possible for a server to
957 // have IPv6 enabled but not have IPv6 connectivity. If this is
958 // the case, curl will try IPv4 first and if that fails, then it will
959 // fall back to IPv6 and the error EHOSTUNREACH is returned by the
960 // operating system
961 if ($result === false && empty($opts[CURLOPT_IPRESOLVE])) {
962 $matches = array();
963 $regex = '/Failed to connect to ([^:].*): Network is unreachable/';
964 if (preg_match($regex, curl_error($ch), $matches)) {
965 if (strlen(@inet_pton($matches[1])) === 16) {
966 self::errorLog('Invalid IPv6 configuration on server, '.
967 'Please disable or get native IPv6 on your server.');
968 self::$CURL_OPTS[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
969 curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
970 $result = curl_exec($ch);
971 }
972 }
973 }
974
975 if ($result === false) {
976 $e = new FacebookApiException(array(
977 'error_code' => curl_errno($ch),
978 'error' => array(
979 'message' => curl_error($ch),
980 'type' => 'CurlException',
981 ),
982 ));
983 curl_close($ch);
984 throw $e;
985 }
986 curl_close($ch);
987 return $result;
988 }
989
996 protected function parseSignedRequest($signed_request) {
997 list($encoded_sig, $payload) = explode('.', $signed_request, 2);
998
999 // decode the data
1000 $sig = self::base64UrlDecode($encoded_sig);
1001 $data = json_decode(self::base64UrlDecode($payload), true);
1002
1003 if (strtoupper($data['algorithm']) !== self::SIGNED_REQUEST_ALGORITHM) {
1005 'Unknown algorithm. Expected ' . self::SIGNED_REQUEST_ALGORITHM);
1006 return null;
1007 }
1008
1009 // check sig
1010 $expected_sig = hash_hmac('sha256', $payload,
1011 $this->getAppSecret(), $raw = true);
1012 if ($sig !== $expected_sig) {
1013 self::errorLog('Bad Signed JSON signature!');
1014 return null;
1015 }
1016
1017 return $data;
1018 }
1019
1026 protected function makeSignedRequest($data) {
1027 if (!is_array($data)) {
1028 throw new InvalidArgumentException(
1029 'makeSignedRequest expects an array. Got: ' . print_r($data, true));
1030 }
1032 $data['issued_at'] = time();
1033 $json = json_encode($data);
1034 $b64 = self::base64UrlEncode($json);
1035 $raw_sig = hash_hmac('sha256', $b64, $this->getAppSecret(), $raw = true);
1036 $sig = self::base64UrlEncode($raw_sig);
1037 return $sig.'.'.$b64;
1038 }
1039
1046 protected function getApiUrl($method) {
1047 static $READ_ONLY_CALLS =
1048 array('admin.getallocation' => 1,
1049 'admin.getappproperties' => 1,
1050 'admin.getbannedusers' => 1,
1051 'admin.getlivestreamvialink' => 1,
1052 'admin.getmetrics' => 1,
1053 'admin.getrestrictioninfo' => 1,
1054 'application.getpublicinfo' => 1,
1055 'auth.getapppublickey' => 1,
1056 'auth.getsession' => 1,
1057 'auth.getsignedpublicsessiondata' => 1,
1058 'comments.get' => 1,
1059 'connect.getunconnectedfriendscount' => 1,
1060 'dashboard.getactivity' => 1,
1061 'dashboard.getcount' => 1,
1062 'dashboard.getglobalnews' => 1,
1063 'dashboard.getnews' => 1,
1064 'dashboard.multigetcount' => 1,
1065 'dashboard.multigetnews' => 1,
1066 'data.getcookies' => 1,
1067 'events.get' => 1,
1068 'events.getmembers' => 1,
1069 'fbml.getcustomtags' => 1,
1070 'feed.getappfriendstories' => 1,
1071 'feed.getregisteredtemplatebundlebyid' => 1,
1072 'feed.getregisteredtemplatebundles' => 1,
1073 'fql.multiquery' => 1,
1074 'fql.query' => 1,
1075 'friends.arefriends' => 1,
1076 'friends.get' => 1,
1077 'friends.getappusers' => 1,
1078 'friends.getlists' => 1,
1079 'friends.getmutualfriends' => 1,
1080 'gifts.get' => 1,
1081 'groups.get' => 1,
1082 'groups.getmembers' => 1,
1083 'intl.gettranslations' => 1,
1084 'links.get' => 1,
1085 'notes.get' => 1,
1086 'notifications.get' => 1,
1087 'pages.getinfo' => 1,
1088 'pages.isadmin' => 1,
1089 'pages.isappadded' => 1,
1090 'pages.isfan' => 1,
1091 'permissions.checkavailableapiaccess' => 1,
1092 'permissions.checkgrantedapiaccess' => 1,
1093 'photos.get' => 1,
1094 'photos.getalbums' => 1,
1095 'photos.gettags' => 1,
1096 'profile.getinfo' => 1,
1097 'profile.getinfooptions' => 1,
1098 'stream.get' => 1,
1099 'stream.getcomments' => 1,
1100 'stream.getfilters' => 1,
1101 'users.getinfo' => 1,
1102 'users.getloggedinuser' => 1,
1103 'users.getstandardinfo' => 1,
1104 'users.hasapppermission' => 1,
1105 'users.isappuser' => 1,
1106 'users.isverified' => 1,
1107 'video.getuploadlimits' => 1);
1108 $name = 'api';
1109 if (isset($READ_ONLY_CALLS[strtolower($method)])) {
1110 $name = 'api_read';
1111 } else if (strtolower($method) == 'video.upload') {
1112 $name = 'api_video';
1113 }
1114 return self::getUrl($name, 'restserver.php');
1115 }
1116
1126 protected function getUrl($name, $path='', $params=array()) {
1127 $url = self::$DOMAIN_MAP[$name];
1128 if ($path) {
1129 if ($path[0] === '/') {
1130 $path = substr($path, 1);
1131 }
1132 $url .= $path;
1133 }
1134 if ($params) {
1135 $url .= '?' . http_build_query($params, null, '&');
1136 }
1137
1138 return $url;
1139 }
1140
1141 protected function getHttpHost() {
1142 if ($this->trustForwarded && isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
1143 return $_SERVER['HTTP_X_FORWARDED_HOST'];
1144 }
1145 return $_SERVER['HTTP_HOST'];
1146 }
1147
1148 protected function getHttpProtocol() {
1149 if ($this->trustForwarded && isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
1150 if ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
1151 return 'https';
1152 }
1153 return 'http';
1154 }
1155 /*apache + variants specific way of checking for https*/
1156 if (isset($_SERVER['HTTPS']) &&
1157 ($_SERVER['HTTPS'] === 'on' || $_SERVER['HTTPS'] == 1)) {
1158 return 'https';
1159 }
1160 /*nginx way of checking for https*/
1161 if (isset($_SERVER['SERVER_PORT']) &&
1162 ($_SERVER['SERVER_PORT'] === '443')) {
1163 return 'https';
1164 }
1165 return 'http';
1166 }
1167
1171 protected function getBaseDomain() {
1172 // The base domain is stored in the metadata cookie if not we fallback
1173 // to the current hostname
1174 $metadata = $this->getMetadataCookie();
1175 if (array_key_exists('base_domain', $metadata) &&
1176 !empty($metadata['base_domain'])) {
1177 return trim($metadata['base_domain'], '.');
1178 }
1179 return $this->getHttpHost();
1180 }
1181
1188 protected function getCurrentUrl() {
1189 $protocol = $this->getHttpProtocol() . '://';
1190 $host = $this->getHttpHost();
1191 $currentUrl = $protocol.$host.$_SERVER['REQUEST_URI'];
1192 $parts = parse_url($currentUrl);
1193
1194 $query = '';
1195 if (!empty($parts['query'])) {
1196 // drop known fb params
1197 $params = explode('&', $parts['query']);
1198 $retained_params = array();
1199 foreach ($params as $param) {
1200 if ($this->shouldRetainParam($param)) {
1201 $retained_params[] = $param;
1202 }
1203 }
1204
1205 if (!empty($retained_params)) {
1206 $query = '?'.implode($retained_params, '&');
1207 }
1208 }
1209
1210 // use port if non default
1211 $port =
1212 isset($parts['port']) &&
1213 (($protocol === 'http://' && $parts['port'] !== 80) ||
1214 ($protocol === 'https://' && $parts['port'] !== 443))
1215 ? ':' . $parts['port'] : '';
1216
1217 // rebuild
1218 return $protocol . $parts['host'] . $port . $parts['path'] . $query;
1219 }
1220
1232 protected function shouldRetainParam($param) {
1233 foreach (self::$DROP_QUERY_PARAMS as $drop_query_param) {
1234 if (strpos($param, $drop_query_param.'=') === 0) {
1235 return false;
1236 }
1237 }
1238
1239 return true;
1240 }
1241
1250 protected function throwAPIException($result) {
1251 $e = new FacebookApiException($result);
1252 switch ($e->getType()) {
1253 // OAuth 2.0 Draft 00 style
1254 case 'OAuthException':
1255 // OAuth 2.0 Draft 10 style
1256 case 'invalid_token':
1257 // REST server errors are just Exceptions
1258 case 'Exception':
1259 $message = $e->getMessage();
1260 if ((strpos($message, 'Error validating access token') !== false) ||
1261 (strpos($message, 'Invalid OAuth access token') !== false) ||
1262 (strpos($message, 'An active access token must be used') !== false)
1263 ) {
1264 $this->destroySession();
1265 }
1266 break;
1267 }
1268
1269 throw $e;
1270 }
1271
1272
1278 protected static function errorLog($msg) {
1279 // disable error log if we are running in a CLI environment
1280 // @codeCoverageIgnoreStart
1281 if (php_sapi_name() != 'cli') {
1282 error_log($msg);
1283 }
1284 // @codeCoverageIgnoreEnd
1285 }
1286
1297 protected static function base64UrlDecode($input) {
1298 return base64_decode(strtr($input, '-_', '+/'));
1299 }
1300
1310 protected static function base64UrlEncode($input) {
1311 $str = strtr(base64_encode($input), '+/', '-_');
1312 $str = str_replace('=', '', $str);
1313 return $str;
1314 }
1315
1319 public function destroySession() {
1320 $this->accessToken = null;
1321 $this->signedRequest = null;
1322 $this->user = null;
1323 $this->clearAllPersistentData();
1324
1325 // Javascript sets a cookie that will be used in getSignedRequest that we
1326 // need to clear if we can
1327 $cookie_name = $this->getSignedRequestCookieName();
1328 if (array_key_exists($cookie_name, $_COOKIE)) {
1329 unset($_COOKIE[$cookie_name]);
1330 if (!headers_sent()) {
1331 $base_domain = $this->getBaseDomain();
1332 setcookie($cookie_name, '', 1, '/', '.'.$base_domain);
1333 } else {
1334 // @codeCoverageIgnoreStart
1336 'There exists a cookie that we wanted to clear that we couldn\'t '.
1337 'clear because headers was already sent. Make sure to do the first '.
1338 'API call before outputing anything.'
1339 );
1340 // @codeCoverageIgnoreEnd
1341 }
1342 }
1343 }
1344
1350 protected function getMetadataCookie() {
1351 $cookie_name = $this->getMetadataCookieName();
1352 if (!array_key_exists($cookie_name, $_COOKIE)) {
1353 return array();
1354 }
1355
1356 // The cookie value can be wrapped in "-characters so remove them
1357 $cookie_value = trim($_COOKIE[$cookie_name], '"');
1358
1359 if (empty($cookie_value)) {
1360 return array();
1361 }
1362
1363 $parts = explode('&', $cookie_value);
1364 $metadata = array();
1365 foreach ($parts as $part) {
1366 $pair = explode('=', $part, 2);
1367 if (!empty($pair[0])) {
1368 $metadata[urldecode($pair[0])] =
1369 (count($pair) > 1) ? urldecode($pair[1]) : '';
1370 }
1371 }
1372
1373 return $metadata;
1374 }
1375
1376 protected static function isAllowedDomain($big, $small) {
1377 if ($big === $small) {
1378 return true;
1379 }
1380 return self::endsWith($big, '.'.$small);
1381 }
1382
1383 protected static function endsWith($big, $small) {
1384 $len = strlen($small);
1385 if ($len === 0) {
1386 return true;
1387 }
1388 return substr($big, -$len) === $small;
1389 }
1390
1410 abstract protected function setPersistentData($key, $value);
1411
1420 abstract protected function getPersistentData($key, $default = false);
1421
1428 abstract protected function clearPersistentData($key);
1429
1435 abstract protected function clearAllPersistentData();
1436}
$result
user()
Definition: user.php:4
$_COOKIE['client_id']
Definition: server.php:9
$metadata['__DYNAMIC:1__']
Provides access to the Facebook Platform.
_graph($path, $method='GET', $params=array())
Invoke the Graph API.
parseSignedRequest($signed_request)
Parses a signed_request and validates the signature.
static $DOMAIN_MAP
Maps aliases to Facebook domains.
getApplicationAccessToken()
Returns the access token that should be used for logged out users when no authorization code is avail...
getAppId()
Get the Application ID.
makeSignedRequest($data)
Makes a signed_request blob using the given data.
getAccessToken()
Determines the access token that should be used for API calls.
useFileUploadSupport()
DEPRECATED! Please use getFileUploadSupport instead.
api()
Make an API call.
setPersistentData($key, $value)
Each of the following four methods should be overridden in a concrete subclass, as they are in the pr...
clearAllPersistentData()
Clear all data from the persistent storage.
_restserver($params)
Invoke the old restserver.php endpoint.
getFileUploadSupport()
Get the file upload support status.
setAccessToken($access_token)
Sets the access token for api calls.
getApiUrl($method)
Build the URL for api given parameters.
getLoginStatusUrl($params=array())
Get a login status URL to fetch the status from Facebook.
clearPersistentData($key)
Clear the data with $key from the persistent storage.
static endsWith($big, $small)
getUrl($name, $path='', $params=array())
Build the URL for given domain alias, path and parameters.
destroySession()
Destroy the current session.
establishCSRFTokenState()
Lays down a CSRF state token for this process.
static $DROP_QUERY_PARAMS
List of query parameters that get automatically dropped when rebuilding the current URL.
setAppId($appId)
Set the Application ID.
shouldRetainParam($param)
Returns true if and only if the key or key/value pair should be retained as part of the query string.
getCurrentUrl()
Returns the Current URL, stripping it of known FB parameters that should not persist.
getAccessTokenFromCode($code, $redirect_uri=null)
Retrieves an access token for the given authorization code (previously generated from www....
getCode()
Get the authorization code from the query parameters, if it exists, and otherwise return false to sig...
getBaseDomain()
Get the base domain used for the cookie.
getSignedRequestCookieName()
Constructs and returns the name of the cookie that potentially houses the signed request for the app ...
getLogoutUrl($params=array())
Get a Logout URL suitable for use with redirects.
throwAPIException($result)
Analyzes the supplied result to see if it was thrown because the access token is no longer valid.
_oauthRequest($url, $params)
Make a OAuth Request.
getUser()
Get the UID of the connected user, or 0 if the Facebook user is not connected.
$state
A CSRF state variable to assist in the defense against CSRF attacks.
static base64UrlDecode($input)
Base64 encoding that doesn't need to be urlencode()ed.
static errorLog($msg)
Prints to the error log if you aren't in command line mode.
$signedRequest
The data from the signed_request token.
const SIGNED_REQUEST_ALGORITHM
Signed Request Algorithm.
getMetadataCookie()
Parses the metadata cookie that our Javascript API set.
isVideoPost($path, $method='GET')
Return true if this is video post.
setFileUploadSupport($fileUploadSupport)
Set the file upload support status.
static base64UrlEncode($input)
Base64 encoding that doesn't need to be urlencode()ed.
getApiSecret()
Get the App Secret.
setAppSecret($appSecret)
Set the App Secret.
getUserFromAccessToken()
Retrieves the UID with the understanding that $this->accessToken has already been set and is seemingl...
getMetadataCookieName()
Constructs and returns the name of the coookie that potentially contain metadata.
makeRequest($url, $params, $ch=null)
Makes an HTTP request.
getPersistentData($key, $default=false)
Get the data for $key, persisted by BaseFacebook::setPersistentData()
getUserFromAvailableData()
Determines the connected user by first examining any signed requests, then considering an authorizati...
static $CURL_OPTS
Default options for curl.
getLoginUrl($params=array())
Get a Login URL for use with redirects.
getSignedRequest()
Retrieve the signed request, either from a request parameter or, if not present, from a cookie.
const VERSION
Version.
setExtendedAccessToken()
Extend an access token, while removing the short-lived token that might have been generated via clien...
getAppSecret()
Get the App Secret.
__construct($config)
Initialize a Facebook Application.
static isAllowedDomain($big, $small)
setApiSecret($apiSecret)
Set the App Secret.
getUserAccessToken()
Determines and returns the user access token, first using the signed request if present,...
An exception for terminatinating execution or to throw for unit testing.
Copyright 2011 Facebook, Inc.
getType()
Returns the associated type for the error.
$result
The result from the API server that represents the exception information.
__toString()
To make debugging easier.
__construct($result)
Make a new API Exception with the given result.
getResult()
Return the associated result object returned by the API server.
$key
Definition: croninfo.php:18
$code
Definition: example_050.php:99
$error
Definition: Error.php:17
if($format !==null) $name
Definition: metadata.php:146
catch(Exception $e) $message
$query
$url
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']
$params
Definition: disable.php:11