1<?php 2/** 3 * Copyright 2017 Facebook, Inc. 4 * 5 * You are hereby granted a non-exclusive, worldwide, royalty-free license to 6 * use, copy, modify, and distribute this software in source code or binary 7 * form for use in connection with the web services and APIs provided by 8 * Facebook. 9 * 10 * As with any software that integrates with the Facebook platform, your use 11 * of this software is subject to the Facebook Developer Principles and 12 * Policies [http://developers.facebook.com/policy/]. This copyright notice 13 * shall be included in all copies or substantial portions of the software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 * DEALINGS IN THE SOFTWARE. 22 * 23 */ 24namespace Facebook; 25 26use Facebook\Authentication\AccessToken; 27use Facebook\Authentication\OAuth2Client; 28use Facebook\FileUpload\FacebookFile; 29use Facebook\FileUpload\FacebookResumableUploader; 30use Facebook\FileUpload\FacebookTransferChunk; 31use Facebook\FileUpload\FacebookVideo; 32use Facebook\GraphNodes\GraphEdge; 33use Facebook\Url\UrlDetectionInterface; 34use Facebook\Url\FacebookUrlDetectionHandler; 35use Facebook\PseudoRandomString\PseudoRandomStringGeneratorFactory; 36use Facebook\PseudoRandomString\PseudoRandomStringGeneratorInterface; 37use Facebook\HttpClients\HttpClientsFactory; 38use Facebook\PersistentData\PersistentDataFactory; 39use Facebook\PersistentData\PersistentDataInterface; 40use Facebook\Helpers\FacebookCanvasHelper; 41use Facebook\Helpers\FacebookJavaScriptHelper; 42use Facebook\Helpers\FacebookPageTabHelper; 43use Facebook\Helpers\FacebookRedirectLoginHelper; 44use Facebook\Exceptions\FacebookSDKException; 45 46/** 47 * Class Facebook 48 * 49 * @package Facebook 50 */ 51class Facebook 52{ 53 /** 54 * @const string Version number of the Facebook PHP SDK. 55 */ 56 const VERSION = '5.6.2'; 57 58 /** 59 * @const string Default Graph API version for requests. 60 */ 61 const DEFAULT_GRAPH_VERSION = 'v2.10'; 62 63 /** 64 * @const string The name of the environment variable that contains the app ID. 65 */ 66 const APP_ID_ENV_NAME = 'FACEBOOK_APP_ID'; 67 68 /** 69 * @const string The name of the environment variable that contains the app secret. 70 */ 71 const APP_SECRET_ENV_NAME = 'FACEBOOK_APP_SECRET'; 72 73 /** 74 * @var FacebookApp The FacebookApp entity. 75 */ 76 protected $app; 77 78 /** 79 * @var FacebookClient The Facebook client service. 80 */ 81 protected $client; 82 83 /** 84 * @var OAuth2Client The OAuth 2.0 client service. 85 */ 86 protected $oAuth2Client; 87 88 /** 89 * @var UrlDetectionInterface|null The URL detection handler. 90 */ 91 protected $urlDetectionHandler; 92 93 /** 94 * @var PseudoRandomStringGeneratorInterface|null The cryptographically secure pseudo-random string generator. 95 */ 96 protected $pseudoRandomStringGenerator; 97 98 /** 99 * @var AccessToken|null The default access token to use with requests. 100 */ 101 protected $defaultAccessToken; 102 103 /** 104 * @var string|null The default Graph version we want to use. 105 */ 106 protected $defaultGraphVersion; 107 108 /** 109 * @var PersistentDataInterface|null The persistent data handler. 110 */ 111 protected $persistentDataHandler; 112 113 /** 114 * @var FacebookResponse|FacebookBatchResponse|null Stores the last request made to Graph. 115 */ 116 protected $lastResponse; 117 118 /** 119 * Instantiates a new Facebook super-class object. 120 * 121 * @param array $config 122 * 123 * @throws FacebookSDKException 124 */ 125 public function __construct(array $config = []) 126 { 127 $config = array_merge([ 128 'app_id' => getenv(static::APP_ID_ENV_NAME), 129 'app_secret' => getenv(static::APP_SECRET_ENV_NAME), 130 'default_graph_version' => static::DEFAULT_GRAPH_VERSION, 131 'enable_beta_mode' => false, 132 'http_client_handler' => null, 133 'persistent_data_handler' => null, 134 'pseudo_random_string_generator' => null, 135 'url_detection_handler' => null, 136 ], $config); 137 138 if (!$config['app_id']) { 139 throw new FacebookSDKException('Required "app_id" key not supplied in config and could not find fallback environment variable "' . static::APP_ID_ENV_NAME . '"'); 140 } 141 if (!$config['app_secret']) { 142 throw new FacebookSDKException('Required "app_secret" key not supplied in config and could not find fallback environment variable "' . static::APP_SECRET_ENV_NAME . '"'); 143 } 144 145 $this->app = new FacebookApp($config['app_id'], $config['app_secret']); 146 $this->client = new FacebookClient( 147 HttpClientsFactory::createHttpClient($config['http_client_handler']), 148 $config['enable_beta_mode'] 149 ); 150 $this->pseudoRandomStringGenerator = PseudoRandomStringGeneratorFactory::createPseudoRandomStringGenerator( 151 $config['pseudo_random_string_generator'] 152 ); 153 $this->setUrlDetectionHandler($config['url_detection_handler'] ?: new FacebookUrlDetectionHandler()); 154 $this->persistentDataHandler = PersistentDataFactory::createPersistentDataHandler( 155 $config['persistent_data_handler'] 156 ); 157 158 if (isset($config['default_access_token'])) { 159 $this->setDefaultAccessToken($config['default_access_token']); 160 } 161 162 // @todo v6: Throw an InvalidArgumentException if "default_graph_version" is not set 163 $this->defaultGraphVersion = $config['default_graph_version']; 164 } 165 166 /** 167 * Returns the FacebookApp entity. 168 * 169 * @return FacebookApp 170 */ 171 public function getApp() 172 { 173 return $this->app; 174 } 175 176 /** 177 * Returns the FacebookClient service. 178 * 179 * @return FacebookClient 180 */ 181 public function getClient() 182 { 183 return $this->client; 184 } 185 186 /** 187 * Returns the OAuth 2.0 client service. 188 * 189 * @return OAuth2Client 190 */ 191 public function getOAuth2Client() 192 { 193 if (!$this->oAuth2Client instanceof OAuth2Client) { 194 $app = $this->getApp(); 195 $client = $this->getClient(); 196 $this->oAuth2Client = new OAuth2Client($app, $client, $this->defaultGraphVersion); 197 } 198 199 return $this->oAuth2Client; 200 } 201 202 /** 203 * Returns the last response returned from Graph. 204 * 205 * @return FacebookResponse|FacebookBatchResponse|null 206 */ 207 public function getLastResponse() 208 { 209 return $this->lastResponse; 210 } 211 212 /** 213 * Returns the URL detection handler. 214 * 215 * @return UrlDetectionInterface 216 */ 217 public function getUrlDetectionHandler() 218 { 219 return $this->urlDetectionHandler; 220 } 221 222 /** 223 * Changes the URL detection handler. 224 * 225 * @param UrlDetectionInterface $urlDetectionHandler 226 */ 227 private function setUrlDetectionHandler(UrlDetectionInterface $urlDetectionHandler) 228 { 229 $this->urlDetectionHandler = $urlDetectionHandler; 230 } 231 232 /** 233 * Returns the default AccessToken entity. 234 * 235 * @return AccessToken|null 236 */ 237 public function getDefaultAccessToken() 238 { 239 return $this->defaultAccessToken; 240 } 241 242 /** 243 * Sets the default access token to use with requests. 244 * 245 * @param AccessToken|string $accessToken The access token to save. 246 * 247 * @throws \InvalidArgumentException 248 */ 249 public function setDefaultAccessToken($accessToken) 250 { 251 if (is_string($accessToken)) { 252 $this->defaultAccessToken = new AccessToken($accessToken); 253 254 return; 255 } 256 257 if ($accessToken instanceof AccessToken) { 258 $this->defaultAccessToken = $accessToken; 259 260 return; 261 } 262 263 throw new \InvalidArgumentException('The default access token must be of type "string" or Facebook\AccessToken'); 264 } 265 266 /** 267 * Returns the default Graph version. 268 * 269 * @return string 270 */ 271 public function getDefaultGraphVersion() 272 { 273 return $this->defaultGraphVersion; 274 } 275 276 /** 277 * Returns the redirect login helper. 278 * 279 * @return FacebookRedirectLoginHelper 280 */ 281 public function getRedirectLoginHelper() 282 { 283 return new FacebookRedirectLoginHelper( 284 $this->getOAuth2Client(), 285 $this->persistentDataHandler, 286 $this->urlDetectionHandler, 287 $this->pseudoRandomStringGenerator 288 ); 289 } 290 291 /** 292 * Returns the JavaScript helper. 293 * 294 * @return FacebookJavaScriptHelper 295 */ 296 public function getJavaScriptHelper() 297 { 298 return new FacebookJavaScriptHelper($this->app, $this->client, $this->defaultGraphVersion); 299 } 300 301 /** 302 * Returns the canvas helper. 303 * 304 * @return FacebookCanvasHelper 305 */ 306 public function getCanvasHelper() 307 { 308 return new FacebookCanvasHelper($this->app, $this->client, $this->defaultGraphVersion); 309 } 310 311 /** 312 * Returns the page tab helper. 313 * 314 * @return FacebookPageTabHelper 315 */ 316 public function getPageTabHelper() 317 { 318 return new FacebookPageTabHelper($this->app, $this->client, $this->defaultGraphVersion); 319 } 320 321 /** 322 * Sends a GET request to Graph and returns the result. 323 * 324 * @param string $endpoint 325 * @param AccessToken|string|null $accessToken 326 * @param string|null $eTag 327 * @param string|null $graphVersion 328 * 329 * @return FacebookResponse 330 * 331 * @throws FacebookSDKException 332 */ 333 public function get($endpoint, $accessToken = null, $eTag = null, $graphVersion = null) 334 { 335 return $this->sendRequest( 336 'GET', 337 $endpoint, 338 $params = [], 339 $accessToken, 340 $eTag, 341 $graphVersion 342 ); 343 } 344 345 /** 346 * Sends a POST request to Graph and returns the result. 347 * 348 * @param string $endpoint 349 * @param array $params 350 * @param AccessToken|string|null $accessToken 351 * @param string|null $eTag 352 * @param string|null $graphVersion 353 * 354 * @return FacebookResponse 355 * 356 * @throws FacebookSDKException 357 */ 358 public function post($endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) 359 { 360 return $this->sendRequest( 361 'POST', 362 $endpoint, 363 $params, 364 $accessToken, 365 $eTag, 366 $graphVersion 367 ); 368 } 369 370 /** 371 * Sends a DELETE request to Graph and returns the result. 372 * 373 * @param string $endpoint 374 * @param array $params 375 * @param AccessToken|string|null $accessToken 376 * @param string|null $eTag 377 * @param string|null $graphVersion 378 * 379 * @return FacebookResponse 380 * 381 * @throws FacebookSDKException 382 */ 383 public function delete($endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) 384 { 385 return $this->sendRequest( 386 'DELETE', 387 $endpoint, 388 $params, 389 $accessToken, 390 $eTag, 391 $graphVersion 392 ); 393 } 394 395 /** 396 * Sends a request to Graph for the next page of results. 397 * 398 * @param GraphEdge $graphEdge The GraphEdge to paginate over. 399 * 400 * @return GraphEdge|null 401 * 402 * @throws FacebookSDKException 403 */ 404 public function next(GraphEdge $graphEdge) 405 { 406 return $this->getPaginationResults($graphEdge, 'next'); 407 } 408 409 /** 410 * Sends a request to Graph for the previous page of results. 411 * 412 * @param GraphEdge $graphEdge The GraphEdge to paginate over. 413 * 414 * @return GraphEdge|null 415 * 416 * @throws FacebookSDKException 417 */ 418 public function previous(GraphEdge $graphEdge) 419 { 420 return $this->getPaginationResults($graphEdge, 'previous'); 421 } 422 423 /** 424 * Sends a request to Graph for the next page of results. 425 * 426 * @param GraphEdge $graphEdge The GraphEdge to paginate over. 427 * @param string $direction The direction of the pagination: next|previous. 428 * 429 * @return GraphEdge|null 430 * 431 * @throws FacebookSDKException 432 */ 433 public function getPaginationResults(GraphEdge $graphEdge, $direction) 434 { 435 $paginationRequest = $graphEdge->getPaginationRequest($direction); 436 if (!$paginationRequest) { 437 return null; 438 } 439 440 $this->lastResponse = $this->client->sendRequest($paginationRequest); 441 442 // Keep the same GraphNode subclass 443 $subClassName = $graphEdge->getSubClassName(); 444 $graphEdge = $this->lastResponse->getGraphEdge($subClassName, false); 445 446 return count($graphEdge) > 0 ? $graphEdge : null; 447 } 448 449 /** 450 * Sends a request to Graph and returns the result. 451 * 452 * @param string $method 453 * @param string $endpoint 454 * @param array $params 455 * @param AccessToken|string|null $accessToken 456 * @param string|null $eTag 457 * @param string|null $graphVersion 458 * 459 * @return FacebookResponse 460 * 461 * @throws FacebookSDKException 462 */ 463 public function sendRequest($method, $endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) 464 { 465 $accessToken = $accessToken ?: $this->defaultAccessToken; 466 $graphVersion = $graphVersion ?: $this->defaultGraphVersion; 467 $request = $this->request($method, $endpoint, $params, $accessToken, $eTag, $graphVersion); 468 469 return $this->lastResponse = $this->client->sendRequest($request); 470 } 471 472 /** 473 * Sends a batched request to Graph and returns the result. 474 * 475 * @param array $requests 476 * @param AccessToken|string|null $accessToken 477 * @param string|null $graphVersion 478 * 479 * @return FacebookBatchResponse 480 * 481 * @throws FacebookSDKException 482 */ 483 public function sendBatchRequest(array $requests, $accessToken = null, $graphVersion = null) 484 { 485 $accessToken = $accessToken ?: $this->defaultAccessToken; 486 $graphVersion = $graphVersion ?: $this->defaultGraphVersion; 487 $batchRequest = new FacebookBatchRequest( 488 $this->app, 489 $requests, 490 $accessToken, 491 $graphVersion 492 ); 493 494 return $this->lastResponse = $this->client->sendBatchRequest($batchRequest); 495 } 496 497 /** 498 * Instantiates an empty FacebookBatchRequest entity. 499 * 500 * @param AccessToken|string|null $accessToken The top-level access token. Requests with no access token 501 * will fallback to this. 502 * @param string|null $graphVersion The Graph API version to use. 503 * @return FacebookBatchRequest 504 */ 505 public function newBatchRequest($accessToken = null, $graphVersion = null) 506 { 507 $accessToken = $accessToken ?: $this->defaultAccessToken; 508 $graphVersion = $graphVersion ?: $this->defaultGraphVersion; 509 510 return new FacebookBatchRequest( 511 $this->app, 512 [], 513 $accessToken, 514 $graphVersion 515 ); 516 } 517 518 /** 519 * Instantiates a new FacebookRequest entity. 520 * 521 * @param string $method 522 * @param string $endpoint 523 * @param array $params 524 * @param AccessToken|string|null $accessToken 525 * @param string|null $eTag 526 * @param string|null $graphVersion 527 * 528 * @return FacebookRequest 529 * 530 * @throws FacebookSDKException 531 */ 532 public function request($method, $endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) 533 { 534 $accessToken = $accessToken ?: $this->defaultAccessToken; 535 $graphVersion = $graphVersion ?: $this->defaultGraphVersion; 536 537 return new FacebookRequest( 538 $this->app, 539 $accessToken, 540 $method, 541 $endpoint, 542 $params, 543 $eTag, 544 $graphVersion 545 ); 546 } 547 548 /** 549 * Factory to create FacebookFile's. 550 * 551 * @param string $pathToFile 552 * 553 * @return FacebookFile 554 * 555 * @throws FacebookSDKException 556 */ 557 public function fileToUpload($pathToFile) 558 { 559 return new FacebookFile($pathToFile); 560 } 561 562 /** 563 * Factory to create FacebookVideo's. 564 * 565 * @param string $pathToFile 566 * 567 * @return FacebookVideo 568 * 569 * @throws FacebookSDKException 570 */ 571 public function videoToUpload($pathToFile) 572 { 573 return new FacebookVideo($pathToFile); 574 } 575 576 /** 577 * Upload a video in chunks. 578 * 579 * @param int $target The id of the target node before the /videos edge. 580 * @param string $pathToFile The full path to the file. 581 * @param array $metadata The metadata associated with the video file. 582 * @param string|null $accessToken The access token. 583 * @param int $maxTransferTries The max times to retry a failed upload chunk. 584 * @param string|null $graphVersion The Graph API version to use. 585 * 586 * @return array 587 * 588 * @throws FacebookSDKException 589 */ 590 public function uploadVideo($target, $pathToFile, $metadata = [], $accessToken = null, $maxTransferTries = 5, $graphVersion = null) 591 { 592 $accessToken = $accessToken ?: $this->defaultAccessToken; 593 $graphVersion = $graphVersion ?: $this->defaultGraphVersion; 594 595 $uploader = new FacebookResumableUploader($this->app, $this->client, $accessToken, $graphVersion); 596 $endpoint = '/'.$target.'/videos'; 597 $file = $this->videoToUpload($pathToFile); 598 $chunk = $uploader->start($endpoint, $file); 599 600 do { 601 $chunk = $this->maxTriesTransfer($uploader, $endpoint, $chunk, $maxTransferTries); 602 } while (!$chunk->isLastChunk()); 603 604 return [ 605 'video_id' => $chunk->getVideoId(), 606 'success' => $uploader->finish($endpoint, $chunk->getUploadSessionId(), $metadata), 607 ]; 608 } 609 610 /** 611 * Attempts to upload a chunk of a file in $retryCountdown tries. 612 * 613 * @param FacebookResumableUploader $uploader 614 * @param string $endpoint 615 * @param FacebookTransferChunk $chunk 616 * @param int $retryCountdown 617 * 618 * @return FacebookTransferChunk 619 * 620 * @throws FacebookSDKException 621 */ 622 private function maxTriesTransfer(FacebookResumableUploader $uploader, $endpoint, FacebookTransferChunk $chunk, $retryCountdown) 623 { 624 $newChunk = $uploader->transfer($endpoint, $chunk, $retryCountdown < 1); 625 626 if ($newChunk !== $chunk) { 627 return $newChunk; 628 } 629 630 $retryCountdown--; 631 632 // If transfer() returned the same chunk entity, the transfer failed but is resumable. 633 return $this->maxTriesTransfer($uploader, $endpoint, $chunk, $retryCountdown); 634 } 635} 636