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\Url\FacebookUrlManipulator; 28use Facebook\FileUpload\FacebookFile; 29use Facebook\FileUpload\FacebookVideo; 30use Facebook\Http\RequestBodyMultipart; 31use Facebook\Http\RequestBodyUrlEncoded; 32use Facebook\Exceptions\FacebookSDKException; 33 34/** 35 * Class Request 36 * 37 * @package Facebook 38 */ 39class FacebookRequest 40{ 41 /** 42 * @var FacebookApp The Facebook app entity. 43 */ 44 protected $app; 45 46 /** 47 * @var string|null The access token to use for this request. 48 */ 49 protected $accessToken; 50 51 /** 52 * @var string The HTTP method for this request. 53 */ 54 protected $method; 55 56 /** 57 * @var string The Graph endpoint for this request. 58 */ 59 protected $endpoint; 60 61 /** 62 * @var array The headers to send with this request. 63 */ 64 protected $headers = []; 65 66 /** 67 * @var array The parameters to send with this request. 68 */ 69 protected $params = []; 70 71 /** 72 * @var array The files to send with this request. 73 */ 74 protected $files = []; 75 76 /** 77 * @var string ETag to send with this request. 78 */ 79 protected $eTag; 80 81 /** 82 * @var string Graph version to use for this request. 83 */ 84 protected $graphVersion; 85 86 /** 87 * Creates a new Request entity. 88 * 89 * @param FacebookApp|null $app 90 * @param AccessToken|string|null $accessToken 91 * @param string|null $method 92 * @param string|null $endpoint 93 * @param array|null $params 94 * @param string|null $eTag 95 * @param string|null $graphVersion 96 */ 97 public function __construct(FacebookApp $app = null, $accessToken = null, $method = null, $endpoint = null, array $params = [], $eTag = null, $graphVersion = null) 98 { 99 $this->setApp($app); 100 $this->setAccessToken($accessToken); 101 $this->setMethod($method); 102 $this->setEndpoint($endpoint); 103 $this->setParams($params); 104 $this->setETag($eTag); 105 $this->graphVersion = $graphVersion ?: Facebook::DEFAULT_GRAPH_VERSION; 106 } 107 108 /** 109 * Set the access token for this request. 110 * 111 * @param AccessToken|string|null 112 * 113 * @return FacebookRequest 114 */ 115 public function setAccessToken($accessToken) 116 { 117 $this->accessToken = $accessToken; 118 if ($accessToken instanceof AccessToken) { 119 $this->accessToken = $accessToken->getValue(); 120 } 121 122 return $this; 123 } 124 125 /** 126 * Sets the access token with one harvested from a URL or POST params. 127 * 128 * @param string $accessToken The access token. 129 * 130 * @return FacebookRequest 131 * 132 * @throws FacebookSDKException 133 */ 134 public function setAccessTokenFromParams($accessToken) 135 { 136 $existingAccessToken = $this->getAccessToken(); 137 if (!$existingAccessToken) { 138 $this->setAccessToken($accessToken); 139 } elseif ($accessToken !== $existingAccessToken) { 140 throw new FacebookSDKException('Access token mismatch. The access token provided in the FacebookRequest and the one provided in the URL or POST params do not match.'); 141 } 142 143 return $this; 144 } 145 146 /** 147 * Return the access token for this request. 148 * 149 * @return string|null 150 */ 151 public function getAccessToken() 152 { 153 return $this->accessToken; 154 } 155 156 /** 157 * Return the access token for this request as an AccessToken entity. 158 * 159 * @return AccessToken|null 160 */ 161 public function getAccessTokenEntity() 162 { 163 return $this->accessToken ? new AccessToken($this->accessToken) : null; 164 } 165 166 /** 167 * Set the FacebookApp entity used for this request. 168 * 169 * @param FacebookApp|null $app 170 */ 171 public function setApp(FacebookApp $app = null) 172 { 173 $this->app = $app; 174 } 175 176 /** 177 * Return the FacebookApp entity used for this request. 178 * 179 * @return FacebookApp 180 */ 181 public function getApp() 182 { 183 return $this->app; 184 } 185 186 /** 187 * Generate an app secret proof to sign this request. 188 * 189 * @return string|null 190 */ 191 public function getAppSecretProof() 192 { 193 if (!$accessTokenEntity = $this->getAccessTokenEntity()) { 194 return null; 195 } 196 197 return $accessTokenEntity->getAppSecretProof($this->app->getSecret()); 198 } 199 200 /** 201 * Validate that an access token exists for this request. 202 * 203 * @throws FacebookSDKException 204 */ 205 public function validateAccessToken() 206 { 207 $accessToken = $this->getAccessToken(); 208 if (!$accessToken) { 209 throw new FacebookSDKException('You must provide an access token.'); 210 } 211 } 212 213 /** 214 * Set the HTTP method for this request. 215 * 216 * @param string 217 */ 218 public function setMethod($method) 219 { 220 $this->method = strtoupper($method); 221 } 222 223 /** 224 * Return the HTTP method for this request. 225 * 226 * @return string 227 */ 228 public function getMethod() 229 { 230 return $this->method; 231 } 232 233 /** 234 * Validate that the HTTP method is set. 235 * 236 * @throws FacebookSDKException 237 */ 238 public function validateMethod() 239 { 240 if (!$this->method) { 241 throw new FacebookSDKException('HTTP method not specified.'); 242 } 243 244 if (!in_array($this->method, ['GET', 'POST', 'DELETE'])) { 245 throw new FacebookSDKException('Invalid HTTP method specified.'); 246 } 247 } 248 249 /** 250 * Set the endpoint for this request. 251 * 252 * @param string 253 * 254 * @return FacebookRequest 255 * 256 * @throws FacebookSDKException 257 */ 258 public function setEndpoint($endpoint) 259 { 260 // Harvest the access token from the endpoint to keep things in sync 261 $params = FacebookUrlManipulator::getParamsAsArray($endpoint); 262 if (isset($params['access_token'])) { 263 $this->setAccessTokenFromParams($params['access_token']); 264 } 265 266 // Clean the token & app secret proof from the endpoint. 267 $filterParams = ['access_token', 'appsecret_proof']; 268 $this->endpoint = FacebookUrlManipulator::removeParamsFromUrl($endpoint, $filterParams); 269 270 return $this; 271 } 272 273 /** 274 * Return the endpoint for this request. 275 * 276 * @return string 277 */ 278 public function getEndpoint() 279 { 280 // For batch requests, this will be empty 281 return $this->endpoint; 282 } 283 284 /** 285 * Generate and return the headers for this request. 286 * 287 * @return array 288 */ 289 public function getHeaders() 290 { 291 $headers = static::getDefaultHeaders(); 292 293 if ($this->eTag) { 294 $headers['If-None-Match'] = $this->eTag; 295 } 296 297 return array_merge($this->headers, $headers); 298 } 299 300 /** 301 * Set the headers for this request. 302 * 303 * @param array $headers 304 */ 305 public function setHeaders(array $headers) 306 { 307 $this->headers = array_merge($this->headers, $headers); 308 } 309 310 /** 311 * Sets the eTag value. 312 * 313 * @param string $eTag 314 */ 315 public function setETag($eTag) 316 { 317 $this->eTag = $eTag; 318 } 319 320 /** 321 * Set the params for this request. 322 * 323 * @param array $params 324 * 325 * @return FacebookRequest 326 * 327 * @throws FacebookSDKException 328 */ 329 public function setParams(array $params = []) 330 { 331 if (isset($params['access_token'])) { 332 $this->setAccessTokenFromParams($params['access_token']); 333 } 334 335 // Don't let these buggers slip in. 336 unset($params['access_token'], $params['appsecret_proof']); 337 338 // @TODO Refactor code above with this 339 //$params = $this->sanitizeAuthenticationParams($params); 340 $params = $this->sanitizeFileParams($params); 341 $this->dangerouslySetParams($params); 342 343 return $this; 344 } 345 346 /** 347 * Set the params for this request without filtering them first. 348 * 349 * @param array $params 350 * 351 * @return FacebookRequest 352 */ 353 public function dangerouslySetParams(array $params = []) 354 { 355 $this->params = array_merge($this->params, $params); 356 357 return $this; 358 } 359 360 /** 361 * Iterate over the params and pull out the file uploads. 362 * 363 * @param array $params 364 * 365 * @return array 366 */ 367 public function sanitizeFileParams(array $params) 368 { 369 foreach ($params as $key => $value) { 370 if ($value instanceof FacebookFile) { 371 $this->addFile($key, $value); 372 unset($params[$key]); 373 } 374 } 375 376 return $params; 377 } 378 379 /** 380 * Add a file to be uploaded. 381 * 382 * @param string $key 383 * @param FacebookFile $file 384 */ 385 public function addFile($key, FacebookFile $file) 386 { 387 $this->files[$key] = $file; 388 } 389 390 /** 391 * Removes all the files from the upload queue. 392 */ 393 public function resetFiles() 394 { 395 $this->files = []; 396 } 397 398 /** 399 * Get the list of files to be uploaded. 400 * 401 * @return array 402 */ 403 public function getFiles() 404 { 405 return $this->files; 406 } 407 408 /** 409 * Let's us know if there is a file upload with this request. 410 * 411 * @return boolean 412 */ 413 public function containsFileUploads() 414 { 415 return !empty($this->files); 416 } 417 418 /** 419 * Let's us know if there is a video upload with this request. 420 * 421 * @return boolean 422 */ 423 public function containsVideoUploads() 424 { 425 foreach ($this->files as $file) { 426 if ($file instanceof FacebookVideo) { 427 return true; 428 } 429 } 430 431 return false; 432 } 433 434 /** 435 * Returns the body of the request as multipart/form-data. 436 * 437 * @return RequestBodyMultipart 438 */ 439 public function getMultipartBody() 440 { 441 $params = $this->getPostParams(); 442 443 return new RequestBodyMultipart($params, $this->files); 444 } 445 446 /** 447 * Returns the body of the request as URL-encoded. 448 * 449 * @return RequestBodyUrlEncoded 450 */ 451 public function getUrlEncodedBody() 452 { 453 $params = $this->getPostParams(); 454 455 return new RequestBodyUrlEncoded($params); 456 } 457 458 /** 459 * Generate and return the params for this request. 460 * 461 * @return array 462 */ 463 public function getParams() 464 { 465 $params = $this->params; 466 467 $accessToken = $this->getAccessToken(); 468 if ($accessToken) { 469 $params['access_token'] = $accessToken; 470 $params['appsecret_proof'] = $this->getAppSecretProof(); 471 } 472 473 return $params; 474 } 475 476 /** 477 * Only return params on POST requests. 478 * 479 * @return array 480 */ 481 public function getPostParams() 482 { 483 if ($this->getMethod() === 'POST') { 484 return $this->getParams(); 485 } 486 487 return []; 488 } 489 490 /** 491 * The graph version used for this request. 492 * 493 * @return string 494 */ 495 public function getGraphVersion() 496 { 497 return $this->graphVersion; 498 } 499 500 /** 501 * Generate and return the URL for this request. 502 * 503 * @return string 504 */ 505 public function getUrl() 506 { 507 $this->validateMethod(); 508 509 $graphVersion = FacebookUrlManipulator::forceSlashPrefix($this->graphVersion); 510 $endpoint = FacebookUrlManipulator::forceSlashPrefix($this->getEndpoint()); 511 512 $url = $graphVersion . $endpoint; 513 514 if ($this->getMethod() !== 'POST') { 515 $params = $this->getParams(); 516 $url = FacebookUrlManipulator::appendParamsToUrl($url, $params); 517 } 518 519 return $url; 520 } 521 522 /** 523 * Return the default headers that every request should use. 524 * 525 * @return array 526 */ 527 public static function getDefaultHeaders() 528 { 529 return [ 530 'User-Agent' => 'fb-php-' . Facebook::VERSION, 531 'Accept-Encoding' => '*', 532 ]; 533 } 534} 535