├── .github └── workflows │ └── test.yml ├── LICENSE ├── composer.json └── src └── Facebook ├── Authentication ├── AccessToken.php ├── AccessTokenMetadata.php └── OAuth2Client.php ├── Exceptions ├── FacebookAuthenticationException.php ├── FacebookAuthorizationException.php ├── FacebookClientException.php ├── FacebookOtherException.php ├── FacebookResponseException.php ├── FacebookResumableUploadException.php ├── FacebookSDKException.php ├── FacebookServerException.php └── FacebookThrottleException.php ├── Facebook.php ├── FacebookApp.php ├── FacebookBatchRequest.php ├── FacebookBatchResponse.php ├── FacebookClient.php ├── FacebookRequest.php ├── FacebookResponse.php ├── FileUpload ├── FacebookFile.php ├── FacebookResumableUploader.php ├── FacebookTransferChunk.php ├── FacebookVideo.php └── Mimetypes.php ├── GraphNodes ├── Birthday.php ├── Collection.php ├── GraphAgeRange.php ├── GraphAlbum.php ├── GraphApplication.php ├── GraphApplicationEventsConfig.php ├── GraphApplicationObjectStoreURLs.php ├── GraphApplicationRestrictions.php ├── GraphArchivedAd.php ├── GraphCoverPhoto.php ├── GraphEdge.php ├── GraphEngagement.php ├── GraphEvent.php ├── GraphExperience.php ├── GraphGroup.php ├── GraphInsightsRangeValue.php ├── GraphLocation.php ├── GraphMailingAddress.php ├── GraphNode.php ├── GraphNodeFactory.php ├── GraphPage.php ├── GraphPaymentPricePoint.php ├── GraphPaymentPricePoints.php ├── GraphPermission.php ├── GraphPhoto.php ├── GraphPicture.php ├── GraphPlace.php ├── GraphPlatformImageSource.php ├── GraphSessionInfo.php ├── GraphUser.php ├── GraphVideo.php ├── GraphVideoFormat.php └── GraphVideoUploadLimits.php ├── Helpers ├── FacebookCanvasHelper.php ├── FacebookJavaScriptHelper.php ├── FacebookPageTabHelper.php ├── FacebookRedirectLoginHelper.php └── FacebookSignedRequestFromInputHelper.php ├── Http ├── GraphRawResponse.php ├── RequestBodyInterface.php ├── RequestBodyMultipart.php └── RequestBodyUrlEncoded.php ├── HttpClients ├── FacebookCurl.php ├── FacebookCurlHttpClient.php ├── FacebookGuzzleHttpClient.php ├── FacebookHttpClientInterface.php ├── FacebookStream.php ├── FacebookStreamHttpClient.php ├── HttpClientsFactory.php └── certs │ └── DigiCertHighAssuranceEVRootCA.pem ├── PersistentData ├── FacebookMemoryPersistentDataHandler.php ├── FacebookSessionPersistentDataHandler.php ├── PersistentDataFactory.php └── PersistentDataInterface.php ├── PseudoRandomString ├── OpenSslPseudoRandomStringGenerator.php ├── PseudoRandomStringGeneratorFactory.php ├── PseudoRandomStringGeneratorInterface.php ├── PseudoRandomStringGeneratorTrait.php ├── RandomBytesPseudoRandomStringGenerator.php └── UrandomPseudoRandomStringGenerator.php ├── SignedRequest.php └── Url ├── FacebookUrlDetectionHandler.php ├── FacebookUrlManipulator.php └── UrlDetectionInterface.php /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | - develop 7 | push: 8 | branches: 9 | - master 10 | jobs: 11 | test: 12 | runs-on: ubuntu-24.04 13 | strategy: 14 | matrix: 15 | php: [ '8.1', '8.2', '8.3', '8.4' ] 16 | steps: 17 | - uses: actions/checkout@v4 18 | - run: mkdir -p build/logs 19 | - name: Test PHP 20 | uses: shivammathur/setup-php@v2 21 | with: 22 | php-version: ${{matrix.php}} 23 | - run: composer install 24 | - run: php vendor/bin/phpunit 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Facebook, Inc. 2 | 3 | You are hereby granted a non-exclusive, worldwide, royalty-free license to 4 | use, copy, modify, and distribute this software in source code or binary 5 | form for use in connection with the web services and APIs provided by 6 | Facebook. 7 | 8 | As with any software that integrates with the Facebook platform, your use 9 | of this software is subject to the Facebook Developer Principles and 10 | Policies [http://developers.facebook.com/policy/]. This copyright notice 11 | shall be included in all copies or substantial portions of the software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nickdnk/graph-sdk", 3 | "description": "Facebook SDK for PHP 8+", 4 | "keywords": ["facebook", "sdk"], 5 | "type": "library", 6 | "homepage": "https://github.com/nickdnk/php-graph-sdk", 7 | "license": "proprietary", 8 | "authors": [ 9 | { 10 | "name": "Facebook", 11 | "homepage": "https://github.com/facebook/php-graph-sdk/contributors" 12 | } 13 | ], 14 | "require": { 15 | "php": "^8.1" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "^9.5", 19 | "mockery/mockery": "^1.6.12", 20 | "guzzlehttp/guzzle": "^7.9.2" 21 | }, 22 | "conflict": { 23 | "guzzlehttp/guzzle": "<6.0" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Facebook\\": "src/Facebook/" 28 | } 29 | }, 30 | "autoload-dev": { 31 | "psr-4": { 32 | "Facebook\\Tests\\": "tests/" 33 | } 34 | }, 35 | "replace": { 36 | "facebook/graph-sdk": ">=5.7.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Facebook/Authentication/AccessToken.php: -------------------------------------------------------------------------------- 1 | value = $accessToken; 55 | if ($expiresAt) { 56 | $this->setExpiresAtFromTimeStamp($expiresAt); 57 | } 58 | } 59 | 60 | /** 61 | * Generate an app secret proof to sign a request to Graph. 62 | * 63 | * @param string $appSecret The app secret. 64 | * 65 | * @return string 66 | */ 67 | public function getAppSecretProof(string $appSecret): string 68 | { 69 | return hash_hmac('sha256', $this->value, $appSecret); 70 | } 71 | 72 | /** 73 | * Getter for expiresAt. 74 | * 75 | * @return DateTime|null 76 | */ 77 | public function getExpiresAt(): ?DateTime 78 | { 79 | return $this->expiresAt; 80 | } 81 | 82 | /** 83 | * Determines whether or not this is an app access token. 84 | */ 85 | public function isAppAccessToken(): bool 86 | { 87 | return str_contains($this->value, '|'); 88 | } 89 | 90 | /** 91 | * Determines whether or not this is a long-lived token. 92 | * 93 | * @return bool 94 | */ 95 | public function isLongLived(): bool 96 | { 97 | if ($this->expiresAt) { 98 | return $this->expiresAt->getTimestamp() > time() + (60 * 60 * 2); 99 | } 100 | 101 | return $this->isAppAccessToken(); 102 | 103 | } 104 | 105 | /** 106 | * Checks the expiration of the access token. 107 | */ 108 | public function isExpired(): bool 109 | { 110 | if ($this->getExpiresAt() instanceof DateTime) { 111 | return $this->getExpiresAt()->getTimestamp() < time(); 112 | } 113 | 114 | return false; 115 | 116 | } 117 | 118 | /** 119 | * Returns the access token as a string. 120 | */ 121 | public function getValue(): string 122 | { 123 | return $this->value; 124 | } 125 | 126 | /** 127 | * Returns the access token as a string. 128 | * 129 | * @return string 130 | */ 131 | public function __toString() 132 | { 133 | return $this->getValue(); 134 | } 135 | 136 | protected function setExpiresAtFromTimeStamp(int $timeStamp): void 137 | { 138 | $dt = new DateTime(); 139 | $dt->setTimestamp($timeStamp); 140 | $this->expiresAt = $dt; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Facebook/Exceptions/FacebookAuthenticationException.php: -------------------------------------------------------------------------------- 1 | response = $response; 54 | $this->responseData = $response->getDecodedBody(); 55 | 56 | $errorMessage = $this->get('message', 'Unknown error from Graph.'); 57 | $errorCode = $this->get('code', -1); 58 | 59 | parent::__construct($errorMessage, $errorCode, $previousException); 60 | } 61 | 62 | /** 63 | * A factory for creating the appropriate exception based on the response from Graph. 64 | * 65 | * @param FacebookResponse $response The response that threw the exception. 66 | * 67 | * @return FacebookResponseException 68 | */ 69 | public static function create(FacebookResponse $response): FacebookResponseException 70 | { 71 | $data = $response->getDecodedBody(); 72 | 73 | if (!isset($data['error']['code']) && isset($data['code'])) { 74 | $data = ['error' => $data]; 75 | } 76 | 77 | $code = $data['error']['code'] ?? null; 78 | $message = $data['error']['message'] ?? 'Unknown error from Graph.'; 79 | 80 | if (isset($data['error']['error_subcode'])) { 81 | switch ($data['error']['error_subcode']) { 82 | // Other authentication issues 83 | case 458: 84 | case 459: 85 | case 460: 86 | case 463: 87 | case 464: 88 | case 467: 89 | return new static($response, new FacebookAuthenticationException($message, $code)); 90 | // Video upload resumable error 91 | case 1363030: 92 | case 1363019: 93 | case 1363033: 94 | case 1363021: 95 | case 1363041: 96 | return new static($response, new FacebookResumableUploadException($message, $code)); 97 | case 1363037: 98 | $previousException = new FacebookResumableUploadException($message, $code); 99 | 100 | $startOffset = isset($data['error']['error_data']['start_offset']) ? (int)$data['error']['error_data']['start_offset'] : null; 101 | $previousException->setStartOffset($startOffset); 102 | 103 | $endOffset = isset($data['error']['error_data']['end_offset']) ? (int)$data['error']['error_data']['end_offset'] : null; 104 | $previousException->setEndOffset($endOffset); 105 | 106 | return new static($response, $previousException); 107 | } 108 | } 109 | 110 | switch ($code) { 111 | // Login status or token expired, revoked, or invalid 112 | case 100: 113 | case 102: 114 | case 190: 115 | return new static($response, new FacebookAuthenticationException($message, $code)); 116 | 117 | // Server issue, possible downtime 118 | case 1: 119 | case 2: 120 | return new static($response, new FacebookServerException($message, $code)); 121 | 122 | // API Throttling 123 | case 4: 124 | case 17: 125 | case 32: 126 | case 341: 127 | case 613: 128 | return new static($response, new FacebookThrottleException($message, $code)); 129 | 130 | // Duplicate Post 131 | case 506: 132 | return new static($response, new FacebookClientException($message, $code)); 133 | } 134 | 135 | // Missing Permissions 136 | if ($code == 10 || ($code >= 200 && $code <= 299)) { 137 | return new static($response, new FacebookAuthorizationException($message, $code)); 138 | } 139 | 140 | // OAuth authentication error 141 | if (isset($data['error']['type']) && $data['error']['type'] === 'OAuthException') { 142 | return new static($response, new FacebookAuthenticationException($message, $code)); 143 | } 144 | 145 | // All others 146 | return new static($response, new FacebookOtherException($message, $code)); 147 | } 148 | 149 | /** 150 | * Checks isset and returns that or a default value. 151 | */ 152 | private function get(string $key, mixed $default = null) 153 | { 154 | if (isset($this->responseData['error'][$key])) { 155 | return $this->responseData['error'][$key]; 156 | } 157 | 158 | return $default; 159 | } 160 | 161 | /** 162 | * Returns the HTTP status code 163 | */ 164 | public function getHttpStatusCode(): ?int 165 | { 166 | return $this->response->getHttpStatusCode(); 167 | } 168 | 169 | /** 170 | * Returns the sub-error code 171 | */ 172 | public function getSubErrorCode(): int 173 | { 174 | return $this->get('error_subcode', -1); 175 | } 176 | 177 | /** 178 | * Returns the error type 179 | */ 180 | public function getErrorType(): string 181 | { 182 | return $this->get('type', ''); 183 | } 184 | 185 | /** 186 | * Returns the raw response used to create the exception. 187 | */ 188 | public function getRawResponse(): ?string 189 | { 190 | return $this->response->getBody(); 191 | } 192 | 193 | /** 194 | * Returns the decoded response used to create the exception. 195 | */ 196 | public function getResponseData(): ?array 197 | { 198 | return $this->responseData; 199 | } 200 | 201 | /** 202 | * Returns the response entity used to create the exception. 203 | * 204 | * @return FacebookResponse 205 | */ 206 | public function getResponse(): FacebookResponse 207 | { 208 | return $this->response; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/Facebook/Exceptions/FacebookResumableUploadException.php: -------------------------------------------------------------------------------- 1 | startOffset; 39 | } 40 | 41 | public function setStartOffset(?int $startOffset): void 42 | { 43 | $this->startOffset = $startOffset; 44 | } 45 | 46 | public function getEndOffset(): ?int 47 | { 48 | return $this->endOffset; 49 | } 50 | 51 | public function setEndOffset(?int $endOffset): void 52 | { 53 | $this->endOffset = $endOffset; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Facebook/Exceptions/FacebookSDKException.php: -------------------------------------------------------------------------------- 1 | id = (string) $id; 53 | $this->secret = $secret; 54 | } 55 | 56 | /** 57 | * Returns the app ID. 58 | */ 59 | public function getId(): string 60 | { 61 | return $this->id; 62 | } 63 | 64 | /** 65 | * Returns the app secret. 66 | */ 67 | public function getSecret(): string 68 | { 69 | return $this->secret; 70 | } 71 | 72 | /** 73 | * Returns an app access token. 74 | */ 75 | public function getAccessToken(): AccessToken 76 | { 77 | return new AccessToken($this->id . '|' . $this->secret); 78 | } 79 | 80 | /** 81 | * Serializes the FacebookApp entity as a string. 82 | */ 83 | public function serialize(): string 84 | { 85 | return implode('|', [$this->id, $this->secret]); 86 | } 87 | 88 | public function __serialize(): array 89 | { 90 | return ['id' => $this->id, 'secret' => $this->secret]; 91 | } 92 | 93 | /** 94 | * Unserializes a string as a FacebookApp entity. 95 | * @throws FacebookSDKException 96 | */ 97 | public function unserialize(string $data): void 98 | { 99 | list($id, $secret) = explode('|', $data); 100 | 101 | $this->__construct($id, $secret); 102 | } 103 | 104 | public function __unserialize($data): void 105 | { 106 | $this->__construct($data['id'], $data['secret']); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Facebook/FacebookBatchResponse.php: -------------------------------------------------------------------------------- 1 | batchRequest = $batchRequest; 56 | 57 | $request = $response->getRequest(); 58 | $body = $response->getBody(); 59 | $httpStatusCode = $response->getHttpStatusCode(); 60 | $headers = $response->getHeaders(); 61 | parent::__construct($request, $body, $httpStatusCode, $headers); 62 | 63 | $responses = $response->getDecodedBody(); 64 | $this->setResponses($responses); 65 | } 66 | 67 | /** 68 | * Returns an array of FacebookResponse entities. 69 | */ 70 | public function getResponses(): array 71 | { 72 | return $this->responses; 73 | } 74 | 75 | /** 76 | * The main batch response will be an array of requests so 77 | * we need to iterate over all the responses. 78 | */ 79 | public function setResponses(array $responses): void 80 | { 81 | $this->responses = []; 82 | 83 | foreach ($responses as $key => $graphResponse) { 84 | $this->addResponse($key, $graphResponse); 85 | } 86 | } 87 | 88 | /** 89 | * Add a response to the list. 90 | */ 91 | public function addResponse(int $key, ?array $response): void 92 | { 93 | $originalRequestName = isset($this->batchRequest[$key]['name']) ? $this->batchRequest[$key]['name'] : $key; 94 | $originalRequest = isset($this->batchRequest[$key]['request']) ? $this->batchRequest[$key]['request'] : null; 95 | 96 | $httpResponseBody = $response['body'] ?? null; 97 | $httpResponseCode = $response['code'] ?? null; 98 | $httpResponseHeaders = isset($response['headers']) ? array_column($response['headers'], 'value', 'name') : []; 99 | 100 | $this->responses[$originalRequestName] = new FacebookResponse( 101 | $originalRequest, 102 | $httpResponseBody, 103 | $httpResponseCode, 104 | $httpResponseHeaders 105 | ); 106 | } 107 | 108 | /** 109 | * @inheritdoc 110 | */ 111 | public function getIterator(): ArrayIterator 112 | { 113 | return new ArrayIterator($this->responses); 114 | } 115 | 116 | /** 117 | * @inheritdoc 118 | */ 119 | public function offsetSet($offset, $value): void 120 | { 121 | $this->addResponse($offset, $value); 122 | } 123 | 124 | /** 125 | * @inheritdoc 126 | */ 127 | public function offsetExists($offset): bool 128 | { 129 | return isset($this->responses[$offset]); 130 | } 131 | 132 | /** 133 | * @inheritdoc 134 | */ 135 | public function offsetUnset($offset): void 136 | { 137 | unset($this->responses[$offset]); 138 | } 139 | 140 | /** 141 | * @inheritdoc 142 | */ 143 | public function offsetGet($offset): mixed 144 | { 145 | return $this->responses[$offset] ?? null; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Facebook/FacebookClient.php: -------------------------------------------------------------------------------- 1 | httpClientHandler = $httpClientHandler ?: $this->detectHttpClientHandler(); 95 | $this->enableBetaMode = $enableBeta; 96 | } 97 | 98 | /** 99 | * Sets the HTTP client handler. 100 | */ 101 | public function setHttpClientHandler(FacebookHttpClientInterface $httpClientHandler): void 102 | { 103 | $this->httpClientHandler = $httpClientHandler; 104 | } 105 | 106 | /** 107 | * Returns the HTTP client handler. 108 | */ 109 | public function getHttpClientHandler(): FacebookHttpClientInterface 110 | { 111 | return $this->httpClientHandler; 112 | } 113 | 114 | /** 115 | * Detects which HTTP client handler to use. 116 | */ 117 | public function detectHttpClientHandler(): FacebookHttpClientInterface 118 | { 119 | if (class_exists('GuzzleHttp\Client')) { 120 | return new FacebookGuzzleHttpClient(); 121 | } 122 | return extension_loaded('curl') ? new FacebookCurlHttpClient() : new FacebookStreamHttpClient(); 123 | } 124 | 125 | /** 126 | * Toggle beta mode. 127 | * 128 | * @param boolean $betaMode 129 | */ 130 | public function enableBetaMode(bool $betaMode = true): void 131 | { 132 | $this->enableBetaMode = $betaMode; 133 | } 134 | 135 | /** 136 | * Returns the base Graph URL. 137 | * 138 | * @param boolean $postToVideoUrl Post to the video API if videos are being uploaded. 139 | */ 140 | public function getBaseGraphUrl(bool $postToVideoUrl = false): string 141 | { 142 | if ($postToVideoUrl) { 143 | return $this->enableBetaMode ? static::BASE_GRAPH_VIDEO_URL_BETA : static::BASE_GRAPH_VIDEO_URL; 144 | } 145 | 146 | return $this->enableBetaMode ? static::BASE_GRAPH_URL_BETA : static::BASE_GRAPH_URL; 147 | } 148 | 149 | /** 150 | * Prepares the request for sending to the client handler. 151 | * 152 | * @throws FacebookSDKException 153 | */ 154 | public function prepareRequestMessage(FacebookRequest $request): array 155 | { 156 | $postToVideoUrl = $request->containsVideoUploads(); 157 | $url = $this->getBaseGraphUrl($postToVideoUrl) . $request->getUrl(); 158 | 159 | // If we're sending files they should be sent as multipart/form-data 160 | if ($request->containsFileUploads()) { 161 | $requestBody = $request->getMultipartBody(); 162 | $request->setHeaders([ 163 | 'Content-Type' => 'multipart/form-data; boundary=' . $requestBody->getBoundary(), 164 | ]); 165 | } else { 166 | $requestBody = $request->getUrlEncodedBody(); 167 | $request->setHeaders([ 168 | 'Content-Type' => 'application/x-www-form-urlencoded', 169 | ]); 170 | } 171 | 172 | return [ 173 | $url, 174 | $request->getMethod(), 175 | $request->getHeaders(), 176 | $requestBody->getBody(), 177 | ]; 178 | } 179 | 180 | /** 181 | * Makes the request to Graph and returns the result. 182 | * 183 | * @throws FacebookSDKException 184 | */ 185 | public function sendRequest(FacebookRequest $request): FacebookResponse 186 | { 187 | if (get_class($request) === FacebookRequest::class) { 188 | $request->validateAccessToken(); 189 | } 190 | 191 | list($url, $method, $headers, $body) = $this->prepareRequestMessage($request); 192 | 193 | // Since file uploads can take a while, we need to give more time for uploads 194 | $timeOut = static::DEFAULT_REQUEST_TIMEOUT; 195 | if ($request->containsFileUploads()) { 196 | $timeOut = static::DEFAULT_FILE_UPLOAD_REQUEST_TIMEOUT; 197 | } elseif ($request->containsVideoUploads()) { 198 | $timeOut = static::DEFAULT_VIDEO_UPLOAD_REQUEST_TIMEOUT; 199 | } 200 | 201 | // Should throw `FacebookSDKException` exception on HTTP client error. 202 | // Don't catch to allow it to bubble up. 203 | $rawResponse = $this->httpClientHandler->send($url, $method, $body, $headers, $timeOut); 204 | 205 | static::$requestCount++; 206 | 207 | $returnResponse = new FacebookResponse( 208 | $request, 209 | $rawResponse->getBody(), 210 | $rawResponse->getHttpResponseCode(), 211 | $rawResponse->getHeaders() 212 | ); 213 | 214 | if ($returnResponse->isError()) { 215 | throw $returnResponse->getThrownException(); 216 | } 217 | 218 | return $returnResponse; 219 | } 220 | 221 | /** 222 | * Makes a batched request to Graph and returns the result. 223 | * 224 | * @throws FacebookSDKException 225 | */ 226 | public function sendBatchRequest(FacebookBatchRequest $request): FacebookBatchResponse 227 | { 228 | $request->prepareRequestsForBatch(); 229 | $facebookResponse = $this->sendRequest($request); 230 | 231 | return new FacebookBatchResponse($request, $facebookResponse); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/Facebook/FileUpload/FacebookFile.php: -------------------------------------------------------------------------------- 1 | path = $filePath; 63 | $this->maxLength = $maxLength; 64 | $this->offset = $offset; 65 | $this->open(); 66 | } 67 | 68 | /** 69 | * Closes the stream when destructed. 70 | */ 71 | public function __destruct() 72 | { 73 | $this->close(); 74 | } 75 | 76 | /** 77 | * Opens a stream for the file. 78 | * 79 | * @throws FacebookSDKException 80 | */ 81 | public function open(): void 82 | { 83 | if (!$this->isRemoteFile($this->path) && !is_readable($this->path)) { 84 | throw new FacebookSDKException('Failed to create FacebookFile entity. Unable to read resource: ' . $this->path . '.'); 85 | } 86 | 87 | $this->stream = fopen($this->path, 'r'); 88 | 89 | if (!$this->stream) { 90 | throw new FacebookSDKException('Failed to create FacebookFile entity. Unable to open resource: ' . $this->path . '.'); 91 | } 92 | } 93 | 94 | /** 95 | * Stops the file stream. 96 | */ 97 | public function close(): void 98 | { 99 | if (is_resource($this->stream)) { 100 | fclose($this->stream); 101 | } 102 | } 103 | 104 | /** 105 | * Return the contents of the file. 106 | * 107 | */ 108 | public function getContents(): false|string 109 | { 110 | return stream_get_contents($this->stream, $this->maxLength, $this->offset); 111 | } 112 | 113 | /** 114 | * Return the name of the file. 115 | */ 116 | public function getFileName(): string 117 | { 118 | return basename($this->path); 119 | } 120 | 121 | /** 122 | * Return the path of the file. 123 | */ 124 | public function getFilePath(): string 125 | { 126 | return $this->path; 127 | } 128 | 129 | /** 130 | * Return the size of the file. 131 | */ 132 | public function getSize(): int 133 | { 134 | return filesize($this->path); 135 | } 136 | 137 | /** 138 | * Return the mimetype of the file. 139 | */ 140 | public function getMimetype(): string 141 | { 142 | return Mimetypes::getInstance()->fromFilename($this->path) ?: 'text/plain'; 143 | } 144 | 145 | /** 146 | * Returns true if the path to the file is remote. 147 | */ 148 | protected function isRemoteFile(string $pathToFile): bool 149 | { 150 | return preg_match('/^(https?|ftp):\/\/.*/', $pathToFile) === 1; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Facebook/FileUpload/FacebookResumableUploader.php: -------------------------------------------------------------------------------- 1 | app = $app; 57 | $this->client = $client; 58 | $this->accessToken = $accessToken; 59 | $this->graphVersion = $graphVersion; 60 | } 61 | 62 | /** 63 | * Upload by chunks - start phase 64 | * 65 | * @param string $endpoint 66 | * @param FacebookFile $file 67 | * 68 | * @return FacebookTransferChunk 69 | * 70 | * @throws FacebookSDKException 71 | */ 72 | public function start(string $endpoint, FacebookFile $file): FacebookTransferChunk 73 | { 74 | $params = [ 75 | 'upload_phase' => 'start', 76 | 'file_size' => $file->getSize(), 77 | ]; 78 | $response = $this->sendUploadRequest($endpoint, $params); 79 | 80 | return new FacebookTransferChunk($file, $response['upload_session_id'], $response['video_id'], $response['start_offset'], $response['end_offset']); 81 | } 82 | 83 | /** 84 | * Upload by chunks - transfer phase 85 | * 86 | * @param string $endpoint 87 | * @param FacebookTransferChunk $chunk 88 | * @param boolean $allowToThrow 89 | * 90 | * @return FacebookTransferChunk 91 | * 92 | * @throws FacebookResponseException 93 | * @throws FacebookSDKException 94 | */ 95 | public function transfer(string $endpoint, FacebookTransferChunk $chunk, bool $allowToThrow = false): FacebookTransferChunk 96 | { 97 | $params = [ 98 | 'upload_phase' => 'transfer', 99 | 'upload_session_id' => $chunk->getUploadSessionId(), 100 | 'start_offset' => $chunk->getStartOffset(), 101 | 'video_file_chunk' => $chunk->getPartialFile(), 102 | ]; 103 | 104 | try { 105 | $response = $this->sendUploadRequest($endpoint, $params); 106 | } catch (FacebookResponseException $e) { 107 | $preException = $e->getPrevious(); 108 | if ($allowToThrow || !$preException instanceof FacebookResumableUploadException) { 109 | throw $e; 110 | } 111 | 112 | if (null !== $preException->getStartOffset() && null !== $preException->getEndOffset()) { 113 | return new FacebookTransferChunk( 114 | $chunk->getFile(), 115 | $chunk->getUploadSessionId(), 116 | $chunk->getVideoId(), 117 | $preException->getStartOffset(), 118 | $preException->getEndOffset() 119 | ); 120 | } 121 | 122 | // Return the same chunk entity so it can be retried. 123 | return $chunk; 124 | } 125 | 126 | return new FacebookTransferChunk($chunk->getFile(), $chunk->getUploadSessionId(), $chunk->getVideoId(), $response['start_offset'], $response['end_offset']); 127 | } 128 | 129 | /** 130 | * Upload by chunks - finish phase 131 | * 132 | * @param string $endpoint 133 | * @param string $uploadSessionId 134 | * @param array $metadata The metadata associated with the file. 135 | * 136 | * @return bool 137 | * @throws FacebookSDKException 138 | */ 139 | public function finish(string $endpoint, string $uploadSessionId, array $metadata = []): bool 140 | { 141 | $params = array_merge($metadata, [ 142 | 'upload_phase' => 'finish', 143 | 'upload_session_id' => $uploadSessionId, 144 | ]); 145 | $response = $this->sendUploadRequest($endpoint, $params); 146 | 147 | return $response['success']; 148 | } 149 | 150 | /** 151 | * Helper to make a FacebookRequest and send it. 152 | * 153 | * @param string $endpoint The endpoint to POST to. 154 | * @param array $params The params to send with the request. 155 | * 156 | * @throws FacebookSDKException 157 | */ 158 | private function sendUploadRequest(string $endpoint, array $params = []): ?array 159 | { 160 | $request = new FacebookRequest($this->app, $this->accessToken, 'POST', $endpoint, $params, null, $this->graphVersion); 161 | 162 | return $this->client->sendRequest($request)->getDecodedBody(); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/Facebook/FileUpload/FacebookTransferChunk.php: -------------------------------------------------------------------------------- 1 | file = $file; 63 | $this->uploadSessionId = $uploadSessionId; 64 | $this->videoId = $videoId; 65 | $this->startOffset = $startOffset; 66 | $this->endOffset = $endOffset; 67 | } 68 | 69 | /** 70 | * Return the file entity. 71 | * 72 | * @return FacebookFile 73 | */ 74 | public function getFile(): FacebookFile 75 | { 76 | return $this->file; 77 | } 78 | 79 | /** 80 | * Return a FacebookFile entity with partial content. 81 | * @throws FacebookSDKException 82 | */ 83 | public function getPartialFile(): FacebookFile 84 | { 85 | $maxLength = $this->endOffset - $this->startOffset; 86 | 87 | return new FacebookFile($this->file->getFilePath(), $maxLength, $this->startOffset); 88 | } 89 | 90 | public function getUploadSessionId(): int 91 | { 92 | return $this->uploadSessionId; 93 | } 94 | 95 | public function isLastChunk(): bool 96 | { 97 | return $this->startOffset === $this->endOffset; 98 | } 99 | 100 | public function getStartOffset(): int 101 | { 102 | return $this->startOffset; 103 | } 104 | 105 | public function getEndOffset(): int 106 | { 107 | return $this->endOffset; 108 | } 109 | 110 | public function getVideoId(): int 111 | { 112 | return $this->videoId; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Facebook/FileUpload/FacebookVideo.php: -------------------------------------------------------------------------------- 1 | hasYear = $count === 3 || $count === 1; 53 | $this->hasDate = $count === 3 || $count === 2; 54 | 55 | parent::__construct($date); 56 | } 57 | 58 | /** 59 | * Returns whether date object contains birth day and month 60 | */ 61 | public function hasDate(): bool 62 | { 63 | return $this->hasDate; 64 | } 65 | 66 | /** 67 | * Returns whether date object contains birth year 68 | */ 69 | public function hasYear(): bool 70 | { 71 | return $this->hasYear; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/Collection.php: -------------------------------------------------------------------------------- 1 | items = $items; 57 | } 58 | 59 | /** 60 | * Gets the value of a field from the Graph node. 61 | * 62 | * @param string $name The field to retrieve. 63 | * @param mixed $default The default to return if the field doesn't exist. 64 | * 65 | * @return mixed 66 | */ 67 | public function getField(string $name, mixed $default = null): mixed 68 | { 69 | if (isset($this->items[$name])) { 70 | return $this->items[$name]; 71 | } 72 | 73 | return $default; 74 | } 75 | 76 | /** 77 | * Returns a list of all fields set on the object. 78 | */ 79 | public function getFieldNames(): array 80 | { 81 | return array_keys($this->items); 82 | } 83 | 84 | /** 85 | * Get all of the items in the collection. 86 | */ 87 | public function all(): array 88 | { 89 | return $this->items; 90 | } 91 | 92 | /** 93 | * Get the collection of items as a plain array. 94 | */ 95 | public function asArray(): array 96 | { 97 | return array_map(function ($value) { 98 | return $value instanceof Collection ? $value->asArray() : $value; 99 | }, $this->items); 100 | } 101 | 102 | /** 103 | * Run a map over each of the items. 104 | */ 105 | public function map(Closure $callback): static 106 | { 107 | return new static(array_map($callback, $this->items, array_keys($this->items))); 108 | } 109 | 110 | /** 111 | * Get the collection of items as JSON. 112 | */ 113 | public function asJson(int $options = 0): string 114 | { 115 | return json_encode($this->asArray(), $options); 116 | } 117 | 118 | /** 119 | * Count the number of items in the collection. 120 | */ 121 | public function count(): int 122 | { 123 | return count($this->items); 124 | } 125 | 126 | /** 127 | * Get an iterator for the items. 128 | */ 129 | public function getIterator(): ArrayIterator 130 | { 131 | return new ArrayIterator($this->items); 132 | } 133 | 134 | /** 135 | * Determine if an item exists at an offset. 136 | */ 137 | public function offsetExists(mixed $offset): bool 138 | { 139 | return array_key_exists($offset, $this->items); 140 | } 141 | 142 | /** 143 | * Get an item at a given offset. 144 | */ 145 | public function offsetGet(mixed $offset): mixed 146 | { 147 | return $this->items[$offset] ?? null; 148 | } 149 | 150 | /** 151 | * Set the item at a given offset. 152 | */ 153 | public function offsetSet(mixed $offset, mixed $value): void 154 | { 155 | if (is_null($offset)) { 156 | $this->items[] = $value; 157 | } else { 158 | $this->items[$offset] = $value; 159 | } 160 | } 161 | 162 | /** 163 | * Unset the item at a given offset. 164 | */ 165 | public function offsetUnset(mixed $offset): void 166 | { 167 | unset($this->items[$offset]); 168 | } 169 | 170 | /** 171 | * Convert the collection to its string representation. 172 | */ 173 | public function __toString(): string 174 | { 175 | return $this->asJson(); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphAgeRange.php: -------------------------------------------------------------------------------- 1 | getField('max'); 13 | } 14 | 15 | public function getMin(): ?int 16 | { 17 | return $this->getField('min'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphAlbum.php: -------------------------------------------------------------------------------- 1 | GraphUser::class, 40 | 'place' => GraphPlace::class, 41 | 'event' => GraphEvent::class, 42 | 'cover_photo' => GraphPhoto::class, 43 | ]; 44 | 45 | public function getId(): ?string 46 | { 47 | return $this->getField('id'); 48 | } 49 | 50 | public function getBackdatedTime(): ?DateTime 51 | { 52 | return $this->getField('backdated_time'); 53 | } 54 | 55 | public function getCanUpload(): bool 56 | { 57 | return (bool)$this->getField('can_upload'); 58 | } 59 | 60 | public function getCount(): ?int 61 | { 62 | return $this->getField('count'); 63 | } 64 | 65 | public function getCoverPhoto(): ?GraphPhoto 66 | { 67 | return $this->getField('cover_photo'); 68 | } 69 | 70 | public function getCreatedTime(): ?DateTime 71 | { 72 | return $this->getField('created_time'); 73 | } 74 | 75 | public function getUpdatedTime(): ?DateTime 76 | { 77 | return $this->getField('updated_time'); 78 | } 79 | 80 | public function getDescription(): ?string 81 | { 82 | return $this->getField('description'); 83 | } 84 | 85 | public function getFrom(): ?GraphUser 86 | { 87 | return $this->getField('from'); 88 | } 89 | 90 | public function getPlace(): ?GraphPlace 91 | { 92 | return $this->getField('place'); 93 | } 94 | 95 | public function getLink(): ?string 96 | { 97 | return $this->getField('link'); 98 | } 99 | 100 | public function getLocation(): ?string 101 | { 102 | return $this->getField('location'); 103 | } 104 | 105 | public function getName(): ?string 106 | { 107 | return $this->getField('name'); 108 | } 109 | 110 | public function getPrivacy(): ?string 111 | { 112 | return $this->getField('privacy'); 113 | } 114 | 115 | /** 116 | * One of `album`, `app`, `cover`, `profile`, `mobile`, `normal` or `wall`. 117 | */ 118 | public function getType(): ?string 119 | { 120 | return $this->getField('type'); 121 | } 122 | 123 | public function getEvent(): ?GraphEvent 124 | { 125 | return $this->getField('event'); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphApplicationEventsConfig.php: -------------------------------------------------------------------------------- 1 | getField('default_ate_status'); 11 | } 12 | 13 | public function isAdvertiserIdCollectionEnabled(): bool 14 | { 15 | return (bool)$this->getField('advertiser_id_collection_enabled'); 16 | } 17 | 18 | public function isEventCollectionEnabled(): bool 19 | { 20 | return (bool)$this->getField('event_collection_enabled'); 21 | } 22 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphApplicationObjectStoreURLs.php: -------------------------------------------------------------------------------- 1 | getField('amazon_app_store'); 14 | } 15 | 16 | public function getFBCanvas(): ?string 17 | { 18 | return $this->getField('fb_canvas'); 19 | } 20 | 21 | public function getFBGameRoom(): ?string 22 | { 23 | return $this->getField('fb_gameroom'); 24 | } 25 | 26 | public function getGooglePlay(): ?string 27 | { 28 | return $this->getField('google_play'); 29 | } 30 | 31 | public function getInstantGame(): ?string 32 | { 33 | return $this->getField('instant_game'); 34 | } 35 | 36 | public function getItunes(): ?string 37 | { 38 | return $this->getField('itunes'); 39 | } 40 | 41 | public function getItunesIpad(): ?string 42 | { 43 | return $this->getField('itunes_ipad'); 44 | } 45 | 46 | public function getWindows10Store(): ?string 47 | { 48 | return $this->getField('windows_10_store'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphApplicationRestrictions.php: -------------------------------------------------------------------------------- 1 | getField('age'); 13 | } 14 | 15 | public function getAgeDistribution(): ?string 16 | { 17 | return $this->getField('age_distribution'); 18 | } 19 | 20 | public function getLocation(): ?string 21 | { 22 | return $this->getField('location'); 23 | } 24 | 25 | public function getType(): ?string 26 | { 27 | return $this->getField('type'); 28 | } 29 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphArchivedAd.php: -------------------------------------------------------------------------------- 1 | GraphInsightsRangeValue::class, 16 | 'impressions' => GraphInsightsRangeValue::class, 17 | 'spend' => GraphInsightsRangeValue::class, 18 | ]; 19 | 20 | public function getId(): ?string 21 | { 22 | return $this->getField('id'); 23 | } 24 | 25 | public function getAdCreationTime(): ?DateTime 26 | { 27 | return $this->getField('ad_creation_time'); 28 | } 29 | 30 | /** 31 | * @return ?string[] 32 | */ 33 | public function getAdCreativeBodies(): ?array 34 | { 35 | return $this->getField('ad_creative_bodies'); 36 | } 37 | 38 | /** 39 | * @return ?string[] 40 | */ 41 | public function getAdCreativeLinkCaptions(): ?array 42 | { 43 | return $this->getField('ad_creative_link_captions'); 44 | } 45 | 46 | /** 47 | * @return ?string[] 48 | */ 49 | public function getAdCreativeLinkDescriptions(): ?array 50 | { 51 | return $this->getField('ad_creative_link_descriptions'); 52 | } 53 | 54 | /** 55 | * @return ?string[] 56 | */ 57 | public function getAdCreativeLinkTitles(): ?array 58 | { 59 | return $this->getField('ad_creative_link_titles'); 60 | } 61 | 62 | public function getAdDeliveryStartTime(): ?DateTime 63 | { 64 | return $this->getField('ad_delivery_start_time'); 65 | } 66 | 67 | public function getAdDeliveryStopTime(): ?DateTime 68 | { 69 | return $this->getField('ad_delivery_stop_time'); 70 | } 71 | 72 | public function getAdSnapshotUrl(): ?string 73 | { 74 | return $this->getField('ad_snapshot_url'); 75 | } 76 | 77 | public function getBrTotalReach(): ?int 78 | { 79 | return $this->getField('br_total_reach'); 80 | } 81 | 82 | public function getBylines(): ?string 83 | { 84 | return $this->getField('bylines'); 85 | } 86 | 87 | public function getCurrency(): ?string 88 | { 89 | return $this->getField('currency'); 90 | } 91 | 92 | public function getEstimatedAudienceSize(): ?GraphInsightsRangeValue 93 | { 94 | return $this->getField('estimated_audience_size'); 95 | } 96 | 97 | public function getEUTotalReach(): ?int 98 | { 99 | return $this->getField('eu_total_reach'); 100 | } 101 | 102 | public function getImpressions(): ?GraphInsightsRangeValue 103 | { 104 | return $this->getField('impressions'); 105 | } 106 | 107 | /** 108 | * @return ?string[] 109 | */ 110 | public function getLanguages(): ?array 111 | { 112 | return $this->getField('languages'); 113 | } 114 | 115 | public function getPageId(): ?string 116 | { 117 | return $this->getField('page_id'); 118 | } 119 | 120 | public function getPageName(): ?string 121 | { 122 | return $this->getField('page_name'); 123 | } 124 | 125 | /** 126 | * @return ?string[] 127 | */ 128 | public function getPublisherPlatforms(): ?array 129 | { 130 | return $this->getField('publisher_platforms'); 131 | } 132 | 133 | public function getSpend(): ?GraphInsightsRangeValue 134 | { 135 | return $this->getField('spend'); 136 | } 137 | 138 | /** 139 | * @return ?string[] 140 | */ 141 | public function getTargetAges(): ?array 142 | { 143 | return $this->getField('target_ages'); 144 | } 145 | 146 | /** 147 | * @return ?string[] 148 | */ 149 | public function getTargetGenders(): ?array 150 | { 151 | return $this->getField('target_genders'); 152 | } 153 | 154 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphCoverPhoto.php: -------------------------------------------------------------------------------- 1 | getField('id'); 39 | } 40 | 41 | public function getSource(): ?string 42 | { 43 | return $this->getField('source'); 44 | } 45 | 46 | public function getOffsetX(): ?float 47 | { 48 | return $this->getField('offset_x'); 49 | } 50 | 51 | public function getOffsetY(): ?float 52 | { 53 | return $this->getField('offset_y'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphEdge.php: -------------------------------------------------------------------------------- 1 | request = $request; 70 | $this->metaData = $metaData; 71 | $this->parentEdgeEndpoint = $parentEdgeEndpoint; 72 | $this->subclassName = $subclassName; 73 | 74 | parent::__construct($data); 75 | } 76 | 77 | /** 78 | * Gets the parent Graph edge endpoint that generated the list. 79 | * 80 | * @return string|null 81 | */ 82 | public function getParentGraphEdge(): ?string 83 | { 84 | return $this->parentEdgeEndpoint; 85 | } 86 | 87 | /** 88 | * Gets the subclass name that the child GraphNode's are cast as. 89 | * 90 | * @return string|null 91 | */ 92 | public function getSubClassName(): ?string 93 | { 94 | return $this->subclassName; 95 | } 96 | 97 | /** 98 | * Returns the raw meta data associated with this GraphEdge. 99 | * 100 | * @return array 101 | */ 102 | public function getMetaData(): array 103 | { 104 | return $this->metaData; 105 | } 106 | 107 | /** 108 | * Returns the next cursor if it exists. 109 | * 110 | * @return string|null 111 | */ 112 | public function getNextCursor(): ?string 113 | { 114 | return $this->getCursor('after'); 115 | } 116 | 117 | /** 118 | * Returns the previous cursor if it exists. 119 | * 120 | * @return string|null 121 | */ 122 | public function getPreviousCursor(): ?string 123 | { 124 | return $this->getCursor('before'); 125 | } 126 | 127 | /** 128 | * Returns the cursor for a specific direction if it exists. 129 | * 130 | * @param string $direction The direction of the page: after|before 131 | * 132 | * @return string|null 133 | */ 134 | public function getCursor(string $direction): ?string 135 | { 136 | if (isset($this->metaData['paging']['cursors'][$direction])) { 137 | return $this->metaData['paging']['cursors'][$direction]; 138 | } 139 | 140 | return null; 141 | } 142 | 143 | /** 144 | * Generates a pagination URL based on a cursor. 145 | * 146 | * @param string $direction The direction of the page: next|previous 147 | * 148 | * @return string|null 149 | * 150 | * @throws FacebookSDKException 151 | */ 152 | public function getPaginationUrl(string $direction): ?string 153 | { 154 | $this->validateForPagination(); 155 | 156 | // Do we have a paging URL? 157 | if (!isset($this->metaData['paging'][$direction])) { 158 | return null; 159 | } 160 | 161 | $pageUrl = $this->metaData['paging'][$direction]; 162 | 163 | return FacebookUrlManipulator::baseGraphUrlEndpoint($pageUrl); 164 | } 165 | 166 | /** 167 | * Validates whether or not we can paginate on this request. 168 | * 169 | * @throws FacebookSDKException 170 | */ 171 | public function validateForPagination(): void 172 | { 173 | if ($this->request->getMethod() !== 'GET') { 174 | throw new FacebookSDKException('You can only paginate on a GET request.', 720); 175 | } 176 | } 177 | 178 | /** 179 | * Gets the request object needed to make a next|previous page request. 180 | * 181 | * @param string $direction The direction of the page: next|previous 182 | * 183 | * @throws FacebookSDKException 184 | */ 185 | public function getPaginationRequest(string $direction): FacebookRequest|null 186 | { 187 | $pageUrl = $this->getPaginationUrl($direction); 188 | if (!$pageUrl) { 189 | return null; 190 | } 191 | 192 | $newRequest = clone $this->request; 193 | $newRequest->setEndpoint($pageUrl); 194 | 195 | return $newRequest; 196 | } 197 | 198 | /** 199 | * Gets the request object needed to make a "next" page request. 200 | * 201 | * @throws FacebookSDKException 202 | */ 203 | public function getNextPageRequest(): ?FacebookRequest 204 | { 205 | return $this->getPaginationRequest('next'); 206 | } 207 | 208 | /** 209 | * Gets the request object needed to make a "previous" page request. 210 | * 211 | * @throws FacebookSDKException 212 | */ 213 | public function getPreviousPageRequest(): ?FacebookRequest 214 | { 215 | return $this->getPaginationRequest('previous'); 216 | } 217 | 218 | /** 219 | * The total number of results according to Graph if it exists. 220 | * 221 | * This will be returned if the summary=true modifier is present in the request. 222 | */ 223 | public function getTotalCount(): ?int 224 | { 225 | return $this->metaData['summary']['total_count'] ?? null; 226 | } 227 | 228 | /** 229 | * @inheritDoc 230 | */ 231 | public function map(Closure $callback): static 232 | { 233 | return new static( 234 | $this->request, 235 | array_map($callback, $this->items, array_keys($this->items)), 236 | $this->metaData, 237 | $this->parentEdgeEndpoint, 238 | $this->subclassName 239 | ); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphEngagement.php: -------------------------------------------------------------------------------- 1 | getField('count'); 14 | } 15 | 16 | public function getCountString(): ?string 17 | { 18 | return $this->getField('count_string'); 19 | } 20 | 21 | public function getCountStringWithLike(): ?string 22 | { 23 | return $this->getField('count_string_with_like'); 24 | } 25 | 26 | public function getCountStringWithoutLike(): ?string 27 | { 28 | return $this->getField('count_string_without_like'); 29 | } 30 | 31 | public function getSocialSentence(): ?string 32 | { 33 | return $this->getField('social_sentence'); 34 | } 35 | 36 | public function getSocialSentenceWithLike(): ?string 37 | { 38 | return $this->getField('social_sentence_with_like'); 39 | } 40 | 41 | public function getSocialSentenceWithoutLike(): ?string 42 | { 43 | return $this->getField('social_sentence_without_like'); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphEvent.php: -------------------------------------------------------------------------------- 1 | GraphCoverPhoto::class, 42 | 'place' => GraphPage::class, 43 | 'parent_group' => GraphGroup::class, 44 | ]; 45 | 46 | /** 47 | * Returns the `id` (The event ID) as string if present. 48 | */ 49 | public function getId(): ?string 50 | { 51 | return $this->getField('id'); 52 | } 53 | 54 | /** 55 | * Returns the `cover` (Cover picture) as GraphCoverPhoto if present. 56 | */ 57 | public function getCover(): ?GraphCoverPhoto 58 | { 59 | return $this->getField('cover'); 60 | } 61 | 62 | /** 63 | * Returns the `description` (Long-form description) as string if present. 64 | */ 65 | public function getDescription(): ?string 66 | { 67 | return $this->getField('description'); 68 | } 69 | 70 | /** 71 | * Returns the `end_time` (End time, if one has been set) as DateTime if present. 72 | */ 73 | public function getEndTime(): ?DateTime 74 | { 75 | return $this->getField('end_time'); 76 | } 77 | 78 | /** 79 | * Returns the `is_date_only` (Whether the event only has a date specified, but no time) as bool if present. 80 | */ 81 | public function getIsDateOnly(): bool 82 | { 83 | return $this->getField('is_date_only') ?? false; 84 | } 85 | 86 | /** 87 | * Returns the `name` (Event name) as string if present. 88 | */ 89 | public function getName(): ?string 90 | { 91 | return $this->getField('name'); 92 | } 93 | 94 | /** 95 | * Returns the `owner` (The profile that created the event) as GraphNode if present. 96 | */ 97 | public function getOwner(): ?GraphNode 98 | { 99 | return $this->getField('owner'); 100 | } 101 | 102 | /** 103 | * Returns the `parent_group` (The group the event belongs to) as GraphGroup if present. 104 | */ 105 | public function getParentGroup(): ?GraphGroup 106 | { 107 | return $this->getField('parent_group'); 108 | } 109 | 110 | /** 111 | * Returns the `place` (Event Place information) as GraphPage if present. 112 | */ 113 | public function getPlace(): ?GraphPage 114 | { 115 | return $this->getField('place'); 116 | } 117 | 118 | /** 119 | * Returns the `privacy` (Who can see the event) as string if present. 120 | */ 121 | public function getPrivacy(): ?string 122 | { 123 | return $this->getField('privacy'); 124 | } 125 | 126 | /** 127 | * Returns the `start_time` (Start time) as DateTime if present. 128 | */ 129 | public function getStartTime(): ?DateTime 130 | { 131 | return $this->getField('start_time'); 132 | } 133 | 134 | /** 135 | * Returns the `ticket_uri` (The link users can visit to buy a ticket to this event) as string if present. 136 | */ 137 | public function getTicketUri(): ?string 138 | { 139 | return $this->getField('ticket_uri'); 140 | } 141 | 142 | /** 143 | * Returns the `timezone` (Timezone) as string if present. 144 | */ 145 | public function getTimezone(): ?string 146 | { 147 | return $this->getField('timezone'); 148 | } 149 | 150 | /** 151 | * Returns the `updated_time` (Last update time) as DateTime if present. 152 | */ 153 | public function getUpdatedTime(): ?DateTime 154 | { 155 | return $this->getField('updated_time'); 156 | } 157 | 158 | /** 159 | * Returns the `attending_count` (Number of people attending the event) as int if present. 160 | */ 161 | public function getAttendingCount(): ?int 162 | { 163 | return $this->getField('attending_count'); 164 | } 165 | 166 | /** 167 | * Returns the `declined_count` (Number of people who declined the event) as int if present. 168 | */ 169 | public function getDeclinedCount(): ?int 170 | { 171 | return $this->getField('declined_count'); 172 | } 173 | 174 | /** 175 | * Returns the `maybe_count` (Number of people who maybe going to the event) as int if present. 176 | */ 177 | public function getMaybeCount(): ?int 178 | { 179 | return $this->getField('maybe_count'); 180 | } 181 | 182 | /** 183 | * Returns the `noreply_count` (Number of people who did not reply to the event) as int if present. 184 | */ 185 | public function getNoreplyCount(): ?int 186 | { 187 | return $this->getField('noreply_count'); 188 | } 189 | 190 | /** 191 | * Returns the `invited_count` (Number of people invited to the event) as int if present. 192 | * 193 | * @return int|null 194 | */ 195 | public function getInvitedCount(): ?int 196 | { 197 | return $this->getField('invited_count'); 198 | } 199 | 200 | public function getType(): ?string 201 | { 202 | return $this->getField('type'); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphExperience.php: -------------------------------------------------------------------------------- 1 | GraphUser::class 13 | ]; 14 | 15 | public function getId(): ?string 16 | { 17 | return $this->getField('id'); 18 | } 19 | 20 | public function getDescription(): ?string 21 | { 22 | return $this->getField('description'); 23 | } 24 | 25 | public function getName(): ?string 26 | { 27 | return $this->getField('name'); 28 | } 29 | 30 | public function getWith(): ?GraphUser 31 | { 32 | return $this->getField('with'); 33 | } 34 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphGroup.php: -------------------------------------------------------------------------------- 1 | GraphCoverPhoto::class 42 | ]; 43 | 44 | public function getId(): ?string 45 | { 46 | return $this->getField('id'); 47 | } 48 | 49 | public function getCover(): ?GraphCoverPhoto 50 | { 51 | return $this->getField('cover'); 52 | } 53 | 54 | public function getDescription(): ?string 55 | { 56 | return $this->getField('description'); 57 | } 58 | 59 | public function getEmail(): ?string 60 | { 61 | return $this->getField('email'); 62 | } 63 | 64 | public function getIcon(): ?string 65 | { 66 | return $this->getField('icon'); 67 | } 68 | 69 | public function getMemberCount(): ?int 70 | { 71 | return $this->getField('member_count'); 72 | } 73 | 74 | public function getMemberRequestCount(): ?int 75 | { 76 | return $this->getField('member_request_count'); 77 | } 78 | 79 | public function getName(): ?string 80 | { 81 | return $this->getField('name'); 82 | } 83 | 84 | /** 85 | * @deprecated Deprecated in v9.0. Probably does not work anymore. 86 | */ 87 | public function getOwner(): ?GraphNode 88 | { 89 | return $this->getField('owner'); 90 | } 91 | 92 | public function getParent(): ?GraphNode 93 | { 94 | return $this->getField('parent'); 95 | } 96 | 97 | public function getPermissions(): ?string 98 | { 99 | return $this->getField('permissions'); 100 | } 101 | 102 | public function getPrivacy(): ?string 103 | { 104 | return $this->getField('privacy'); 105 | } 106 | 107 | public function getUpdatedTime(): ?DateTime 108 | { 109 | return $this->getField('updated_time'); 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphInsightsRangeValue.php: -------------------------------------------------------------------------------- 1 | getField('lower_bound'); 14 | } 15 | 16 | public function getUpperBound(): ?string 17 | { 18 | return $this->getField('upper_bound'); 19 | } 20 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphLocation.php: -------------------------------------------------------------------------------- 1 | getField('street'); 38 | } 39 | 40 | public function getCity(): ?string 41 | { 42 | return $this->getField('city'); 43 | } 44 | 45 | public function getState(): ?string 46 | { 47 | return $this->getField('state'); 48 | } 49 | 50 | public function getCountry(): ?string 51 | { 52 | return $this->getField('country'); 53 | } 54 | 55 | public function getZip(): ?string 56 | { 57 | return $this->getField('zip'); 58 | } 59 | 60 | public function getLatitude(): ?float 61 | { 62 | return $this->getField('latitude'); 63 | } 64 | 65 | public function getLongitude(): ?float 66 | { 67 | return $this->getField('longitude'); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphMailingAddress.php: -------------------------------------------------------------------------------- 1 | GraphPage::class 13 | ]; 14 | 15 | public function getId(): ?string 16 | { 17 | return $this->getField('id'); 18 | } 19 | 20 | public function getCity(): ?string 21 | { 22 | return $this->getField('city'); 23 | } 24 | 25 | public function getCountry(): ?string 26 | { 27 | return $this->getField('country'); 28 | } 29 | 30 | public function getPostalCode(): ?string 31 | { 32 | return $this->getField('postal_code'); 33 | } 34 | 35 | public function getRegion(): ?string 36 | { 37 | return $this->getField('region'); 38 | } 39 | 40 | public function getStreet1(): ?string 41 | { 42 | return $this->getField('street1'); 43 | } 44 | 45 | public function getStreet2(): ?string 46 | { 47 | return $this->getField('street2'); 48 | } 49 | 50 | public function getCityPage(): ?GraphPage 51 | { 52 | return $this->getField('city_page'); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphNode.php: -------------------------------------------------------------------------------- 1 | castItems($data)); 49 | } 50 | 51 | /** 52 | * Iterates over an array and detects the types each node 53 | * should be cast to and returns all the items as an array. 54 | * 55 | * @TODO Add auto-casting to AccessToken entities. 56 | * 57 | * @param array $data The array to iterate over. 58 | * 59 | * @return array 60 | */ 61 | public function castItems(array $data): array 62 | { 63 | $items = []; 64 | 65 | foreach ($data as $k => $v) { 66 | if ($this->shouldCastAsDateTime($k) 67 | ) { 68 | try { 69 | $items[$k] = $this->castToDateTime($v); 70 | } catch (Exception) { 71 | // If it cannot be parsed as a date but should be one, we cannot add it because of type checking. 72 | } 73 | } elseif ($k === 'birthday') { 74 | $items[$k] = $this->castToBirthday($v); 75 | } else { 76 | $items[$k] = $v; 77 | } 78 | } 79 | 80 | return $items; 81 | } 82 | 83 | /** 84 | * Uncasts any auto-casted datatypes. 85 | * Basically the reverse of castItems(). 86 | * 87 | * @return array 88 | */ 89 | public function uncastItems(): array 90 | { 91 | $items = $this->asArray(); 92 | 93 | return array_map(function ($v) { 94 | if ($v instanceof DateTime) { 95 | return $v->format(DateTime::ISO8601); 96 | } 97 | 98 | return $v; 99 | }, $items); 100 | } 101 | 102 | /** 103 | * Get the collection of items as JSON. 104 | */ 105 | public function asJson(int $options = 0): string 106 | { 107 | return json_encode($this->uncastItems(), $options); 108 | } 109 | 110 | /** 111 | * Determines if a value from Graph should be cast to DateTime. 112 | * 113 | * @param string $key 114 | * 115 | * @return boolean 116 | */ 117 | public function shouldCastAsDateTime(string $key): bool 118 | { 119 | return in_array($key, [ 120 | 'created_time', 121 | 'updated_time', 122 | 'start_time', 123 | 'end_time', 124 | 'backdated_time', 125 | 'issued_at', 126 | 'expires_at', 127 | 'publish_time', 128 | 'joined' 129 | ], true); 130 | } 131 | 132 | /** 133 | * Casts a date value from Graph to DateTime. 134 | * On PHP 8.3+, this is DateMalformedStringException, so we catch everything. 135 | * @throws Exception 136 | */ 137 | private function castToDateTime(int|string $value): DateTime 138 | { 139 | if (is_int($value)) { 140 | $dt = new DateTime(); 141 | $dt->setTimestamp($value); 142 | } else { 143 | $dt = new DateTime($value); 144 | } 145 | 146 | return $dt; 147 | } 148 | 149 | /** 150 | * Casts a birthday value from Graph to Birthday 151 | */ 152 | private function castToBirthday(string $value): Birthday 153 | { 154 | return new Birthday($value); 155 | } 156 | 157 | /** 158 | * Getter for $graphObjectMap. 159 | * 160 | * @return array 161 | */ 162 | public static function getObjectMap(): array 163 | { 164 | return static::$graphObjectMap; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphPage.php: -------------------------------------------------------------------------------- 1 | GraphVideo::class, 37 | 'engagement' => GraphEngagement::class, 38 | 'contact_address' => GraphMailingAddress::class, 39 | 'best_page' => GraphPage::class, 40 | 'location' => GraphLocation::class, 41 | 'cover' => GraphCoverPhoto::class 42 | ]; 43 | 44 | public function getId(): ?string 45 | { 46 | return $this->getField('id'); 47 | } 48 | 49 | public function getAbout(): ?string 50 | { 51 | return $this->getField('about'); 52 | } 53 | 54 | public function getCategory(): ?string 55 | { 56 | return $this->getField('category'); 57 | } 58 | 59 | public function getDescription(): ?string 60 | { 61 | return $this->getField('description'); 62 | } 63 | 64 | public function getDescriptionHtml(): ?string 65 | { 66 | return $this->getField('description_html'); 67 | } 68 | 69 | /** 70 | * @return ?string[] 71 | */ 72 | public function getEmails(): ?array 73 | { 74 | return $this->getField('emails'); 75 | } 76 | 77 | public function getEngagement(): ?GraphEngagement 78 | { 79 | return $this->getField('engagement'); 80 | } 81 | 82 | public function getName(): ?string 83 | { 84 | return $this->getField('name'); 85 | } 86 | 87 | public function getAppId(): ?string 88 | { 89 | return $this->getField('app_id'); 90 | } 91 | 92 | public function getBestPage(): ?GraphPage 93 | { 94 | return $this->getField('best_page'); 95 | } 96 | 97 | public function getLocation(): ?GraphLocation 98 | { 99 | return $this->getField('location'); 100 | } 101 | 102 | public function getCover(): ?GraphCoverPhoto 103 | { 104 | return $this->getField('cover'); 105 | } 106 | 107 | public function getAccessToken(): ?string 108 | { 109 | return $this->getField('access_token'); 110 | } 111 | 112 | public function getAffiliation(): ?string 113 | { 114 | return $this->getField('affiliation'); 115 | } 116 | 117 | public function getFanCount(): ?int 118 | { 119 | return $this->getField('fan_count'); 120 | } 121 | 122 | public function getFeaturedVideo(): ?GraphVideo 123 | { 124 | return $this->getField('featured_video'); 125 | } 126 | 127 | public function getContactAddress(): ?GraphMailingAddress 128 | { 129 | return $this->getField('contact_address'); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphPaymentPricePoint.php: -------------------------------------------------------------------------------- 1 | getField('credits'); 13 | } 14 | 15 | public function getCurrency(): ?string 16 | { 17 | return $this->getField('currency'); 18 | } 19 | 20 | public function getUserPrice(): ?string 21 | { 22 | return $this->getField('user_price'); 23 | } 24 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphPaymentPricePoints.php: -------------------------------------------------------------------------------- 1 | GraphPaymentPricePoint::class 13 | ]; 14 | 15 | /** 16 | * @return ?GraphPaymentPricePoint[] 17 | */ 18 | public function getMobile(): ?array 19 | { 20 | return $this->getField('mobile'); 21 | } 22 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphPermission.php: -------------------------------------------------------------------------------- 1 | getField('permission'); 14 | } 15 | 16 | public function getStatus(): string 17 | { 18 | return $this->getField('status'); 19 | } 20 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphPhoto.php: -------------------------------------------------------------------------------- 1 | GraphEvent::class, 15 | 'album' => GraphAlbum::class, 16 | 'images' => GraphPlatformImageSource::class, 17 | 'webp_images' => GraphPlatformImageSource::class, 18 | 'place' => GraphPlace::class, 19 | ]; 20 | 21 | public function getId(): ?string 22 | { 23 | return $this->getField('id'); 24 | } 25 | 26 | public function getAlbum(): ?GraphAlbum 27 | { 28 | return $this->getField('album'); 29 | } 30 | 31 | public function getAltText(): ?string 32 | { 33 | return $this->getField('alt_text'); 34 | } 35 | 36 | public function getAltTextCustom(): ?string 37 | { 38 | return $this->getField('alt_text_custom'); 39 | } 40 | 41 | public function getBackDatedTime(): ?DateTime 42 | { 43 | return $this->getField('backdated_time'); 44 | } 45 | 46 | public function canBackDate(): bool 47 | { 48 | return (bool)$this->getField('can_backdate'); 49 | } 50 | 51 | public function canDelete(): bool 52 | { 53 | return (bool)$this->getField('can_delete'); 54 | } 55 | 56 | public function canTag(): bool 57 | { 58 | return (bool)$this->getField('can_tag'); 59 | } 60 | 61 | public function getCreatedTime(): ?DateTime 62 | { 63 | return $this->getField('created_time'); 64 | } 65 | 66 | public function getEvent(): ?GraphEvent 67 | { 68 | return $this->getField('event'); 69 | } 70 | 71 | public function getHeight(): ?int 72 | { 73 | return $this->getField('height'); 74 | } 75 | 76 | public function getWidth(): ?int 77 | { 78 | return $this->getField('width'); 79 | } 80 | 81 | public function getIcon(): ?string 82 | { 83 | return $this->getField('icon'); 84 | } 85 | 86 | public function getLink(): ?string 87 | { 88 | return $this->getField('link'); 89 | } 90 | 91 | public function getName(): ?string 92 | { 93 | return $this->getField('name'); 94 | } 95 | 96 | public function getPageStoryId(): ?string 97 | { 98 | return $this->getField('page_story_id'); 99 | } 100 | 101 | public function getPlace(): ?GraphPlace 102 | { 103 | return $this->getField('place'); 104 | } 105 | 106 | public function getUpdatedTime(): ?DateTime 107 | { 108 | return $this->getField('updated_time'); 109 | } 110 | 111 | /** 112 | * @return ?GraphPlatformImageSource[] 113 | */ 114 | public function getWebPImages(): ?array 115 | { 116 | return $this->getField('webp_images'); 117 | } 118 | 119 | /** 120 | * @return ?GraphPlatformImageSource[] 121 | */ 122 | public function getImages(): ?array 123 | { 124 | return $this->getField('images'); 125 | } 126 | 127 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphPicture.php: -------------------------------------------------------------------------------- 1 | getField('is_silhouette') ?? false; 39 | } 40 | 41 | /** 42 | * Returns the url of user picture if it exists 43 | */ 44 | public function getUrl(): ?string 45 | { 46 | return $this->getField('url'); 47 | } 48 | 49 | /** 50 | * Returns the width of user picture if it exists 51 | */ 52 | public function getWidth(): ?int 53 | { 54 | return $this->getField('width'); 55 | } 56 | 57 | /** 58 | * Returns the height of user picture if it exists 59 | */ 60 | public function getHeight(): ?int 61 | { 62 | return $this->getField('height'); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphPlace.php: -------------------------------------------------------------------------------- 1 | GraphLocation::class 13 | ]; 14 | 15 | public function getId(): ?string 16 | { 17 | return $this->getField('id'); 18 | } 19 | 20 | public function getLocation(): ?GraphLocation 21 | { 22 | return $this->getField('location'); 23 | } 24 | 25 | public function getName(): ?string 26 | { 27 | return $this->getField('name'); 28 | } 29 | 30 | public function getOverallRating(): ?float 31 | { 32 | return $this->getField('overall_rating'); 33 | } 34 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphPlatformImageSource.php: -------------------------------------------------------------------------------- 1 | getField('height'); 14 | } 15 | 16 | public function getWidth(): ?int 17 | { 18 | return $this->getField('width'); 19 | } 20 | 21 | public function getSource(): ?string 22 | { 23 | return $this->getField('source'); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphSessionInfo.php: -------------------------------------------------------------------------------- 1 | getField('app_id'); 41 | } 42 | 43 | /** 44 | * Returns the application name the token was issued for. 45 | */ 46 | public function getApplication(): ?string 47 | { 48 | return $this->getField('application'); 49 | } 50 | 51 | /** 52 | * Returns the date & time that the token expires. 53 | */ 54 | public function getExpiresAt(): ?DateTime 55 | { 56 | return $this->getField('expires_at'); 57 | } 58 | 59 | /** 60 | * Returns whether the token is valid. 61 | */ 62 | public function getIsValid(): bool 63 | { 64 | return $this->getField('is_valid') ?? false; 65 | } 66 | 67 | /** 68 | * Returns the date & time the token was issued at. 69 | */ 70 | public function getIssuedAt(): ?DateTime 71 | { 72 | return $this->getField('issued_at'); 73 | } 74 | 75 | /** 76 | * Returns the scope permissions associated with the token. 77 | */ 78 | public function getScopes(): ?array 79 | { 80 | return $this->getField('scopes'); 81 | } 82 | 83 | /** 84 | * Returns the login id of the user associated with the token. 85 | */ 86 | public function getUserId(): ?string 87 | { 88 | return $this->getField('user_id'); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphUser.php: -------------------------------------------------------------------------------- 1 | GraphAgeRange::class, 38 | 'hometown' => GraphPage::class, 39 | 'location' => GraphPage::class, 40 | 'picture' => GraphPicture::class, 41 | 'significant_other' => GraphUser::class, 42 | 'sports' => GraphExperience::class, 43 | 'favorite_teams' => GraphExperience::class, 44 | 'favorite_athletes' => GraphExperience::class, 45 | 'languages' => GraphExperience::class, 46 | 'inspirational_people' => GraphExperience::class, 47 | 'video_upload_limits' => GraphVideoUploadLimits::class, 48 | 'permissions' => GraphPermission::class, 49 | 'payment_pricepoints' => GraphPaymentPricePoints::class, 50 | 'albums' => GraphAlbum::class 51 | ]; 52 | 53 | public function getId(): ?string 54 | { 55 | return $this->getField('id'); 56 | } 57 | 58 | public function getName(): ?string 59 | { 60 | return $this->getField('name'); 61 | } 62 | 63 | public function getProfilePicture(): ?string 64 | { 65 | return $this->getField('profile_pic'); 66 | } 67 | 68 | public function getClientBusinessId(): ?string 69 | { 70 | return $this->getField('client_business_id'); 71 | } 72 | 73 | public function getTokenForBusiness(): ?string 74 | { 75 | return $this->getField('token_for_business'); 76 | } 77 | 78 | public function getIdForAvatars(): ?string 79 | { 80 | return $this->getField('id_for_avatars'); 81 | } 82 | 83 | /** 84 | * @return ?GraphExperience[] 85 | */ 86 | public function getFavoriteAthletes(): ?array 87 | { 88 | return $this->getField('favorite_athletes'); 89 | } 90 | 91 | /** 92 | * @return ?GraphExperience[] 93 | */ 94 | public function getFavoriteTeams(): ?array 95 | { 96 | return $this->getField('favorite_teams'); 97 | } 98 | 99 | /** 100 | * @return ?GraphExperience[] 101 | */ 102 | public function getLanguages(): ?array 103 | { 104 | return $this->getField('languages'); 105 | } 106 | 107 | /** 108 | * @return ?GraphExperience[] 109 | */ 110 | public function getInspirationalPeople(): ?array 111 | { 112 | return $this->getField('inspirational_people'); 113 | } 114 | 115 | public function getAbout(): ?string 116 | { 117 | return $this->getField('about'); 118 | } 119 | 120 | public function getNameFormat(): ?string 121 | { 122 | return $this->getField('name_format'); 123 | } 124 | 125 | public function getShortName(): ?string 126 | { 127 | return $this->getField('short_name'); 128 | } 129 | 130 | public function getFirstName(): ?string 131 | { 132 | return $this->getField('first_name'); 133 | } 134 | 135 | public function getMiddleName(): ?string 136 | { 137 | return $this->getField('middle_name'); 138 | } 139 | 140 | public function getLastName(): ?string 141 | { 142 | return $this->getField('last_name'); 143 | } 144 | 145 | public function getEmail(): ?string 146 | { 147 | return $this->getField('email'); 148 | } 149 | 150 | public function getGender(): ?string 151 | { 152 | return $this->getField('gender'); 153 | } 154 | 155 | public function getLink(): ?string 156 | { 157 | return $this->getField('link'); 158 | } 159 | 160 | public function getBirthday(): ?Birthday 161 | { 162 | return $this->getField('birthday'); 163 | } 164 | 165 | public function getLocation(): ?GraphPage 166 | { 167 | return $this->getField('location'); 168 | } 169 | 170 | /** 171 | * Returns the current location of the user as a GraphPage. 172 | */ 173 | public function getHometown(): ?GraphPage 174 | { 175 | return $this->getField('hometown'); 176 | } 177 | 178 | /** 179 | * Returns the current location of the user as a GraphUser. 180 | */ 181 | public function getSignificantOther(): ?GraphUser 182 | { 183 | return $this->getField('significant_other'); 184 | } 185 | 186 | /** 187 | * Returns the picture of the user as a GraphPicture 188 | */ 189 | public function getPicture(): ?GraphPicture 190 | { 191 | return $this->getField('picture'); 192 | } 193 | 194 | public function supportsDonateButtonInLiveVideos(): bool 195 | { 196 | return (bool)$this->getField('supports_donate_button_in_live_video'); 197 | } 198 | 199 | public function getAgeRange(): ?GraphAgeRange 200 | { 201 | return $this->getField('age_range'); 202 | } 203 | 204 | public function isInstalled(): bool 205 | { 206 | return (bool)$this->getField('installed'); 207 | } 208 | 209 | /** 210 | * @return ?string[] 211 | */ 212 | public function getMeetingFor(): ?array 213 | { 214 | return $this->getField('meeting_for'); 215 | } 216 | 217 | /** 218 | * @return ?GraphExperience[] 219 | */ 220 | public function getSports(): ?array 221 | { 222 | return $this->getField('sports'); 223 | } 224 | 225 | public function getVideoUploadLimits(): ?GraphVideoUploadLimits 226 | { 227 | return $this->getField('video_upload_limits'); 228 | } 229 | 230 | public function getPermissions(): ?GraphEdge 231 | { 232 | return $this->getField('permissions'); 233 | } 234 | 235 | public function getAlbums(): ?GraphEdge 236 | { 237 | return $this->getField('albums'); 238 | } 239 | 240 | public function getPaymentPricePoints(): ?GraphPaymentPricePoints 241 | { 242 | return $this->getField('payment_pricepoints'); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphVideo.php: -------------------------------------------------------------------------------- 1 | GraphEvent::class, 15 | 'format' => GraphVideoFormat::class, 16 | 'place' => GraphPlace::class, 17 | ]; 18 | 19 | public function getId(): ?string 20 | { 21 | return $this->getField('id'); 22 | } 23 | 24 | /** 25 | * @return ?int[] 26 | */ 27 | public function adBreaks(): ?array 28 | { 29 | return $this->getField('ad_breaks'); 30 | } 31 | 32 | public function getCreatedTime(): ?DateTime 33 | { 34 | return $this->getField('created_time'); 35 | } 36 | 37 | /** 38 | * @return ?string[] 39 | */ 40 | public function getCustomLabels(): ?array 41 | { 42 | return $this->getField('custom_labels'); 43 | } 44 | 45 | public function getDescription(): ?string 46 | { 47 | return $this->getField('description'); 48 | } 49 | 50 | public function getEmbedHtml(): ?string 51 | { 52 | return $this->getField('embed_html'); 53 | } 54 | 55 | public function isEmbeddable(): bool 56 | { 57 | return (bool)$this->getField('embeddable'); 58 | } 59 | 60 | public function getBackdatedTime(): ?DateTime 61 | { 62 | return $this->getField('backdated_time'); 63 | } 64 | 65 | public function getEvent(): ?GraphEvent 66 | { 67 | return $this->getField('event'); 68 | } 69 | 70 | /** 71 | * @return ?GraphVideoFormat[] 72 | */ 73 | public function getFormat(): ?array 74 | { 75 | return $this->getField('format'); 76 | } 77 | 78 | public function getLength(): ?float 79 | { 80 | return $this->getField('length'); 81 | } 82 | 83 | public function getIcon(): ?string 84 | { 85 | return $this->getField('icon'); 86 | } 87 | 88 | public function isCrossPostVideo(): bool 89 | { 90 | return (bool)$this->getField('is_crosspost_video'); 91 | } 92 | 93 | public function isCrossPostEligible(): bool 94 | { 95 | return (bool)$this->getField('is_crossposting_eligible'); 96 | } 97 | 98 | public function isEpisode(): bool 99 | { 100 | return (bool)$this->getField('is_episode'); 101 | } 102 | 103 | public function isInstagramEligible(): bool 104 | { 105 | return (bool)$this->getField('is_instagram_eligible'); 106 | } 107 | 108 | public function isReferenceOnly(): bool 109 | { 110 | return (bool)$this->getField('is_reference_only'); 111 | } 112 | 113 | public function permaLinkUrl(): ?string 114 | { 115 | return $this->getField('permalink_url'); 116 | } 117 | 118 | public function getPlace(): ?GraphPlace 119 | { 120 | return $this->getField('place'); 121 | } 122 | 123 | public function getPostId(): ?string 124 | { 125 | return $this->getField('post_id'); 126 | } 127 | 128 | public function getPostViews(): ?int 129 | { 130 | return $this->getField('post_views'); 131 | } 132 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphVideoFormat.php: -------------------------------------------------------------------------------- 1 | getField('embed_html'); 13 | } 14 | 15 | public function getFilter(): ?string 16 | { 17 | return $this->getField('filter'); 18 | } 19 | 20 | public function getHeight(): ?int 21 | { 22 | return $this->getField('height'); 23 | } 24 | 25 | public function getWidth(): ?int 26 | { 27 | return $this->getField('width'); 28 | } 29 | 30 | public function getPicture(): ?string 31 | { 32 | return $this->getField('picture'); 33 | } 34 | } -------------------------------------------------------------------------------- /src/Facebook/GraphNodes/GraphVideoUploadLimits.php: -------------------------------------------------------------------------------- 1 | getField('length'); 14 | } 15 | 16 | public function getSize(): ?int 17 | { 18 | return $this->getField('size'); 19 | } 20 | } -------------------------------------------------------------------------------- /src/Facebook/Helpers/FacebookCanvasHelper.php: -------------------------------------------------------------------------------- 1 | signedRequest?->get('app_data'); 39 | } 40 | 41 | /** 42 | * Get raw signed request from POST. 43 | */ 44 | public function getRawSignedRequest(): ?string 45 | { 46 | return $this->getRawSignedRequestFromPost() ?: null; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Facebook/Helpers/FacebookJavaScriptHelper.php: -------------------------------------------------------------------------------- 1 | getRawSignedRequestFromCookie(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Facebook/Helpers/FacebookPageTabHelper.php: -------------------------------------------------------------------------------- 1 | signedRequest) { 53 | return; 54 | } 55 | 56 | $this->pageData = $this->signedRequest->get('page'); 57 | } 58 | 59 | /** 60 | * Returns a value from the page data. 61 | */ 62 | public function getPageData(string $key, mixed $default = null): mixed 63 | { 64 | if (isset($this->pageData[$key])) { 65 | return $this->pageData[$key]; 66 | } 67 | 68 | return $default; 69 | } 70 | 71 | public function isAdmin(): bool 72 | { 73 | return $this->getPageData('admin') === true; 74 | } 75 | 76 | public function getPageId(): ?string 77 | { 78 | return $this->getPageData('id'); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Facebook/Helpers/FacebookSignedRequestFromInputHelper.php: -------------------------------------------------------------------------------- 1 | app = $app; 67 | $graphVersion = $graphVersion ?: Facebook::DEFAULT_GRAPH_VERSION; 68 | $this->oAuth2Client = new OAuth2Client($this->app, $client, $graphVersion); 69 | 70 | $this->instantiateSignedRequest(); 71 | } 72 | 73 | /** 74 | * Instantiates a new SignedRequest entity. 75 | * @throws FacebookSDKException 76 | */ 77 | public function instantiateSignedRequest(?string $rawSignedRequest = null): void 78 | { 79 | $rawSignedRequest = $rawSignedRequest ?: $this->getRawSignedRequest(); 80 | 81 | if (!$rawSignedRequest) { 82 | return; 83 | } 84 | 85 | $this->signedRequest = new SignedRequest($this->app, $rawSignedRequest); 86 | } 87 | 88 | /** 89 | * Returns an AccessToken entity from the signed request. 90 | * 91 | * @throws FacebookSDKException 92 | */ 93 | public function getAccessToken(): ?AccessToken 94 | { 95 | if ($this->signedRequest && $this->signedRequest->hasOAuthData()) { 96 | $code = $this->signedRequest->get('code'); 97 | $accessToken = $this->signedRequest->get('oauth_token'); 98 | 99 | if ($code && !$accessToken) { 100 | return $this->oAuth2Client->getAccessTokenFromCode($code); 101 | } 102 | 103 | $expiresAt = $this->signedRequest->get('expires', 0); 104 | 105 | return new AccessToken($accessToken, $expiresAt); 106 | } 107 | 108 | return null; 109 | } 110 | 111 | /** 112 | * Returns the SignedRequest entity. 113 | */ 114 | public function getSignedRequest(): ?SignedRequest 115 | { 116 | return $this->signedRequest; 117 | } 118 | 119 | /** 120 | * Returns the user_id if available. 121 | */ 122 | public function getUserId(): ?string 123 | { 124 | return $this->signedRequest?->getUserId(); 125 | } 126 | 127 | /** 128 | * Get raw signed request from input. 129 | */ 130 | abstract public function getRawSignedRequest(): ?string; 131 | 132 | /** 133 | * Get raw signed request from POST input. 134 | */ 135 | public function getRawSignedRequestFromPost(): ?string 136 | { 137 | if (isset($_POST['signed_request'])) { 138 | return $_POST['signed_request']; 139 | } 140 | 141 | return null; 142 | } 143 | 144 | /** 145 | * Get raw signed request from cookie set from the Javascript SDK. 146 | */ 147 | public function getRawSignedRequestFromCookie(): ?string 148 | { 149 | if (isset($_COOKIE['fbsr_' . $this->app->getId()])) { 150 | return $_COOKIE['fbsr_' . $this->app->getId()]; 151 | } 152 | 153 | return null; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/Facebook/Http/GraphRawResponse.php: -------------------------------------------------------------------------------- 1 | httpResponseCode = $httpStatusCode; 59 | 60 | if (is_array($headers)) { 61 | $this->headers = $headers; 62 | } else { 63 | $this->setHeadersFromString($headers); 64 | } 65 | 66 | $this->body = $body; 67 | } 68 | 69 | /** 70 | * Return the response headers. 71 | * 72 | * @return array 73 | */ 74 | public function getHeaders(): array 75 | { 76 | return $this->headers; 77 | } 78 | 79 | /** 80 | * Return the body of the response. 81 | */ 82 | public function getBody(): ?string 83 | { 84 | return $this->body; 85 | } 86 | 87 | /** 88 | * Return the HTTP response code. 89 | */ 90 | public function getHttpResponseCode(): ?int 91 | { 92 | return $this->httpResponseCode; 93 | } 94 | 95 | /** 96 | * Sets the HTTP response code from a raw header. 97 | */ 98 | public function setHttpResponseCodeFromHeader(string $rawResponseHeader): void 99 | { 100 | // https://tools.ietf.org/html/rfc7230#section-3.1.2 101 | list(,$status) = array_pad(explode(' ', $rawResponseHeader, 3), 3, null); 102 | $this->httpResponseCode = (int)$status; 103 | } 104 | 105 | /** 106 | * Parse the raw headers and set as an array. 107 | * 108 | * @param string $rawHeaders The raw headers from the response. 109 | */ 110 | protected function setHeadersFromString(string $rawHeaders): void 111 | { 112 | // Normalize line breaks 113 | $rawHeaders = str_replace("\r\n", "\n", $rawHeaders); 114 | 115 | // There will be multiple headers if a 301 was followed 116 | // or a proxy was followed, etc 117 | $headerCollection = explode("\n\n", trim($rawHeaders)); 118 | // We just want the last response (at the end) 119 | $rawHeader = array_pop($headerCollection); 120 | 121 | $headerComponents = explode("\n", $rawHeader); 122 | foreach ($headerComponents as $line) { 123 | if (!str_contains($line, ': ')) { 124 | $this->setHttpResponseCodeFromHeader($line); 125 | } else { 126 | list($key, $value) = explode(': ', $line, 2); 127 | $this->headers[$key] = $value; 128 | } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Facebook/Http/RequestBodyInterface.php: -------------------------------------------------------------------------------- 1 | params = $params; 62 | $this->files = $files; 63 | $this->boundary = $boundary ?: uniqid(); 64 | } 65 | 66 | /** 67 | * @inheritdoc 68 | */ 69 | public function getBody(): string 70 | { 71 | $body = ''; 72 | 73 | // Compile normal params 74 | $params = $this->getNestedParams($this->params); 75 | foreach ($params as $k => $v) { 76 | $body .= $this->getParamString($k, $v); 77 | } 78 | 79 | // Compile files 80 | foreach ($this->files as $k => $v) { 81 | $body .= $this->getFileString($k, $v); 82 | } 83 | 84 | // Peace out 85 | $body .= "--$this->boundary--\r\n"; 86 | 87 | return $body; 88 | } 89 | 90 | /** 91 | * Get the boundary 92 | */ 93 | public function getBoundary(): string 94 | { 95 | return $this->boundary; 96 | } 97 | 98 | /** 99 | * Get the string needed to transfer a file. 100 | */ 101 | private function getFileString(string $name, FacebookFile $file): string 102 | { 103 | return sprintf( 104 | "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"%s\r\n\r\n%s\r\n", 105 | $this->boundary, 106 | $name, 107 | $file->getFileName(), 108 | $this->getFileHeaders($file), 109 | $file->getContents() 110 | ); 111 | } 112 | 113 | /** 114 | * Get the string needed to transfer a POST field. 115 | */ 116 | private function getParamString(string $name, string $value): string 117 | { 118 | return sprintf( 119 | "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n", 120 | $this->boundary, 121 | $name, 122 | $value 123 | ); 124 | } 125 | 126 | /** 127 | * Returns the params as an array of nested params. 128 | */ 129 | private function getNestedParams(array $params): array 130 | { 131 | $query = http_build_query($params); 132 | $params = explode('&', $query); 133 | $result = []; 134 | 135 | foreach ($params as $param) { 136 | list($key, $value) = explode('=', $param, 2); 137 | $result[urldecode($key)] = urldecode($value); 138 | } 139 | 140 | return $result; 141 | } 142 | 143 | /** 144 | * Get the headers needed before transferring the content of a POST file. 145 | */ 146 | protected function getFileHeaders(FacebookFile $file): string 147 | { 148 | return "\r\nContent-Type: {$file->getMimetype()}"; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/Facebook/Http/RequestBodyUrlEncoded.php: -------------------------------------------------------------------------------- 1 | params = $params; 46 | } 47 | 48 | /** 49 | * @inheritdoc 50 | */ 51 | public function getBody(): string 52 | { 53 | return http_build_query($this->params); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Facebook/HttpClients/FacebookCurl.php: -------------------------------------------------------------------------------- 1 | curl = curl_init(); 47 | } 48 | 49 | /** 50 | * Set a curl option 51 | */ 52 | public function setopt(int $key, mixed $value): void 53 | { 54 | curl_setopt($this->curl, $key, $value); 55 | } 56 | 57 | /** 58 | * Set an array of options to a curl resource 59 | */ 60 | public function setoptArray(array $options): void 61 | { 62 | curl_setopt_array($this->curl, $options); 63 | } 64 | 65 | /** 66 | * Send a curl request 67 | */ 68 | public function exec(): bool|string 69 | { 70 | return curl_exec($this->curl); 71 | } 72 | 73 | /** 74 | * Return the curl error number 75 | */ 76 | public function errno(): ?int 77 | { 78 | // For some reason, adding int type to return here causes the mock testing framework to not work and return null. 79 | return curl_errno($this->curl); 80 | } 81 | 82 | /** 83 | * Return the curl error message 84 | */ 85 | public function error(): string 86 | { 87 | return curl_error($this->curl); 88 | } 89 | 90 | /** 91 | * Get info from a curl reference 92 | */ 93 | public function getinfo(?int $type): mixed 94 | { 95 | return curl_getinfo($this->curl, $type); 96 | } 97 | 98 | /** 99 | * Get the currently installed curl version 100 | * 101 | * @return array|false 102 | */ 103 | public function version(): array|false 104 | { 105 | return curl_version(); 106 | } 107 | 108 | /** 109 | * Close the resource connection to curl 110 | */ 111 | public function close(): void 112 | { 113 | curl_close($this->curl); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Facebook/HttpClients/FacebookCurlHttpClient.php: -------------------------------------------------------------------------------- 1 | facebookCurl = $facebookCurl ?: new FacebookCurl(); 53 | } 54 | 55 | /** 56 | * @inheritdoc 57 | */ 58 | public function send(string $url, string $method, ?string $body, array $headers, int $timeOut): GraphRawResponse 59 | { 60 | $this->openConnection($url, $method, $body, $headers, $timeOut); 61 | $this->sendRequest(); 62 | 63 | if ($curlErrorCode = $this->facebookCurl->errno()) { 64 | throw new FacebookSDKException($this->facebookCurl->error(), $curlErrorCode); 65 | } 66 | 67 | // Separate the raw headers from the raw body 68 | list($rawHeaders, $rawBody) = $this->extractResponseHeadersAndBody(); 69 | 70 | $this->closeConnection(); 71 | 72 | return new GraphRawResponse($rawHeaders, $rawBody); 73 | } 74 | 75 | /** 76 | * Opens a new curl connection. 77 | * 78 | * @param string $url The endpoint to send the request to. 79 | * @param string $method The request method. 80 | * @param string|null $body The body of the request. 81 | * @param array $headers The request headers. 82 | * @param int $timeOut The timeout in seconds for the request. 83 | */ 84 | public function openConnection(string $url, string $method, ?string $body, array $headers, int $timeOut) 85 | { 86 | $options = [ 87 | CURLOPT_CUSTOMREQUEST => $method, 88 | CURLOPT_HTTPHEADER => $this->compileRequestHeaders($headers), 89 | CURLOPT_URL => $url, 90 | CURLOPT_CONNECTTIMEOUT => 10, 91 | CURLOPT_TIMEOUT => $timeOut, 92 | CURLOPT_RETURNTRANSFER => true, // Return response as string 93 | CURLOPT_HEADER => true, // Enable header processing 94 | CURLOPT_SSL_VERIFYHOST => 2, 95 | CURLOPT_SSL_VERIFYPEER => true, 96 | CURLOPT_CAINFO => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem', 97 | ]; 98 | 99 | if ($method !== "GET" && $body) { 100 | $options[CURLOPT_POSTFIELDS] = $body; 101 | } 102 | 103 | $this->facebookCurl->init(); 104 | $this->facebookCurl->setoptArray($options); 105 | } 106 | 107 | /** 108 | * Closes an existing curl connection 109 | */ 110 | public function closeConnection(): void 111 | { 112 | $this->facebookCurl->close(); 113 | } 114 | 115 | /** 116 | * Send the request and get the raw response from curl 117 | */ 118 | public function sendRequest(): void 119 | { 120 | $this->rawResponse = $this->facebookCurl->exec(); 121 | } 122 | 123 | /** 124 | * Compiles the request headers into a curl-friendly format. 125 | * 126 | * @param array $headers The request headers. 127 | * 128 | * @return array 129 | */ 130 | public function compileRequestHeaders(array $headers): array 131 | { 132 | $return = []; 133 | 134 | foreach ($headers as $key => $value) { 135 | $return[] = $key . ': ' . $value; 136 | } 137 | 138 | return $return; 139 | } 140 | 141 | /** 142 | * Extracts the headers and the body into a two-part array 143 | * 144 | * @return array 145 | */ 146 | public function extractResponseHeadersAndBody(): array 147 | { 148 | $parts = explode("\r\n\r\n", $this->rawResponse); 149 | $rawBody = array_pop($parts); 150 | $rawHeaders = implode("\r\n\r\n", $parts); 151 | 152 | return [trim($rawHeaders), trim($rawBody)]; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/Facebook/HttpClients/FacebookGuzzleHttpClient.php: -------------------------------------------------------------------------------- 1 | guzzleClient = $guzzleClient ?: new Client(); 47 | } 48 | 49 | /** 50 | * @inheritdoc 51 | */ 52 | public function send(string $url, string $method, ?string $body, array $headers, int $timeOut): GraphRawResponse 53 | { 54 | $options = [ 55 | 'headers' => $headers, 56 | 'timeout' => $timeOut, 57 | 'http_errors' => false, 58 | 'connect_timeout' => 10, 59 | 'verify' => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem', 60 | ]; 61 | 62 | if ($body) { 63 | $options['body'] = $body; 64 | } 65 | 66 | try { 67 | 68 | $rawResponse = $this->guzzleClient->request($method, $url, $options); 69 | 70 | return new GraphRawResponse( 71 | $this->getHeadersAsString($rawResponse), 72 | $rawResponse->getBody(), 73 | $rawResponse->getStatusCode() 74 | ); 75 | 76 | } catch (GuzzleException $e) { 77 | throw new FacebookSDKException($e->getMessage(), $e->getCode()); 78 | } 79 | 80 | } 81 | 82 | /** 83 | * Returns the Guzzle array of headers as a string. 84 | * 85 | * @param ResponseInterface $response The Guzzle response. 86 | */ 87 | public function getHeadersAsString(ResponseInterface $response): string 88 | { 89 | $headers = $response->getHeaders(); 90 | $rawHeaders = []; 91 | foreach ($headers as $name => $values) { 92 | $rawHeaders[] = $name . ": " . implode(", ", $values); 93 | } 94 | 95 | return implode("\r\n", $rawHeaders); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Facebook/HttpClients/FacebookHttpClientInterface.php: -------------------------------------------------------------------------------- 1 | stream = stream_context_create($options); 52 | } 53 | 54 | /** 55 | * The response headers from the stream wrapper 56 | * For some reason, this must be nullable or it fails when mocked in test. 57 | * This array is never null. 58 | */ 59 | public function getResponseHeaders(): ?array 60 | { 61 | return $this->responseHeaders; 62 | } 63 | 64 | /** 65 | * Send a stream wrapped request 66 | * 67 | * @param string $url 68 | * 69 | * @return string|false 70 | */ 71 | public function fileGetContents(string $url): string|false 72 | { 73 | $rawResponse = file_get_contents($url, false, $this->stream); 74 | $this->responseHeaders = $http_response_header ?: []; 75 | 76 | return $rawResponse; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Facebook/HttpClients/FacebookStreamHttpClient.php: -------------------------------------------------------------------------------- 1 | facebookStream = $facebookStream ?: new FacebookStream(); 42 | } 43 | 44 | /** 45 | * @inheritdoc 46 | */ 47 | public function send(string $url, string $method, ?string $body, array $headers, int $timeOut): GraphRawResponse 48 | { 49 | $options = [ 50 | 'http' => [ 51 | 'method' => $method, 52 | 'header' => $this->compileHeader($headers), 53 | 'content' => $body, 54 | 'timeout' => $timeOut, 55 | 'ignore_errors' => true 56 | ], 57 | 'ssl' => [ 58 | 'verify_peer' => true, 59 | 'verify_peer_name' => true, 60 | 'allow_self_signed' => true, // All root certificates are self-signed 61 | 'cafile' => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem', 62 | ], 63 | ]; 64 | 65 | $this->facebookStream->streamContextCreate($options); 66 | $rawBody = $this->facebookStream->fileGetContents($url); 67 | $rawHeaders = $this->facebookStream->getResponseHeaders(); 68 | 69 | if ($rawBody === false || empty($rawHeaders)) { 70 | throw new FacebookSDKException('Stream returned an empty response', 660); 71 | } 72 | 73 | $rawHeaders = implode("\r\n", $rawHeaders); 74 | 75 | return new GraphRawResponse($rawHeaders, $rawBody); 76 | } 77 | 78 | /** 79 | * Formats the headers for use in the stream wrapper. 80 | */ 81 | public function compileHeader(array $headers): string 82 | { 83 | $header = []; 84 | foreach ($headers as $k => $v) { 85 | $header[] = $k . ': ' . $v; 86 | } 87 | 88 | return implode("\r\n", $header); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Facebook/HttpClients/HttpClientsFactory.php: -------------------------------------------------------------------------------- 1 | sessionData[$key] ?? null; 44 | } 45 | 46 | /** 47 | * @inheritdoc 48 | */ 49 | public function set(mixed $key, mixed $value): void 50 | { 51 | $this->sessionData[$key] = $value; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Facebook/PersistentData/FacebookSessionPersistentDataHandler.php: -------------------------------------------------------------------------------- 1 | sessionPrefix . $key])) { 62 | return $_SESSION[$this->sessionPrefix . $key]; 63 | } 64 | 65 | return null; 66 | } 67 | 68 | /** 69 | * @inheritdoc 70 | */ 71 | public function set(mixed $key, mixed $value): void 72 | { 73 | $_SESSION[$this->sessionPrefix . $key] = $value; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Facebook/PersistentData/PersistentDataFactory.php: -------------------------------------------------------------------------------- 1 | binToHex($binaryString, $length); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Facebook/PseudoRandomString/PseudoRandomStringGeneratorFactory.php: -------------------------------------------------------------------------------- 1 | binToHex(random_bytes($length), $length); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Facebook/PseudoRandomString/UrandomPseudoRandomStringGenerator.php: -------------------------------------------------------------------------------- 1 | binToHex($binaryString, $length); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Facebook/Url/FacebookUrlDetectionHandler.php: -------------------------------------------------------------------------------- 1 | getHttpScheme() . '://' . $this->getHostName() . $this->getServerVar('REQUEST_URI'); 39 | } 40 | 41 | /** 42 | * Get the currently active URL scheme. 43 | */ 44 | protected function getHttpScheme(): string 45 | { 46 | return $this->isBehindSsl() ? 'https' : 'http'; 47 | } 48 | 49 | /** 50 | * Tries to detect if the server is running behind an SSL. 51 | */ 52 | protected function isBehindSsl(): bool 53 | { 54 | // Check for proxy first 55 | $protocol = $this->getHeader('X_FORWARDED_PROTO'); 56 | if ($protocol) { 57 | return $this->protocolWithActiveSsl($protocol); 58 | } 59 | 60 | $protocol = $this->getServerVar('HTTPS'); 61 | if ($protocol) { 62 | return $this->protocolWithActiveSsl($protocol); 63 | } 64 | 65 | return $this->getCurrentPort() === '443'; 66 | } 67 | 68 | /** 69 | * Detects an active SSL protocol value. 70 | * 71 | * @param string $protocol 72 | * 73 | * @return boolean 74 | */ 75 | protected function protocolWithActiveSsl(string $protocol): bool 76 | { 77 | 78 | return in_array(strtolower($protocol), ['on', '1', 'https', 'ssl'], true); 79 | } 80 | 81 | /** 82 | * Tries to detect the host name of the server. 83 | * 84 | * Some elements adapted from 85 | * 86 | * @see https://github.com/symfony/HttpFoundation/blob/master/Request.php 87 | */ 88 | protected function getHostName(): string 89 | { 90 | // Check for proxy first 91 | $header = $this->getHeader('X_FORWARDED_HOST'); 92 | if ($header && $this->isValidForwardedHost($header)) { 93 | $elements = explode(',', $header); 94 | $host = $elements[count($elements) - 1]; 95 | } elseif (!$host = $this->getHeader('HOST')) { 96 | if (!$host = $this->getServerVar('SERVER_NAME')) { 97 | $host = $this->getServerVar('SERVER_ADDR'); 98 | } 99 | } 100 | 101 | // trim and remove port number from host 102 | // host is lowercase as per RFC 952/2181 103 | $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); 104 | 105 | // Port number 106 | $scheme = $this->getHttpScheme(); 107 | $port = $this->getCurrentPort(); 108 | $appendPort = ':' . $port; 109 | 110 | // Don't append port number if a normal port. 111 | if (($scheme == 'http' && $port == '80') || ($scheme == 'https' && $port == '443')) { 112 | $appendPort = ''; 113 | } 114 | 115 | return $host . $appendPort; 116 | } 117 | 118 | protected function getCurrentPort(): string 119 | { 120 | // Check for proxy first 121 | if ($port = $this->getHeader('X_FORWARDED_PORT')) { 122 | return $port; 123 | } 124 | 125 | if ($this->getHeader('X_FORWARDED_PROTO') === 'https') { 126 | return '443'; 127 | } 128 | 129 | return (string)$this->getServerVar('SERVER_PORT'); 130 | } 131 | 132 | /** 133 | * Returns the a value from the $_SERVER super global. 134 | */ 135 | protected function getServerVar(string $key): mixed 136 | { 137 | return $_SERVER[$key] ?? ''; 138 | } 139 | 140 | /** 141 | * Gets a value from the HTTP request headers. 142 | */ 143 | protected function getHeader(string $key): string 144 | { 145 | return $this->getServerVar('HTTP_' . $key); 146 | } 147 | 148 | /** 149 | * Checks if the value in X_FORWARDED_HOST is a valid hostname 150 | * Could prevent unintended redirections 151 | */ 152 | protected function isValidForwardedHost(string $header): bool 153 | { 154 | $elements = explode(',', $header); 155 | $host = $elements[count($elements) - 1]; 156 | 157 | return preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $host) //valid chars check 158 | && 0 < strlen($host) && strlen($host) < 254 //overall length check 159 | && preg_match("/^[^.]{1,63}(\.[^.]{1,63})*$/", $host); //length of each label 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/Facebook/Url/FacebookUrlManipulator.php: -------------------------------------------------------------------------------- 1 | 0) { 60 | $query = '?' . http_build_query($params); 61 | } 62 | } 63 | 64 | $scheme = isset($parts['scheme']) ? $parts['scheme'] . '://' : ''; 65 | $host = $parts['host'] ?? ''; 66 | $port = isset($parts['port']) ? ':' . $parts['port'] : ''; 67 | $path = $parts['path'] ?? ''; 68 | $fragment = isset($parts['fragment']) ? '#' . $parts['fragment'] : ''; 69 | 70 | return $scheme . $host . $port . $path . $query . $fragment; 71 | } 72 | 73 | /** 74 | * Gracefully appends params to the URL. 75 | * 76 | * @param string $url The URL that will receive the params. 77 | * @param array $newParams The params to append to the URL. 78 | */ 79 | public static function appendParamsToUrl(string $url, array $newParams = []): string 80 | { 81 | if (empty($newParams)) { 82 | return $url; 83 | } 84 | 85 | if (!str_contains($url, '?')) { 86 | return $url . '?' . http_build_query($newParams); 87 | } 88 | 89 | list($path, $query) = explode('?', $url, 2); 90 | $existingParams = []; 91 | parse_str($query, $existingParams); 92 | 93 | // Favor params from the original URL over $newParams 94 | $newParams = array_merge($newParams, $existingParams); 95 | 96 | // Sort for a predicable order 97 | ksort($newParams); 98 | 99 | return $path . '?' . http_build_query($newParams); 100 | } 101 | 102 | /** 103 | * Returns the params from a URL in the form of an array. 104 | * 105 | * @param string|null $url The URL to parse the params from. 106 | * 107 | * @return array 108 | */ 109 | public static function getParamsAsArray(?string $url): array 110 | { 111 | if (!is_string($url)) { 112 | return []; 113 | } 114 | $query = parse_url($url, PHP_URL_QUERY); 115 | if (!$query) { 116 | return []; 117 | } 118 | $params = []; 119 | parse_str($query, $params); 120 | 121 | return $params; 122 | } 123 | 124 | /** 125 | * Adds the params of the first URL to the second URL. 126 | * 127 | * Any params that already exist in the second URL will go untouched. 128 | * 129 | * @param string $urlToStealFrom The URL harvest the params from. 130 | * @param string $urlToAddTo The URL that will receive the new params. 131 | * 132 | * @return string The $urlToAddTo with any new params from $urlToStealFrom. 133 | */ 134 | public static function mergeUrlParams(string $urlToStealFrom, string $urlToAddTo): string 135 | { 136 | $newParams = static::getParamsAsArray($urlToStealFrom); 137 | // Nothing new to add, return as-is 138 | if (!$newParams) { 139 | return $urlToAddTo; 140 | } 141 | 142 | return static::appendParamsToUrl($urlToAddTo, $newParams); 143 | } 144 | 145 | /** 146 | * Check for a "/" prefix and prepend it if not exists. 147 | */ 148 | public static function forceSlashPrefix(?string $string): ?string 149 | { 150 | if (!$string) { 151 | return $string; 152 | } 153 | 154 | return str_starts_with($string, '/') ? $string : '/' . $string; 155 | } 156 | 157 | /** 158 | * Trims off the hostname and Graph version from a URL. 159 | * 160 | * @param string $urlToTrim The URL the needs the surgery. 161 | * 162 | * @return string The $urlToTrim with the hostname and Graph version removed. 163 | */ 164 | public static function baseGraphUrlEndpoint(string $urlToTrim): string 165 | { 166 | return '/' . preg_replace('/^https:\/\/.+\.facebook\.com(\/v.+?)?\//', '', $urlToTrim); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/Facebook/Url/UrlDetectionInterface.php: -------------------------------------------------------------------------------- 1 |