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