Friendica Communications Platform (please note that this is a clone of the repository at github, issues are handled there) https://friendi.ca
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

955 lines
26 KiB

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