Guest User

Untitled

a guest
Nov 12th, 2010
613
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 26.84 KB | None | 0 0
  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 <[email protected]>
  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.  
  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 <[email protected]>
  97.  */
  98. class 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.  
  360.     $cookie=Yii::app()->request->cookies[$cookieName];
  361.  
  362.         if ($cookie) {
  363.           $session = array();
  364.           parse_str(trim(
  365.             get_magic_quotes_gpc()
  366.               ? stripslashes($cookie->value)
  367.               : $cookie->value,
  368.             '"'
  369.           ), $session);
  370.           $session = $this->validateSessionObject($session);
  371.           // write only if we need to delete a invalid session cookie
  372.           $write_cookie = empty($session);
  373.         }
  374.       }
  375.       $this->setSession($session, $write_cookie);
  376.     }
  377.  
  378.     return $this->session;
  379.   }
  380.  
  381.   /**
  382.    * Get the UID from the session.
  383.    *
  384.    * @return String the UID if available
  385.    */
  386.   public function getUser() {
  387.     $session = $this->getSession();
  388.     return $session ? $session['uid'] : null;
  389.   }
  390.  
  391.   /**
  392.    * Gets a OAuth access token.
  393.    *
  394.    * @return String the access token
  395.    */
  396.   public function getAccessToken() {
  397.     $session = $this->getSession();
  398.     // either user session signed, or app signed
  399.     if ($session) {
  400.       return $session['access_token'];
  401.     } else {
  402.       return $this->getAppId() .'|'. $this->getApiSecret();
  403.     }
  404.   }
  405.  
  406.   /**
  407.    * Get a Login URL for use with redirects. By default, full page redirect is
  408.    * assumed. If you are using the generated URL with a window.open() call in
  409.    * JavaScript, you can pass in display=popup as part of the $params.
  410.    *
  411.    * The parameters:
  412.    * - next: the url to go to after a successful login
  413.    * - cancel_url: the url to go to after the user cancels
  414.    * - req_perms: comma separated list of requested extended perms
  415.    * - display: can be "page" (default, full page) or "popup"
  416.    *
  417.    * @param Array $params provide custom parameters
  418.    * @return String the URL for the login flow
  419.    */
  420.   public function getLoginUrl($params=array()) {
  421.     $currentUrl = $this->getCurrentUrl();
  422.     return $this->getUrl(
  423.       'www',
  424.       'login.php',
  425.       array_merge(array(
  426.         'api_key'         => $this->getAppId(),
  427.         'cancel_url'      => $currentUrl,
  428.         'display'         => 'page',
  429.         'fbconnect'       => 1,
  430.         'next'            => $currentUrl,
  431.         'return_session'  => 1,
  432.         'session_version' => 3,
  433.         'v'               => '1.0',
  434.       ), $params)
  435.     );
  436.   }
  437.  
  438.   /**
  439.    * Get a Logout URL suitable for use with redirects.
  440.    *
  441.    * The parameters:
  442.    * - next: the url to go to after a successful logout
  443.    *
  444.    * @param Array $params provide custom parameters
  445.    * @return String the URL for the logout flow
  446.    */
  447.   public function getLogoutUrl($params=array()) {
  448.     return $this->getUrl(
  449.       'www',
  450.       'logout.php',
  451.       array_merge(array(
  452.         'next'         => $this->getCurrentUrl(),
  453.         'access_token' => $this->getAccessToken(),
  454.       ), $params)
  455.     );
  456.   }
  457.  
  458.   /**
  459.    * Get a login status URL to fetch the status from facebook.
  460.    *
  461.    * The parameters:
  462.    * - ok_session: the URL to go to if a session is found
  463.    * - no_session: the URL to go to if the user is not connected
  464.    * - no_user: the URL to go to if the user is not signed into facebook
  465.    *
  466.    * @param Array $params provide custom parameters
  467.    * @return String the URL for the logout flow
  468.    */
  469.   public function getLoginStatusUrl($params=array()) {
  470.     return $this->getUrl(
  471.       'www',
  472.       'extern/login_status.php',
  473.       array_merge(array(
  474.         'api_key'         => $this->getAppId(),
  475.         'no_session'      => $this->getCurrentUrl(),
  476.         'no_user'         => $this->getCurrentUrl(),
  477.         'ok_session'      => $this->getCurrentUrl(),
  478.         'session_version' => 3,
  479.       ), $params)
  480.     );
  481.   }
  482.  
  483.   /**
  484.    * Make an API call.
  485.    *
  486.    * @param Array $params the API call parameters
  487.    * @return the decoded response
  488.    */
  489.   public function api(/* polymorphic */) {
  490.     $args = func_get_args();
  491.     if (is_array($args[0])) {
  492.       return $this->_restserver($args[0]);
  493.     } else {
  494.       return call_user_func_array(array($this, '_graph'), $args);
  495.     }
  496.   }
  497.  
  498.   /**
  499.    * Invoke the old restserver.php endpoint.
  500.    *
  501.    * @param Array $params method call object
  502.    * @return the decoded response object
  503.    * @throws FacebookApiException
  504.    */
  505.   protected function _restserver($params) {
  506.     // generic application level parameters
  507.     $params['api_key'] = $this->getAppId();
  508.     $params['format'] = 'json-strings';
  509.  
  510.     $result = json_decode($this->_oauthRequest(
  511.       $this->getApiUrl($params['method']),
  512.       $params
  513.     ), true);
  514.  
  515.     // results are returned, errors are thrown
  516.     if (is_array($result) && isset($result['error_code'])) {
  517.       throw new FacebookApiException($result);
  518.     }
  519.     return $result;
  520.   }
  521.  
  522.   /**
  523.    * Invoke the Graph API.
  524.    *
  525.    * @param String $path the path (required)
  526.    * @param String $method the http method (default 'GET')
  527.    * @param Array $params the query/post data
  528.    * @return the decoded response object
  529.    * @throws FacebookApiException
  530.    */
  531.   protected function _graph($path, $method='GET', $params=array()) {
  532.     if (is_array($method) && empty($params)) {
  533.       $params = $method;
  534.       $method = 'GET';
  535.     }
  536.     $params['method'] = $method; // method override as we always do a POST
  537.  
  538.     $result = json_decode($this->_oauthRequest(
  539.       $this->getUrl('graph', $path),
  540.       $params
  541.     ), true);
  542.  
  543.     // results are returned, errors are thrown
  544.     if (is_array($result) && isset($result['error'])) {
  545.       $e = new FacebookApiException($result);
  546.       switch ($e->getType()) {
  547.         // OAuth 2.0 Draft 00 style
  548.         case 'OAuthException':
  549.         // OAuth 2.0 Draft 10 style
  550.         case 'invalid_token':
  551.           $this->setSession(null);
  552.       }
  553.       throw $e;
  554.     }
  555.     return $result;
  556.   }
  557.  
  558.   /**
  559.    * Make a OAuth Request
  560.    *
  561.    * @param String $path the path (required)
  562.    * @param Array $params the query/post data
  563.    * @return the decoded response object
  564.    * @throws FacebookApiException
  565.    */
  566.   protected function _oauthRequest($url, $params) {
  567.     if (!isset($params['access_token'])) {
  568.       $params['access_token'] = $this->getAccessToken();
  569.     }
  570.  
  571.     // json_encode all params values that are not strings
  572.     foreach ($params as $key => $value) {
  573.       if (!is_string($value)) {
  574.         $params[$key] = json_encode($value);
  575.       }
  576.     }
  577.     return $this->makeRequest($url, $params);
  578.   }
  579.  
  580.   /**
  581.    * Makes an HTTP request. This method can be overriden by subclasses if
  582.    * developers want to do fancier things or use something other than curl to
  583.    * make the request.
  584.    *
  585.    * @param String $url the URL to make the request to
  586.    * @param Array $params the parameters to use for the POST body
  587.    * @param CurlHandler $ch optional initialized curl handle
  588.    * @return String the response text
  589.    */
  590.   protected function makeRequest($url, $params, $ch=null) {
  591.     if (!$ch) {
  592.       $ch = curl_init();
  593.     }
  594.  
  595.     $opts = self::$CURL_OPTS;
  596.     if ($this->useFileUploadSupport()) {
  597.       $opts[CURLOPT_POSTFIELDS] = $params;
  598.     } else {
  599.       $opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&');
  600.     }
  601.     $opts[CURLOPT_URL] = $url;
  602.  
  603.     // disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
  604.     // for 2 seconds if the server does not support this header.
  605.     if (isset($opts[CURLOPT_HTTPHEADER])) {
  606.       $existing_headers = $opts[CURLOPT_HTTPHEADER];
  607.       $existing_headers[] = 'Expect:';
  608.       $opts[CURLOPT_HTTPHEADER] = $existing_headers;
  609.     } else {
  610.       $opts[CURLOPT_HTTPHEADER] = array('Expect:');
  611.     }
  612.  
  613.     curl_setopt_array($ch, $opts);
  614.     $result = curl_exec($ch);
  615.     if ($result === false) {
  616.       $e = new FacebookApiException(array(
  617.         'error_code' => curl_errno($ch),
  618.         'error'      => array(
  619.           'message' => curl_error($ch),
  620.           'type'    => 'CurlException',
  621.         ),
  622.       ));
  623.       curl_close($ch);
  624.       throw $e;
  625.     }
  626.     curl_close($ch);
  627.     return $result;
  628.   }
  629.  
  630.   /**
  631.    * The name of the Cookie that contains the session.
  632.    *
  633.    * @return String the cookie name
  634.    */
  635.   protected function getSessionCookieName() {
  636.     return 'fbs_' . $this->getAppId();
  637.   }
  638.  
  639.   /**
  640.    * Set a JS Cookie based on the _passed in_ session. It does not use the
  641.    * currently stored session -- you need to explicitly pass it in.
  642.    *
  643.    * @param Array $session the session to use for setting the cookie
  644.    */
  645.   public function setCookieFromSession($session=null) {
  646.     if (!$this->useCookieSupport()) {
  647.       return;
  648.     }
  649.  
  650.  
  651.     $cookieName = $this->getSessionCookieName();
  652.     $value = 'deleted';
  653.     $expires = time() - 3600;
  654.     $domain = $this->getBaseDomain();
  655.     if ($session) {
  656.       $value = '"' . http_build_query($session, null, '&') . '"';
  657.       if (isset($session['base_domain'])) {
  658.         $domain = $session['base_domain'];
  659.       }
  660.       $expires = $session['expires'];
  661.     }
  662.  
  663.     // prepend dot if a domain is found
  664.     if ($domain) {
  665.       $domain = '.' . $domain;
  666.     }
  667.  
  668.  
  669.     $cookie=Yii::app()->request->cookies[$cookieName];
  670.  
  671.  
  672.     // if an existing cookie is not set, we dont need to delete it
  673.     if ($value == 'deleted' && empty($cookie->value)) {
  674.       return;
  675.     }
  676.  
  677.     if (headers_sent()) {
  678.       self::errorLog('Could not set cookie. Headers already sent.');
  679.  
  680.     // ignore for code coverage as we will never be able to setcookie in a CLI
  681.     // environment
  682.     // @codeCoverageIgnoreStart
  683.     } else {
  684.  
  685.     $yiiCookie = new CHttpCookie($cookieName, $value);
  686.     $yiiCookie->expire = time() + 31536000; // one year from now
  687.     Yii::app()->request->cookies[$cookieName] = $yiiCookie;
  688.     //setcookie($cookieName, $value, $expires, '/', $domain);
  689.     }
  690.     // @codeCoverageIgnoreEnd
  691.   }
  692.  
  693.   /**
  694.    * Validates a session_version=3 style session object.
  695.    *
  696.    * @param Array $session the session object
  697.    * @return Array the session object if it validates, null otherwise
  698.    */
  699.   protected function validateSessionObject($session) {
  700.     // make sure some essential fields exist
  701.     if (is_array($session) &&
  702.         isset($session['uid']) &&
  703.         isset($session['access_token']) &&
  704.         isset($session['sig'])) {
  705.       // validate the signature
  706.       $session_without_sig = $session;
  707.       unset($session_without_sig['sig']);
  708.       $expected_sig = self::generateSignature(
  709.         $session_without_sig,
  710.         $this->getApiSecret()
  711.       );
  712.       if ($session['sig'] != $expected_sig) {
  713.         self::errorLog('Got invalid session signature in cookie.');
  714.         $session = null;
  715.       }
  716.       // check expiry time
  717.     } else {
  718.       $session = null;
  719.     }
  720.     return $session;
  721.   }
  722.  
  723.   /**
  724.    * Returns something that looks like our JS session object from the
  725.    * signed token's data
  726.    *
  727.    * TODO: Nuke this once the login flow uses OAuth2
  728.    *
  729.    * @param Array the output of getSignedRequest
  730.    * @return Array Something that will work as a session
  731.    */
  732.   protected function createSessionFromSignedRequest($data) {
  733.     if (!isset($data['oauth_token'])) {
  734.       return null;
  735.     }
  736.  
  737.     $session = array(
  738.       'uid'          => $data['user_id'],
  739.       'access_token' => $data['oauth_token'],
  740.       'expires'      => $data['expires'],
  741.     );
  742.  
  743.     // put a real sig, so that validateSignature works
  744.     $session['sig'] = self::generateSignature(
  745.       $session,
  746.       $this->getApiSecret()
  747.     );
  748.  
  749.     return $session;
  750.   }
  751.  
  752.   /**
  753.    * Parses a signed_request and validates the signature.
  754.    * Then saves it in $this->signed_data
  755.    *
  756.    * @param String A signed token
  757.    * @param Boolean Should we remove the parts of the payload that
  758.    *                are used by the algorithm?
  759.    * @return Array the payload inside it or null if the sig is wrong
  760.    */
  761.   protected function parseSignedRequest($signed_request) {
  762.     list($encoded_sig, $payload) = explode('.', $signed_request, 2);
  763.  
  764.     // decode the data
  765.     $sig = self::base64UrlDecode($encoded_sig);
  766.     $data = json_decode(self::base64UrlDecode($payload), true);
  767.  
  768.     if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') {
  769.       self::errorLog('Unknown algorithm. Expected HMAC-SHA256');
  770.       return null;
  771.     }
  772.  
  773.     // check sig
  774.     $expected_sig = hash_hmac('sha256', $payload,
  775.                               $this->getApiSecret(), $raw = true);
  776.     if ($sig !== $expected_sig) {
  777.       self::errorLog('Bad Signed JSON signature!');
  778.       return null;
  779.     }
  780.  
  781.     return $data;
  782.   }
  783.  
  784.   /**
  785.    * Build the URL for api given parameters.
  786.    *
  787.    * @param $method String the method name.
  788.    * @return String the URL for the given parameters
  789.    */
  790.   protected function getApiUrl($method) {
  791.     static $READ_ONLY_CALLS =
  792.       array('admin.getallocation' => 1,
  793.             'admin.getappproperties' => 1,
  794.             'admin.getbannedusers' => 1,
  795.             'admin.getlivestreamvialink' => 1,
  796.             'admin.getmetrics' => 1,
  797.             'admin.getrestrictioninfo' => 1,
  798.             'application.getpublicinfo' => 1,
  799.             'auth.getapppublickey' => 1,
  800.             'auth.getsession' => 1,
  801.             'auth.getsignedpublicsessiondata' => 1,
  802.             'comments.get' => 1,
  803.             'connect.getunconnectedfriendscount' => 1,
  804.             'dashboard.getactivity' => 1,
  805.             'dashboard.getcount' => 1,
  806.             'dashboard.getglobalnews' => 1,
  807.             'dashboard.getnews' => 1,
  808.             'dashboard.multigetcount' => 1,
  809.             'dashboard.multigetnews' => 1,
  810.             'data.getcookies' => 1,
  811.             'events.get' => 1,
  812.             'events.getmembers' => 1,
  813.             'fbml.getcustomtags' => 1,
  814.             'feed.getappfriendstories' => 1,
  815.             'feed.getregisteredtemplatebundlebyid' => 1,
  816.             'feed.getregisteredtemplatebundles' => 1,
  817.             'fql.multiquery' => 1,
  818.             'fql.query' => 1,
  819.             'friends.arefriends' => 1,
  820.             'friends.get' => 1,
  821.             'friends.getappusers' => 1,
  822.             'friends.getlists' => 1,
  823.             'friends.getmutualfriends' => 1,
  824.             'gifts.get' => 1,
  825.             'groups.get' => 1,
  826.             'groups.getmembers' => 1,
  827.             'intl.gettranslations' => 1,
  828.             'links.get' => 1,
  829.             'notes.get' => 1,
  830.             'notifications.get' => 1,
  831.             'pages.getinfo' => 1,
  832.             'pages.isadmin' => 1,
  833.             'pages.isappadded' => 1,
  834.             'pages.isfan' => 1,
  835.             'permissions.checkavailableapiaccess' => 1,
  836.             'permissions.checkgrantedapiaccess' => 1,
  837.             'photos.get' => 1,
  838.             'photos.getalbums' => 1,
  839.             'photos.gettags' => 1,
  840.             'profile.getinfo' => 1,
  841.             'profile.getinfooptions' => 1,
  842.             'stream.get' => 1,
  843.             'stream.getcomments' => 1,
  844.             'stream.getfilters' => 1,
  845.             'users.getinfo' => 1,
  846.             'users.getloggedinuser' => 1,
  847.             'users.getstandardinfo' => 1,
  848.             'users.hasapppermission' => 1,
  849.             'users.isappuser' => 1,
  850.             'users.isverified' => 1,
  851.             'video.getuploadlimits' => 1);
  852.     $name = 'api';
  853.     if (isset($READ_ONLY_CALLS[strtolower($method)])) {
  854.       $name = 'api_read';
  855.     }
  856.     return self::getUrl($name, 'restserver.php');
  857.   }
  858.  
  859.   /**
  860.    * Build the URL for given domain alias, path and parameters.
  861.    *
  862.    * @param $name String the name of the domain
  863.    * @param $path String optional path (without a leading slash)
  864.    * @param $params Array optional query parameters
  865.    * @return String the URL for the given parameters
  866.    */
  867.   protected function getUrl($name, $path='', $params=array()) {
  868.     $url = self::$DOMAIN_MAP[$name];
  869.     if ($path) {
  870.       if ($path[0] === '/') {
  871.         $path = substr($path, 1);
  872.       }
  873.       $url .= $path;
  874.     }
  875.     if ($params) {
  876.       $url .= '?' . http_build_query($params, null, '&');
  877.     }
  878.     return $url;
  879.   }
  880.  
  881.   /**
  882.    * Returns the Current URL, stripping it of known FB parameters that should
  883.    * not persist.
  884.    *
  885.    * @return String the current URL
  886.    */
  887.   protected function getCurrentUrl() {
  888.     $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'
  889.       ? 'https://'
  890.       : 'http://';
  891.     $currentUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
  892.     $parts = parse_url($currentUrl);
  893.  
  894.     // drop known fb params
  895.     $query = '';
  896.     if (!empty($parts['query'])) {
  897.       $params = array();
  898.       parse_str($parts['query'], $params);
  899.       foreach(self::$DROP_QUERY_PARAMS as $key) {
  900.         unset($params[$key]);
  901.       }
  902.       if (!empty($params)) {
  903.         $query = '?' . http_build_query($params, null, '&');
  904.       }
  905.     }
  906.  
  907.     // use port if non default
  908.     $port =
  909.       isset($parts['port']) &&
  910.       (($protocol === 'http://' && $parts['port'] !== 80) ||
  911.        ($protocol === 'https://' && $parts['port'] !== 443))
  912.       ? ':' . $parts['port'] : '';
  913.  
  914.     // rebuild
  915.     return $protocol . $parts['host'] . $port . $parts['path'] . $query;
  916.   }
  917.  
  918.   /**
  919.    * Generate a signature for the given params and secret.
  920.    *
  921.    * @param Array $params the parameters to sign
  922.    * @param String $secret the secret to sign with
  923.    * @return String the generated signature
  924.    */
  925.   protected static function generateSignature($params, $secret) {
  926.     // work with sorted data
  927.     ksort($params);
  928.  
  929.     // generate the base string
  930.     $base_string = '';
  931.     foreach($params as $key => $value) {
  932.       $base_string .= $key . '=' . $value;
  933.     }
  934.     $base_string .= $secret;
  935.  
  936.     return md5($base_string);
  937.   }
  938.  
  939.   /**
  940.    * Prints to the error log if you aren't in command line mode.
  941.    *
  942.    * @param String log message
  943.    */
  944.   protected static function errorLog($msg) {
  945.     // disable error log if we are running in a CLI environment
  946.     // @codeCoverageIgnoreStart
  947.     if (php_sapi_name() != 'cli') {
  948.       error_log($msg);
  949.     }
  950.     // uncomment this if you want to see the errors on the page
  951.     // print 'error_log: '.$msg."\n";
  952.     // @codeCoverageIgnoreEnd
  953.   }
  954.  
  955.   /**
  956.    * Base64 encoding that doesn't need to be urlencode()ed.
  957.    * Exactly the same as base64_encode except it uses
  958.    *   - instead of +
  959.    *   _ instead of /
  960.    *
  961.    * @param String base64UrlEncodeded string
  962.    */
  963.   protected static function base64UrlDecode($input) {
  964.     return base64_decode(strtr($input, '-_', '+/'));
  965.   }
  966. }
Add Comment
Please, Sign In to add comment