Campustream 1.0
A social network MQP for WPI
|
00001 <?php 00002 00003 if (!function_exists('curl_init')) { 00004 throw new Exception('Facebook needs the CURL PHP extension.'); 00005 } 00006 if (!function_exists('json_decode')) { 00007 throw new Exception('Facebook needs the JSON PHP extension.'); 00008 } 00009 00015 class FacebookApiException extends Exception 00016 { 00020 protected $result; 00021 00027 public function __construct($result) { 00028 $this->result = $result; 00029 00030 $code = isset($result['error_code']) ? $result['error_code'] : 0; 00031 00032 if (isset($result['error_description'])) { 00033 // OAuth 2.0 Draft 10 style 00034 $msg = $result['error_description']; 00035 } else if (isset($result['error']) && is_array($result['error'])) { 00036 // OAuth 2.0 Draft 00 style 00037 $msg = $result['error']['message']; 00038 } else if (isset($result['error_msg'])) { 00039 // Rest server style 00040 $msg = $result['error_msg']; 00041 } else { 00042 $msg = 'Unknown Error. Check getResult()'; 00043 } 00044 00045 parent::__construct($msg, $code); 00046 } 00047 00053 public function getResult() { 00054 return $this->result; 00055 } 00056 00063 public function getType() { 00064 if (isset($this->result['error'])) { 00065 $error = $this->result['error']; 00066 if (is_string($error)) { 00067 // OAuth 2.0 Draft 10 style 00068 return $error; 00069 } else if (is_array($error)) { 00070 // OAuth 2.0 Draft 00 style 00071 if (isset($error['type'])) { 00072 return $error['type']; 00073 } 00074 } 00075 } 00076 return 'Exception'; 00077 } 00078 00084 public function __toString() { 00085 $str = $this->getType() . ': '; 00086 if ($this->code != 0) { 00087 $str .= $this->code . ': '; 00088 } 00089 return $str . $this->message; 00090 } 00091 } 00092 00098 class Facebook 00099 { 00103 const VERSION = '2.1.2'; 00104 00108 public static $CURL_OPTS = array( 00109 CURLOPT_CONNECTTIMEOUT => 10, 00110 CURLOPT_RETURNTRANSFER => true, 00111 CURLOPT_TIMEOUT => 60, 00112 CURLOPT_USERAGENT => 'facebook-php-2.0', 00113 ); 00114 00119 protected static $DROP_QUERY_PARAMS = array( 00120 'session', 00121 'signed_request', 00122 ); 00123 00127 public static $DOMAIN_MAP = array( 00128 'api' => 'https://api.facebook.com/', 00129 'api_read' => 'https://api-read.facebook.com/', 00130 'graph' => 'https://graph.facebook.com/', 00131 'www' => 'https://www.facebook.com/', 00132 ); 00133 00137 protected $appId; 00138 00142 protected $apiSecret; 00143 00147 protected $session; 00148 00152 protected $signedRequest; 00153 00157 protected $sessionLoaded = false; 00158 00162 protected $cookieSupport = false; 00163 00167 protected $baseDomain = ''; 00168 00172 protected $fileUploadSupport = false; 00173 00186 public function __construct($config) { 00187 $this->setAppId($config['appId']); 00188 $this->setApiSecret($config['secret']); 00189 if (isset($config['cookie'])) { 00190 $this->setCookieSupport($config['cookie']); 00191 } 00192 if (isset($config['domain'])) { 00193 $this->setBaseDomain($config['domain']); 00194 } 00195 if (isset($config['fileUpload'])) { 00196 $this->setFileUploadSupport($config['fileUpload']); 00197 } 00198 } 00199 00205 public function setAppId($appId) { 00206 $this->appId = $appId; 00207 return $this; 00208 } 00209 00215 public function getAppId() { 00216 return $this->appId; 00217 } 00218 00224 public function setApiSecret($apiSecret) { 00225 $this->apiSecret = $apiSecret; 00226 return $this; 00227 } 00228 00234 public function getApiSecret() { 00235 return $this->apiSecret; 00236 } 00237 00243 public function setCookieSupport($cookieSupport) { 00244 $this->cookieSupport = $cookieSupport; 00245 return $this; 00246 } 00247 00253 public function useCookieSupport() { 00254 return $this->cookieSupport; 00255 } 00256 00262 public function setBaseDomain($domain) { 00263 $this->baseDomain = $domain; 00264 return $this; 00265 } 00266 00272 public function getBaseDomain() { 00273 return $this->baseDomain; 00274 } 00275 00281 public function setFileUploadSupport($fileUploadSupport) { 00282 $this->fileUploadSupport = $fileUploadSupport; 00283 return $this; 00284 } 00285 00291 public function useFileUploadSupport() { 00292 return $this->fileUploadSupport; 00293 } 00294 00300 public function getSignedRequest() { 00301 if (!$this->signedRequest) { 00302 if (isset($_REQUEST['signed_request'])) { 00303 $this->signedRequest = $this->parseSignedRequest( 00304 $_REQUEST['signed_request']); 00305 } 00306 } 00307 return $this->signedRequest; 00308 } 00309 00317 public function setSession($session=null, $write_cookie=true) { 00318 $session = $this->validateSessionObject($session); 00319 $this->sessionLoaded = true; 00320 $this->session = $session; 00321 if ($write_cookie) { 00322 $this->setCookieFromSession($session); 00323 } 00324 return $this; 00325 } 00326 00333 public function getSession() { 00334 if (!$this->sessionLoaded) { 00335 $session = null; 00336 $write_cookie = true; 00337 00338 // try loading session from signed_request in $_REQUEST 00339 $signedRequest = $this->getSignedRequest(); 00340 if ($signedRequest) { 00341 // sig is good, use the signedRequest 00342 $session = $this->createSessionFromSignedRequest($signedRequest); 00343 } 00344 00345 // try loading session from $_REQUEST 00346 if (!$session && isset($_REQUEST['session'])) { 00347 $session = json_decode( 00348 get_magic_quotes_gpc() 00349 ? stripslashes($_REQUEST['session']) 00350 : $_REQUEST['session'], 00351 true 00352 ); 00353 $session = $this->validateSessionObject($session); 00354 } 00355 00356 // try loading session from cookie if necessary 00357 if (!$session && $this->useCookieSupport()) { 00358 $cookieName = $this->getSessionCookieName(); 00359 if (isset($_COOKIE[$cookieName])) { 00360 $session = array(); 00361 parse_str(trim( 00362 get_magic_quotes_gpc() 00363 ? stripslashes($_COOKIE[$cookieName]) 00364 : $_COOKIE[$cookieName], 00365 '"' 00366 ), $session); 00367 $session = $this->validateSessionObject($session); 00368 // write only if we need to delete a invalid session cookie 00369 $write_cookie = empty($session); 00370 } 00371 } 00372 00373 $this->setSession($session, $write_cookie); 00374 } 00375 00376 return $this->session; 00377 } 00378 00384 public function getUser() { 00385 $session = $this->getSession(); 00386 return $session ? $session['uid'] : null; 00387 } 00388 00394 public function getAccessToken() { 00395 $session = $this->getSession(); 00396 // either user session signed, or app signed 00397 if ($session) { 00398 return $session['access_token']; 00399 } else { 00400 return $this->getAppId() .'|'. $this->getApiSecret(); 00401 } 00402 } 00403 00418 public function getLoginUrl($params=array()) { 00419 $currentUrl = $this->getCurrentUrl(); 00420 return $this->getUrl( 00421 'www', 00422 'login.php', 00423 array_merge(array( 00424 'api_key' => $this->getAppId(), 00425 'cancel_url' => $currentUrl, 00426 'display' => 'page', 00427 'fbconnect' => 1, 00428 'next' => $currentUrl, 00429 'return_session' => 1, 00430 'session_version' => 3, 00431 'v' => '1.0', 00432 ), $params) 00433 ); 00434 } 00435 00445 public function getLogoutUrl($params=array()) { 00446 return $this->getUrl( 00447 'www', 00448 'logout.php', 00449 array_merge(array( 00450 'next' => $this->getCurrentUrl(), 00451 'access_token' => $this->getAccessToken(), 00452 ), $params) 00453 ); 00454 } 00455 00467 public function getLoginStatusUrl($params=array()) { 00468 return $this->getUrl( 00469 'www', 00470 'extern/login_status.php', 00471 array_merge(array( 00472 'api_key' => $this->getAppId(), 00473 'no_session' => $this->getCurrentUrl(), 00474 'no_user' => $this->getCurrentUrl(), 00475 'ok_session' => $this->getCurrentUrl(), 00476 'session_version' => 3, 00477 ), $params) 00478 ); 00479 } 00480 00487 public function api(/* polymorphic */) { 00488 $args = func_get_args(); 00489 if (is_array($args[0])) { 00490 return $this->_restserver($args[0]); 00491 } else { 00492 return call_user_func_array(array($this, '_graph'), $args); 00493 } 00494 } 00495 00503 protected function _restserver($params) { 00504 // generic application level parameters 00505 $params['api_key'] = $this->getAppId(); 00506 $params['format'] = 'json-strings'; 00507 00508 $result = json_decode($this->_oauthRequest( 00509 $this->getApiUrl($params['method']), 00510 $params 00511 ), true); 00512 00513 // results are returned, errors are thrown 00514 if (is_array($result) && isset($result['error_code'])) { 00515 throw new FacebookApiException($result); 00516 } 00517 return $result; 00518 } 00519 00529 protected function _graph($path, $method='GET', $params=array()) { 00530 if (is_array($method) && empty($params)) { 00531 $params = $method; 00532 $method = 'GET'; 00533 } 00534 $params['method'] = $method; // method override as we always do a POST 00535 00536 $result = json_decode($this->_oauthRequest( 00537 $this->getUrl('graph', $path), 00538 $params 00539 ), true); 00540 00541 // results are returned, errors are thrown 00542 if (is_array($result) && isset($result['error'])) { 00543 $e = new FacebookApiException($result); 00544 switch ($e->getType()) { 00545 // OAuth 2.0 Draft 00 style 00546 case 'OAuthException': 00547 // OAuth 2.0 Draft 10 style 00548 case 'invalid_token': 00549 $this->setSession(null); 00550 } 00551 throw $e; 00552 } 00553 return $result; 00554 } 00555 00564 protected function _oauthRequest($url, $params) { 00565 if (!isset($params['access_token'])) { 00566 $params['access_token'] = $this->getAccessToken(); 00567 } 00568 00569 // json_encode all params values that are not strings 00570 foreach ($params as $key => $value) { 00571 if (!is_string($value)) { 00572 $params[$key] = json_encode($value); 00573 } 00574 } 00575 return $this->makeRequest($url, $params); 00576 } 00577 00588 protected function makeRequest($url, $params, $ch=null) { 00589 if (!$ch) { 00590 $ch = curl_init(); 00591 } 00592 00593 $opts = self::$CURL_OPTS; 00594 if ($this->useFileUploadSupport()) { 00595 $opts[CURLOPT_POSTFIELDS] = $params; 00596 } else { 00597 $opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&'); 00598 } 00599 $opts[CURLOPT_URL] = $url; 00600 00601 // disable the 'Expect: 100-continue' behaviour. This causes CURL to wait 00602 // for 2 seconds if the server does not support this header. 00603 if (isset($opts[CURLOPT_HTTPHEADER])) { 00604 $existing_headers = $opts[CURLOPT_HTTPHEADER]; 00605 $existing_headers[] = 'Expect:'; 00606 $opts[CURLOPT_HTTPHEADER] = $existing_headers; 00607 } else { 00608 $opts[CURLOPT_HTTPHEADER] = array('Expect:'); 00609 } 00610 00611 curl_setopt_array($ch, $opts); 00612 $result = curl_exec($ch); 00613 00614 if (curl_errno($ch) == 60) { // CURLE_SSL_CACERT 00615 self::errorLog('Invalid or no certificate authority found, using bundled information'); 00616 curl_setopt($ch, CURLOPT_CAINFO, 00617 dirname(__FILE__) . '/fb_ca_chain_bundle.crt'); 00618 $result = curl_exec($ch); 00619 } 00620 00621 if ($result === false) { 00622 $e = new FacebookApiException(array( 00623 'error_code' => curl_errno($ch), 00624 'error' => array( 00625 'message' => curl_error($ch), 00626 'type' => 'CurlException', 00627 ), 00628 )); 00629 curl_close($ch); 00630 throw $e; 00631 } 00632 curl_close($ch); 00633 return $result; 00634 } 00635 00641 protected function getSessionCookieName() { 00642 return 'fbs_' . $this->getAppId(); 00643 } 00644 00651 protected function setCookieFromSession($session=null) { 00652 if (!$this->useCookieSupport()) { 00653 return; 00654 } 00655 00656 $cookieName = $this->getSessionCookieName(); 00657 $value = 'deleted'; 00658 $expires = time() - 3600; 00659 $domain = $this->getBaseDomain(); 00660 if ($session) { 00661 $value = '"' . http_build_query($session, null, '&') . '"'; 00662 if (isset($session['base_domain'])) { 00663 $domain = $session['base_domain']; 00664 } 00665 $expires = $session['expires']; 00666 } 00667 00668 // prepend dot if a domain is found 00669 if ($domain) { 00670 $domain = '.' . $domain; 00671 } 00672 00673 // if an existing cookie is not set, we dont need to delete it 00674 if ($value == 'deleted' && empty($_COOKIE[$cookieName])) { 00675 return; 00676 } 00677 00678 if (headers_sent()) { 00679 self::errorLog('Could not set cookie. Headers already sent.'); 00680 00681 // ignore for code coverage as we will never be able to setcookie in a CLI 00682 // environment 00683 // @codeCoverageIgnoreStart 00684 } else { 00685 setcookie($cookieName, $value, $expires, '/', $domain); 00686 } 00687 // @codeCoverageIgnoreEnd 00688 } 00689 00696 protected function validateSessionObject($session) { 00697 // make sure some essential fields exist 00698 if (is_array($session) && 00699 isset($session['uid']) && 00700 isset($session['access_token']) && 00701 isset($session['sig'])) { 00702 // validate the signature 00703 $session_without_sig = $session; 00704 unset($session_without_sig['sig']); 00705 $expected_sig = self::generateSignature( 00706 $session_without_sig, 00707 $this->getApiSecret() 00708 ); 00709 if ($session['sig'] != $expected_sig) { 00710 self::errorLog('Got invalid session signature in cookie.'); 00711 $session = null; 00712 } 00713 // check expiry time 00714 } else { 00715 $session = null; 00716 } 00717 return $session; 00718 } 00719 00729 protected function createSessionFromSignedRequest($data) { 00730 if (!isset($data['oauth_token'])) { 00731 return null; 00732 } 00733 00734 $session = array( 00735 'uid' => $data['user_id'], 00736 'access_token' => $data['oauth_token'], 00737 'expires' => $data['expires'], 00738 ); 00739 00740 // put a real sig, so that validateSignature works 00741 $session['sig'] = self::generateSignature( 00742 $session, 00743 $this->getApiSecret() 00744 ); 00745 00746 return $session; 00747 } 00748 00758 protected function parseSignedRequest($signed_request) { 00759 list($encoded_sig, $payload) = explode('.', $signed_request, 2); 00760 00761 // decode the data 00762 $sig = self::base64UrlDecode($encoded_sig); 00763 $data = json_decode(self::base64UrlDecode($payload), true); 00764 00765 if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') { 00766 self::errorLog('Unknown algorithm. Expected HMAC-SHA256'); 00767 return null; 00768 } 00769 00770 // check sig 00771 $expected_sig = hash_hmac('sha256', $payload, 00772 $this->getApiSecret(), $raw = true); 00773 if ($sig !== $expected_sig) { 00774 self::errorLog('Bad Signed JSON signature!'); 00775 return null; 00776 } 00777 00778 return $data; 00779 } 00780 00787 protected function getApiUrl($method) { 00788 static $READ_ONLY_CALLS = 00789 array('admin.getallocation' => 1, 00790 'admin.getappproperties' => 1, 00791 'admin.getbannedusers' => 1, 00792 'admin.getlivestreamvialink' => 1, 00793 'admin.getmetrics' => 1, 00794 'admin.getrestrictioninfo' => 1, 00795 'application.getpublicinfo' => 1, 00796 'auth.getapppublickey' => 1, 00797 'auth.getsession' => 1, 00798 'auth.getsignedpublicsessiondata' => 1, 00799 'comments.get' => 1, 00800 'connect.getunconnectedfriendscount' => 1, 00801 'dashboard.getactivity' => 1, 00802 'dashboard.getcount' => 1, 00803 'dashboard.getglobalnews' => 1, 00804 'dashboard.getnews' => 1, 00805 'dashboard.multigetcount' => 1, 00806 'dashboard.multigetnews' => 1, 00807 'data.getcookies' => 1, 00808 'events.get' => 1, 00809 'events.getmembers' => 1, 00810 'fbml.getcustomtags' => 1, 00811 'feed.getappfriendstories' => 1, 00812 'feed.getregisteredtemplatebundlebyid' => 1, 00813 'feed.getregisteredtemplatebundles' => 1, 00814 'fql.multiquery' => 1, 00815 'fql.query' => 1, 00816 'friends.arefriends' => 1, 00817 'friends.get' => 1, 00818 'friends.getappusers' => 1, 00819 'friends.getlists' => 1, 00820 'friends.getmutualfriends' => 1, 00821 'gifts.get' => 1, 00822 'groups.get' => 1, 00823 'groups.getmembers' => 1, 00824 'intl.gettranslations' => 1, 00825 'links.get' => 1, 00826 'notes.get' => 1, 00827 'notifications.get' => 1, 00828 'pages.getinfo' => 1, 00829 'pages.isadmin' => 1, 00830 'pages.isappadded' => 1, 00831 'pages.isfan' => 1, 00832 'permissions.checkavailableapiaccess' => 1, 00833 'permissions.checkgrantedapiaccess' => 1, 00834 'photos.get' => 1, 00835 'photos.getalbums' => 1, 00836 'photos.gettags' => 1, 00837 'profile.getinfo' => 1, 00838 'profile.getinfooptions' => 1, 00839 'stream.get' => 1, 00840 'stream.getcomments' => 1, 00841 'stream.getfilters' => 1, 00842 'users.getinfo' => 1, 00843 'users.getloggedinuser' => 1, 00844 'users.getstandardinfo' => 1, 00845 'users.hasapppermission' => 1, 00846 'users.isappuser' => 1, 00847 'users.isverified' => 1, 00848 'video.getuploadlimits' => 1); 00849 $name = 'api'; 00850 if (isset($READ_ONLY_CALLS[strtolower($method)])) { 00851 $name = 'api_read'; 00852 } 00853 return self::getUrl($name, 'restserver.php'); 00854 } 00855 00864 protected function getUrl($name, $path='', $params=array()) { 00865 $url = self::$DOMAIN_MAP[$name]; 00866 if ($path) { 00867 if ($path[0] === '/') { 00868 $path = substr($path, 1); 00869 } 00870 $url .= $path; 00871 } 00872 if ($params) { 00873 $url .= '?' . http_build_query($params, null, '&'); 00874 } 00875 return $url; 00876 } 00877 00884 protected function getCurrentUrl() { 00885 $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' 00886 ? 'https://' 00887 : 'http://'; 00888 $currentUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; 00889 $parts = parse_url($currentUrl); 00890 00891 // drop known fb params 00892 $query = ''; 00893 if (!empty($parts['query'])) { 00894 $params = array(); 00895 parse_str($parts['query'], $params); 00896 foreach(self::$DROP_QUERY_PARAMS as $key) { 00897 unset($params[$key]); 00898 } 00899 if (!empty($params)) { 00900 $query = '?' . http_build_query($params, null, '&'); 00901 } 00902 } 00903 00904 // use port if non default 00905 $port = 00906 isset($parts['port']) && 00907 (($protocol === 'http://' && $parts['port'] !== 80) || 00908 ($protocol === 'https://' && $parts['port'] !== 443)) 00909 ? ':' . $parts['port'] : ''; 00910 00911 // rebuild 00912 return $protocol . $parts['host'] . $port . $parts['path'] . $query; 00913 } 00914 00922 protected static function generateSignature($params, $secret) { 00923 // work with sorted data 00924 ksort($params); 00925 00926 // generate the base string 00927 $base_string = ''; 00928 foreach($params as $key => $value) { 00929 $base_string .= $key . '=' . $value; 00930 } 00931 $base_string .= $secret; 00932 00933 return md5($base_string); 00934 } 00935 00941 protected static function errorLog($msg) { 00942 // disable error log if we are running in a CLI environment 00943 // @codeCoverageIgnoreStart 00944 if (php_sapi_name() != 'cli') { 00945 error_log($msg); 00946 } 00947 // uncomment this if you want to see the errors on the page 00948 // print 'error_log: '.$msg."\n"; 00949 // @codeCoverageIgnoreEnd 00950 } 00951 00960 protected static function base64UrlDecode($input) { 00961 return base64_decode(strtr($input, '-_', '+/')); 00962 } 00963 }