From 677953067f2bb5502a70f0d004f1ac844b18a128 Mon Sep 17 00:00:00 2001 From: emkael Date: Mon, 16 Jan 2017 22:04:43 +0100 Subject: * Facebook support --- lib/facebook-graph-sdk/.gitignore | 4 + lib/facebook-graph-sdk/.scrutinizer.yml | 8 + lib/facebook-graph-sdk/.travis.yml | 24 + lib/facebook-graph-sdk/CHANGELOG.md | 153 ++++ lib/facebook-graph-sdk/CONTRIBUTING.md | 54 ++ lib/facebook-graph-sdk/LICENSE | 19 + lib/facebook-graph-sdk/README.md | 89 ++ lib/facebook-graph-sdk/composer.json | 41 + lib/facebook-graph-sdk/phpunit.xml.dist | 22 + .../src/Facebook/Authentication/AccessToken.php | 160 ++++ .../Authentication/AccessTokenMetadata.php | 390 ++++++++ .../src/Facebook/Authentication/OAuth2Client.php | 292 ++++++ .../Exceptions/FacebookAuthenticationException.php | 33 + .../Exceptions/FacebookAuthorizationException.php | 33 + .../Exceptions/FacebookClientException.php | 33 + .../Facebook/Exceptions/FacebookOtherException.php | 33 + .../Exceptions/FacebookResponseException.php | 208 +++++ .../Facebook/Exceptions/FacebookSDKException.php | 33 + .../Exceptions/FacebookServerException.php | 33 + .../Exceptions/FacebookThrottleException.php | 33 + lib/facebook-graph-sdk/src/Facebook/Facebook.php | 589 ++++++++++++ .../src/Facebook/FacebookApp.php | 101 +++ .../src/Facebook/FacebookBatchRequest.php | 303 +++++++ .../src/Facebook/FacebookBatchResponse.php | 154 ++++ .../src/Facebook/FacebookClient.php | 250 ++++++ .../src/Facebook/FacebookRequest.php | 536 +++++++++++ .../src/Facebook/FacebookResponse.php | 410 +++++++++ .../src/Facebook/FileUpload/FacebookFile.php | 135 +++ .../src/Facebook/FileUpload/FacebookVideo.php | 33 + .../src/Facebook/FileUpload/Mimetypes.php | 987 +++++++++++++++++++++ .../src/Facebook/GraphNodes/Collection.php | 242 +++++ .../src/Facebook/GraphNodes/GraphAchievement.php | 113 +++ .../src/Facebook/GraphNodes/GraphAlbum.php | 183 ++++ .../src/Facebook/GraphNodes/GraphApplication.php | 43 + .../src/Facebook/GraphNodes/GraphCoverPhoto.php | 72 ++ .../src/Facebook/GraphNodes/GraphEdge.php | 260 ++++++ .../src/Facebook/GraphNodes/GraphEvent.php | 242 +++++ .../src/Facebook/GraphNodes/GraphGroup.php | 171 ++++ .../src/Facebook/GraphNodes/GraphList.php | 36 + .../src/Facebook/GraphNodes/GraphLocation.php | 102 +++ .../src/Facebook/GraphNodes/GraphNode.php | 185 ++++ .../src/Facebook/GraphNodes/GraphNodeFactory.php | 392 ++++++++ .../src/Facebook/GraphNodes/GraphObject.php | 36 + .../src/Facebook/GraphNodes/GraphObjectFactory.php | 86 ++ .../src/Facebook/GraphNodes/GraphPage.php | 125 +++ .../src/Facebook/GraphNodes/GraphPicture.php | 72 ++ .../src/Facebook/GraphNodes/GraphSessionInfo.php | 102 +++ .../src/Facebook/GraphNodes/GraphUser.php | 162 ++++ .../src/Facebook/Helpers/FacebookCanvasHelper.php | 52 ++ .../Facebook/Helpers/FacebookJavaScriptHelper.php | 42 + .../src/Facebook/Helpers/FacebookPageTabHelper.php | 95 ++ .../Helpers/FacebookRedirectLoginHelper.php | 360 ++++++++ .../FacebookSignedRequestFromInputHelper.php | 166 ++++ .../src/Facebook/Http/GraphRawResponse.php | 137 +++ .../src/Facebook/Http/RequestBodyInterface.php | 39 + .../src/Facebook/Http/RequestBodyMultipart.php | 170 ++++ .../src/Facebook/Http/RequestBodyUrlEncoded.php | 55 ++ .../src/Facebook/HttpClients/FacebookCurl.php | 129 +++ .../HttpClients/FacebookCurlHttpClient.php | 210 +++++ .../HttpClients/FacebookGuzzleHttpClient.php | 97 ++ .../HttpClients/FacebookHttpClientInterface.php | 47 + .../src/Facebook/HttpClients/FacebookStream.php | 80 ++ .../HttpClients/FacebookStreamHttpClient.php | 94 ++ .../certs/DigiCertHighAssuranceEVRootCA.pem | 23 + .../FacebookMemoryPersistentDataHandler.php | 53 ++ .../FacebookSessionPersistentDataHandler.php | 76 ++ .../PersistentData/PersistentDataInterface.php | 49 + .../McryptPseudoRandomStringGenerator.php | 68 ++ .../OpenSslPseudoRandomStringGenerator.php | 67 ++ .../PseudoRandomStringGeneratorInterface.php | 45 + .../PseudoRandomStringGeneratorTrait.php | 58 ++ .../UrandomPseudoRandomStringGenerator.php | 89 ++ .../src/Facebook/SignedRequest.php | 332 +++++++ .../Facebook/Url/FacebookUrlDetectionHandler.php | 163 ++++ .../src/Facebook/Url/FacebookUrlManipulator.php | 167 ++++ .../src/Facebook/Url/UrlDetectionInterface.php | 39 + lib/facebook-graph-sdk/src/Facebook/autoload.php | 79 ++ .../tests/Authentication/AccessTokenMetadata.php | 138 +++ .../tests/Authentication/AccessTokenTest.php | 111 +++ .../FooFacebookClientForOAuth2Test.php | 58 ++ .../tests/Authentication/OAuth2ClientTest.php | 167 ++++ .../Exceptions/FacebookResponseExceptionTest.php | 278 ++++++ lib/facebook-graph-sdk/tests/FacebookAppTest.php | 66 ++ .../tests/FacebookBatchRequestTest.php | 381 ++++++++ .../tests/FacebookBatchResponseTest.php | 138 +++ .../tests/FacebookClientTest.php | 308 +++++++ .../tests/FacebookRequestTest.php | 207 +++++ .../tests/FacebookResponseTest.php | 121 +++ lib/facebook-graph-sdk/tests/FacebookTest.php | 381 ++++++++ .../tests/FacebookTestCredentials.php.dist | 36 + .../tests/FileUpload/FacebookFileTest.php | 53 ++ .../tests/FileUpload/MimetypesTest.php | 55 ++ .../tests/GraphNodes/AbstractGraphNode.php | 51 ++ .../tests/GraphNodes/CollectionTest.php | 125 +++ .../tests/GraphNodes/GraphAchievementTest.php | 117 +++ .../tests/GraphNodes/GraphAlbumTest.php | 109 +++ .../tests/GraphNodes/GraphEdgeTest.php | 120 +++ .../tests/GraphNodes/GraphEventTest.php | 109 +++ .../tests/GraphNodes/GraphGroupTest.php | 75 ++ .../tests/GraphNodes/GraphNodeFactoryTest.php | 437 +++++++++ .../tests/GraphNodes/GraphNodeTest.php | 138 +++ .../tests/GraphNodes/GraphObjectFactoryTest.php | 114 +++ .../tests/GraphNodes/GraphPageTest.php | 95 ++ .../tests/GraphNodes/GraphSessionInfoTest.php | 62 ++ .../tests/GraphNodes/GraphUserTest.php | 140 +++ .../tests/Helpers/FacebookCanvasHelperTest.php | 53 ++ .../tests/Helpers/FacebookJavaScriptHelperTest.php | 45 + .../tests/Helpers/FacebookPageTabHelperTest.php | 46 + .../Helpers/FacebookRedirectLoginHelperTest.php | 140 +++ .../FacebookSignedRequestFromInputHelperTest.php | 113 +++ .../tests/Http/GraphRawResponseTest.php | 82 ++ .../tests/Http/RequestBodyMultipartTest.php | 111 +++ .../tests/Http/RequestUrlEncodedTest.php | 64 ++ .../tests/HttpClients/AbstractTestHttpClient.php | 60 ++ .../HttpClients/FacebookCurlHttpClientTest.php | 334 +++++++ .../HttpClients/FacebookGuzzleHttpClientTest.php | 143 +++ .../HttpClients/FacebookStreamHttpClientTest.php | 134 +++ .../FacebookMemoryPersistentDataHandlerTest.php | 46 + .../FacebookSessionPersistentDataHandlerTest.php | 62 ++ .../McryptPseudoRandomStringGeneratorTest.php | 44 + .../OpenSslPseudoRandomStringGeneratorTest.php | 44 + .../PseudoRandomStringGeneratorTraitTest.php | 52 ++ .../UrandomPseudoRandomStringGeneratorTest.php | 50 ++ lib/facebook-graph-sdk/tests/SignedRequestTest.php | 139 +++ .../tests/Url/FacebookUrlDetectionHandlerTest.php | 134 +++ .../tests/Url/FacebookUrlManipulatorTest.php | 217 +++++ lib/facebook-graph-sdk/tests/bootstrap.php | 33 + lib/facebook-graph-sdk/tests/foo.txt | 1 + 128 files changed, 17235 insertions(+) create mode 100644 lib/facebook-graph-sdk/.gitignore create mode 100644 lib/facebook-graph-sdk/.scrutinizer.yml create mode 100644 lib/facebook-graph-sdk/.travis.yml create mode 100644 lib/facebook-graph-sdk/CHANGELOG.md create mode 100644 lib/facebook-graph-sdk/CONTRIBUTING.md create mode 100644 lib/facebook-graph-sdk/LICENSE create mode 100644 lib/facebook-graph-sdk/README.md create mode 100644 lib/facebook-graph-sdk/composer.json create mode 100644 lib/facebook-graph-sdk/phpunit.xml.dist create mode 100644 lib/facebook-graph-sdk/src/Facebook/Authentication/AccessToken.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Authentication/AccessTokenMetadata.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Authentication/OAuth2Client.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Exceptions/FacebookAuthenticationException.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Exceptions/FacebookAuthorizationException.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Exceptions/FacebookClientException.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Exceptions/FacebookOtherException.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Exceptions/FacebookResponseException.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Exceptions/FacebookSDKException.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Exceptions/FacebookServerException.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Exceptions/FacebookThrottleException.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Facebook.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/FacebookApp.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/FacebookBatchRequest.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/FacebookBatchResponse.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/FacebookClient.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/FacebookRequest.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/FacebookResponse.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/FileUpload/FacebookFile.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/FileUpload/FacebookVideo.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/FileUpload/Mimetypes.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/Collection.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphAchievement.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphAlbum.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphApplication.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphCoverPhoto.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphEdge.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphEvent.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphGroup.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphList.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphLocation.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphNode.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphNodeFactory.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphObject.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphObjectFactory.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphPage.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphPicture.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphSessionInfo.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphUser.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookCanvasHelper.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookJavaScriptHelper.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookPageTabHelper.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookRedirectLoginHelper.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookSignedRequestFromInputHelper.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Http/GraphRawResponse.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Http/RequestBodyInterface.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Http/RequestBodyMultipart.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Http/RequestBodyUrlEncoded.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookCurl.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookCurlHttpClient.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookGuzzleHttpClient.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookHttpClientInterface.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookStream.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookStreamHttpClient.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/HttpClients/certs/DigiCertHighAssuranceEVRootCA.pem create mode 100644 lib/facebook-graph-sdk/src/Facebook/PersistentData/FacebookMemoryPersistentDataHandler.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/PersistentData/FacebookSessionPersistentDataHandler.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/PersistentData/PersistentDataInterface.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/PseudoRandomString/McryptPseudoRandomStringGenerator.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/PseudoRandomString/OpenSslPseudoRandomStringGenerator.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/PseudoRandomString/PseudoRandomStringGeneratorInterface.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/PseudoRandomString/PseudoRandomStringGeneratorTrait.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/PseudoRandomString/UrandomPseudoRandomStringGenerator.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/SignedRequest.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Url/FacebookUrlDetectionHandler.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Url/FacebookUrlManipulator.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/Url/UrlDetectionInterface.php create mode 100644 lib/facebook-graph-sdk/src/Facebook/autoload.php create mode 100644 lib/facebook-graph-sdk/tests/Authentication/AccessTokenMetadata.php create mode 100644 lib/facebook-graph-sdk/tests/Authentication/AccessTokenTest.php create mode 100644 lib/facebook-graph-sdk/tests/Authentication/FooFacebookClientForOAuth2Test.php create mode 100644 lib/facebook-graph-sdk/tests/Authentication/OAuth2ClientTest.php create mode 100644 lib/facebook-graph-sdk/tests/Exceptions/FacebookResponseExceptionTest.php create mode 100644 lib/facebook-graph-sdk/tests/FacebookAppTest.php create mode 100755 lib/facebook-graph-sdk/tests/FacebookBatchRequestTest.php create mode 100755 lib/facebook-graph-sdk/tests/FacebookBatchResponseTest.php create mode 100644 lib/facebook-graph-sdk/tests/FacebookClientTest.php create mode 100755 lib/facebook-graph-sdk/tests/FacebookRequestTest.php create mode 100755 lib/facebook-graph-sdk/tests/FacebookResponseTest.php create mode 100644 lib/facebook-graph-sdk/tests/FacebookTest.php create mode 100644 lib/facebook-graph-sdk/tests/FacebookTestCredentials.php.dist create mode 100644 lib/facebook-graph-sdk/tests/FileUpload/FacebookFileTest.php create mode 100644 lib/facebook-graph-sdk/tests/FileUpload/MimetypesTest.php create mode 100644 lib/facebook-graph-sdk/tests/GraphNodes/AbstractGraphNode.php create mode 100755 lib/facebook-graph-sdk/tests/GraphNodes/CollectionTest.php create mode 100644 lib/facebook-graph-sdk/tests/GraphNodes/GraphAchievementTest.php create mode 100644 lib/facebook-graph-sdk/tests/GraphNodes/GraphAlbumTest.php create mode 100644 lib/facebook-graph-sdk/tests/GraphNodes/GraphEdgeTest.php create mode 100644 lib/facebook-graph-sdk/tests/GraphNodes/GraphEventTest.php create mode 100644 lib/facebook-graph-sdk/tests/GraphNodes/GraphGroupTest.php create mode 100644 lib/facebook-graph-sdk/tests/GraphNodes/GraphNodeFactoryTest.php create mode 100644 lib/facebook-graph-sdk/tests/GraphNodes/GraphNodeTest.php create mode 100644 lib/facebook-graph-sdk/tests/GraphNodes/GraphObjectFactoryTest.php create mode 100644 lib/facebook-graph-sdk/tests/GraphNodes/GraphPageTest.php create mode 100644 lib/facebook-graph-sdk/tests/GraphNodes/GraphSessionInfoTest.php create mode 100644 lib/facebook-graph-sdk/tests/GraphNodes/GraphUserTest.php create mode 100644 lib/facebook-graph-sdk/tests/Helpers/FacebookCanvasHelperTest.php create mode 100644 lib/facebook-graph-sdk/tests/Helpers/FacebookJavaScriptHelperTest.php create mode 100644 lib/facebook-graph-sdk/tests/Helpers/FacebookPageTabHelperTest.php create mode 100644 lib/facebook-graph-sdk/tests/Helpers/FacebookRedirectLoginHelperTest.php create mode 100644 lib/facebook-graph-sdk/tests/Helpers/FacebookSignedRequestFromInputHelperTest.php create mode 100644 lib/facebook-graph-sdk/tests/Http/GraphRawResponseTest.php create mode 100644 lib/facebook-graph-sdk/tests/Http/RequestBodyMultipartTest.php create mode 100644 lib/facebook-graph-sdk/tests/Http/RequestUrlEncodedTest.php create mode 100644 lib/facebook-graph-sdk/tests/HttpClients/AbstractTestHttpClient.php create mode 100644 lib/facebook-graph-sdk/tests/HttpClients/FacebookCurlHttpClientTest.php create mode 100644 lib/facebook-graph-sdk/tests/HttpClients/FacebookGuzzleHttpClientTest.php create mode 100644 lib/facebook-graph-sdk/tests/HttpClients/FacebookStreamHttpClientTest.php create mode 100644 lib/facebook-graph-sdk/tests/PersistentData/FacebookMemoryPersistentDataHandlerTest.php create mode 100644 lib/facebook-graph-sdk/tests/PersistentData/FacebookSessionPersistentDataHandlerTest.php create mode 100644 lib/facebook-graph-sdk/tests/PseudoRandomString/McryptPseudoRandomStringGeneratorTest.php create mode 100644 lib/facebook-graph-sdk/tests/PseudoRandomString/OpenSslPseudoRandomStringGeneratorTest.php create mode 100644 lib/facebook-graph-sdk/tests/PseudoRandomString/PseudoRandomStringGeneratorTraitTest.php create mode 100644 lib/facebook-graph-sdk/tests/PseudoRandomString/UrandomPseudoRandomStringGeneratorTest.php create mode 100644 lib/facebook-graph-sdk/tests/SignedRequestTest.php create mode 100644 lib/facebook-graph-sdk/tests/Url/FacebookUrlDetectionHandlerTest.php create mode 100644 lib/facebook-graph-sdk/tests/Url/FacebookUrlManipulatorTest.php create mode 100644 lib/facebook-graph-sdk/tests/bootstrap.php create mode 100644 lib/facebook-graph-sdk/tests/foo.txt (limited to 'lib/facebook-graph-sdk') diff --git a/lib/facebook-graph-sdk/.gitignore b/lib/facebook-graph-sdk/.gitignore new file mode 100644 index 0000000..0c2e885 --- /dev/null +++ b/lib/facebook-graph-sdk/.gitignore @@ -0,0 +1,4 @@ +vendor/ +composer.lock +tests/FacebookTestCredentials.php + diff --git a/lib/facebook-graph-sdk/.scrutinizer.yml b/lib/facebook-graph-sdk/.scrutinizer.yml new file mode 100644 index 0000000..6632013 --- /dev/null +++ b/lib/facebook-graph-sdk/.scrutinizer.yml @@ -0,0 +1,8 @@ +filter: + paths: + - 'src/*' + +tools: + php_code_sniffer: + config: + standard: PSR2 diff --git a/lib/facebook-graph-sdk/.travis.yml b/lib/facebook-graph-sdk/.travis.yml new file mode 100644 index 0000000..c3082df --- /dev/null +++ b/lib/facebook-graph-sdk/.travis.yml @@ -0,0 +1,24 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - hhvm + +sudo: false + +before_install: + - travis_retry composer self-update + +install: + - travis_retry composer install --prefer-source --no-interaction + +script: + - vendor/bin/phpunit --coverage-text --exclude-group integration + +matrix: + allow_failures: + - php: 7.0 + fast_finish: true diff --git a/lib/facebook-graph-sdk/CHANGELOG.md b/lib/facebook-graph-sdk/CHANGELOG.md new file mode 100644 index 0000000..aeb629e --- /dev/null +++ b/lib/facebook-graph-sdk/CHANGELOG.md @@ -0,0 +1,153 @@ +# CHANGELOG + +Starting with version 5, the Facebook PHP SDK follows [SemVer](http://semver.org/). + + +## 5.0.x + +Version 5 of the Facebook PHP SDK is a complete refactor of version 4. It comes loaded with lots of new features and a friendlier API. + +- 5.0 (2015-??-??) + - New features + - Added the `Facebook\Facebook` super service for an easier API + - Improved "reauthentication" and "rerequest" support + - Requests/Responses + - Added full batch support + - Added full file upload support for videos & photos + - Added methods to make pagination easier + - Added "deep" pagination support so that Graph edges embedded in a Graph node can be paginated over easily + - Beta support at `graph.beta.facebook.com` + - Added `getMetaData()` to `GraphEdge` to obtain all the metadata associated with a list of Graph nodes + - Full nested param support + - Many improvements to the Graph node subtypes + - New injectable interfaces + - Added a `PersistentDataInterface` for custom persistent data handling + - Added a `PseudoRandomStringGeneratorInterface` for customizable CSPRNG's + - Added a `UrlDetectionInterface` for custom URL-detection logic + - Codebase changes + - Moved exception classes to `Exception\*` directory + - Moved response collection objects to `GraphNodes\*` directory + - Moved helpers to `Helpers\*` directory + - Killed `FacebookSession` in favor of the `AccessToken` entity + - Added `FacebookClient` service + - Renamed `FacebookRequestException` to `FacebookResponseException` + - Renamed `FacebookHttpable` to `FacebookHttpClientInterface` + - Added `FacebookApp` entity that contains info about the Facebook app + - Updated the API for the helpers + - Tests + - Added namespaces to the tests + - Grouped functional tests under `functional` group + - Other changes + - Made PSR-2 compliant + - Adopted SemVer + - Completely refactored request/response handling + - Refactored the OAuth 2.0 logic + - Added `ext-mbstring` to composer require + - Added this CHANGELOG. Hi! :) + + +## 4.1-dev + +Since the Facebook PHP SDK didn't follow SemVer in version 4.x, the master branch was going to be released as 4.1. However, the SDK switched to SemVer in v5.0. So any references on the internet to version 4.1 can be assumed to be an alias to version `5.0.0` + + +## 4.0.x + +Version 4.0 of the Facebook PHP SDK did not follow [SemVer](http://semver.org/). The versioning format used was as follows: `4.MAJOR.(MINOR|PATCH)`. The `MINOR` and `PATCH` versions were squashed together. + +- 4.0.23 (2015-04-03) + - Added support for new JSON response types in Graph v2.3 when requesting access tokens +- 4.0.22 (2015-04-02) + - Fixed issues related to multidimensional params + - **Bumped default fallback Graph version to `v2.3`** +- 4.0.21 (2015-03-31) + - Added a `FacebookPermissions` class to reference all the Facebook permissions +- 4.0.20 (2015-03-02) + - Fixed a bug introduced in `4.0.19` related to CSRF comparisons +- 4.0.19 (2015-03-02) + - Added stricter CSRF comparison checks to `SignedRequest` and `FacebookRedirectLoginHelper` +- 4.0.18 (2015-02-24) + - [`FacebookHttpable`] Reverted a breaking change from `4.0.17` that changed the method signatures +- 4.0.17 (2015-02-19) + - [`FacebookRedirectLoginHelper`] Added multiple auth types to `getLoginUrl()` + - [`GraphUser`] Added `getTimezone()` + - [`FacebookCurl`] Additional fix for `curl_init()` handling + - Added support for https://graph-video.facebook.com when path ends with `/videos` +- 4.0.16 (2015-02-03) + - [`FacebookRedirectLoginHelper`] Added "reauthenticate" functionality to `getLoginUrl()` + - [`FacebookCurl`] Fixed `curl_init()` issue +- 4.0.15 (2015-01-06) + - [`FacebookRedirectLoginHelper`] Added guard against accidental exposure of app secret via the logout link +- 4.0.14 (2014-12-29) + - [`GraphUser`] Added `getGender()` + - [`FacebookRedirectLoginHelper`] Added CSRF protection for rerequest links + - [`GraphAlbum`] Fixed bugs in getter methods +- 4.0.13 (2014-12-12) + - [`FacebookRedirectLoginHelper`] Added `$displayAsPopup` param to `getLoginUrl()` + - [`FacebookResponse`] Fixed minor pagination bug + - Removed massive cert bundle and replaced with `DigiCertHighAssuranceEVRootCA` for peer verification +- 4.0.12 (2014-10-30) + - **Updated default fallback Graph version to `v2.2`** + - Fixed potential duplicate `type` param in URL's + - [`FacebookRedirectLoginHelper`] Added `getReRequestUrl()` + - [`GraphUser`] Added `getEmail()` +- 4.0.11 (2014-08-25) + - [`FacebookCurlHttpClient`] Added a method to disable IPv6 resolution +- 4.0.10 (2014-08-12) + - [`GraphObject`] Fixed improper usage of `stdClass` + - Fixed warnings when `open_basedir` directive set + - Fixed long lived sessions forgetting the signed request + - [`CanvasLoginHelper`] Removed GET processing + - Updated visibility on `FacebookSession::useAppSecretProof` +- 4.0.9 (2014-06-27) + - [`FacebookPageTabHelper`] Added ability to fetch `app_data` + - Added `GraphUserPage` Graph node collection + - Cleaned up test files + - Decoupled signed request handling + - Added some stronger type hinting + - Explicitly added separator in `http_build_query()` + - [`FacebookCurlHttpClient`] Updated the calculation of the request body size + - Decoupled access token handling + - [`FacebookRedirectLoginHelper`] Implemented better CSPRNG + - Added autoloader for those poor non-composer peeps +- 4.0.8 (2014-06-10) + - Enabled `appsecret_proof` by default + - Added stream wrapper and Guzzle HTTP client implementations +- 4.0.7 (2014-05-31) + - Improved testing environment + - Added `FacebookPageTabHelper` + - [`FacebookSession`] Fixed issue where `validateSessionInfo()` would return incorrect results +- 4.0.6 (2014-05-24) + - Added feature to inject custom HTTP clients + - [`FacebookCanvasLoginHelper`] Fixed bug that would throw when logging out + - Removed appToken from test credentials file + - [`FacebookRequest`] Added `appsecret_proof` handling +- 4.0.5 (2014-05-19) + - Fixed bug in cURL where proxy headers are not included in header_size + - Added internal SDK error codes for thrown exceptions + - Added stream wrapper fallback for hosting environments without cURL + - Added getter methods for signed requests + - Fixed warning that showed up in tests + - Changed SDK error code for stream failure + - Added `GraphAlbum` Graph node collection +- 4.0.4 (2014-05-15) + - Added more error codes to accommodate more Graph error responses + - [`JavaScriptLoginHelper`] Fixed bug that would try to get a new access token when one already existed +- 4.0.3 (2014-05-14) + - Fixed bug for "Missing client_id parameter" error + - Fixed bug for eTag support when "Network is unreachable" error occurs + - Fixed pagination issue related to `sdtClass` +- 4.0.2 (2014-05-07) + - [`composer.json`] Upgraded to use PSR-4 autoloading instead of Composer's `classmap` + - [`FacebookCanvasLoginHelper`] Abstracted access to super globals + - [`FacebookRequest`] Fixed bug that blindly appended params to a url + - [`FacebookRequest`] Added support for `DELETE` and `PUT` methods + - Added eTag support to Graph requests +- 4.0.1 (2014-05-05) + - All exceptions are now extend from `FacebookSDKException` + - [`FacebookSession`] Signed request parsing will throw on malformed signed request input + - Excluded test credentials from tests + - [`FacebookRedirectLoginHelper`] Changed scope on `$state` property + - [`phpunit.xml`] Normalized +- 4.0.0 (2014-04-30) + - Initial release. Yay! diff --git a/lib/facebook-graph-sdk/CONTRIBUTING.md b/lib/facebook-graph-sdk/CONTRIBUTING.md new file mode 100644 index 0000000..e3c16c4 --- /dev/null +++ b/lib/facebook-graph-sdk/CONTRIBUTING.md @@ -0,0 +1,54 @@ +Contributing +------------ + +Contributions are **welcome** and will be fully **credited**. + +We accept contributions via Pull Requests on [Github](https://github.com/facebook/facebook-php-sdk-v4). + + +## Pull Requests + +- **Sign the CLA** - For us to accept contributions you will have to first have signed the + [Contributor License Agreement](https://developers.facebook.com/opensource/cla). + +- **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to run [PHP Code Sniffer](#running-php-code-sniffer) as you code. + +- **Add tests!** - Your patch won't be accepted if it doesn't have tests. + +- **Document any change in behaviour** - Make sure the README and the [documentation](https://github.com/facebook/facebook-php-sdk-v4/tree/master/docs) are kept up-to-date. + +- **Consider our release cycle** - As of version 5.0.0, we try to follow [SemVer](http://semver.org/). Randomly breaking public APIs is not an option. + +- **Create topic branches** - Don't ask us to pull from your master branch. + +- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. + +- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting. + +- **Ensure tests pass!** - Please [run the tests](#running-tests) before submitting your pull request, and make sure they pass. We won't accept a patch until all tests pass. + +- **Ensure no coding standards violations** - Please [run PHP Code Sniffer](#running-php-code-sniffer) using the PSR-2 standard before submitting your pull request. A violation will cause the build to fail, so please make sure there are no violations. We can't accept a patch if the build fails. + + +## Running Tests + +``` bash +$ ./vendor/bin/phpunit +``` + + +## Running PHP Code Sniffer + +You can install [PHP Code Sniffer](https://github.com/squizlabs/PHP_CodeSniffer) globally with composer. + +``` bash +$ composer global require squizlabs/php_codesniffer +``` + +Then you can `cd` into the Facebook PHP SDK folder and run Code Sniffer against the `src/` directory. + +``` bash +$ ~/.composer/vendor/bin/phpcs src --standard=psr2 -sp +``` + +**Happy coding**! diff --git a/lib/facebook-graph-sdk/LICENSE b/lib/facebook-graph-sdk/LICENSE new file mode 100644 index 0000000..be3927b --- /dev/null +++ b/lib/facebook-graph-sdk/LICENSE @@ -0,0 +1,19 @@ +Copyright 2014 Facebook, Inc. + +You are hereby granted a non-exclusive, worldwide, royalty-free license to +use, copy, modify, and distribute this software in source code or binary +form for use in connection with the web services and APIs provided by +Facebook. + +As with any software that integrates with the Facebook platform, your use +of this software is subject to the Facebook Developer Principles and +Policies [http://developers.facebook.com/policy/]. This copyright notice +shall be included in all copies or substantial portions of the software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/lib/facebook-graph-sdk/README.md b/lib/facebook-graph-sdk/README.md new file mode 100644 index 0000000..7d60e6e --- /dev/null +++ b/lib/facebook-graph-sdk/README.md @@ -0,0 +1,89 @@ +# Facebook SDK for PHP + +[![Build Status](https://img.shields.io/travis/facebook/facebook-php-sdk-v4/master.svg)](https://travis-ci.org/facebook/facebook-php-sdk-v4) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/facebook/facebook-php-sdk-v4/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/facebook/facebook-php-sdk-v4/?branch=master) +[![Latest Stable Version](http://img.shields.io/badge/Latest%20Stable-5.0.0-blue.svg)](https://packagist.org/packages/facebook/php-sdk-v4) + + +This repository contains the open source PHP SDK that allows you to access the Facebook Platform from your PHP app. + + +## Installation + +The Facebook PHP SDK can be installed with [Composer](https://getcomposer.org/). Add the Facebook PHP SDK package to your `composer.json` file. + +```json +{ + "require": { + "facebook/php-sdk-v4": "~5.0" + } +} +``` + + +## Usage + +> **Note:** This version of the Facebook SDK for PHP requires PHP 5.4 or greater. + +Simple GET example of a user's profile. + +```php +$fb = new Facebook\Facebook([ + 'app_id' => '{app-id}', + 'app_secret' => '{app-secret}', + 'default_graph_version' => 'v2.4', + //'default_access_token' => '{access-token}', // optional +]); + +// Use one of the helper classes to get a Facebook\Authentication\AccessToken entity. +// $helper = $fb->getRedirectLoginHelper(); +// $helper = $fb->getJavaScriptHelper(); +// $helper = $fb->getCanvasHelper(); +// $helper = $fb->getPageTabHelper(); + +try { + // Get the Facebook\GraphNodes\GraphUser object for the current user. + // If you provided a 'default_access_token', the '{access-token}' is optional. + $response = $fb->get('/me', '{access-token}'); +} catch(Facebook\Exceptions\FacebookResponseException $e) { + // When Graph returns an error + echo 'Graph returned an error: ' . $e->getMessage(); + exit; +} catch(Facebook\Exceptions\FacebookSDKException $e) { + // When validation fails or other local issues + echo 'Facebook SDK returned an error: ' . $e->getMessage(); + exit; +} + +$me = $response->getGraphUser(); +echo 'Logged in as ' . $me->getName(); +``` + +Complete documentation, installation instructions, and examples are available at: [https://developers.facebook.com/docs/php](https://developers.facebook.com/docs/php) + + +## Tests + +1. [Composer](https://getcomposer.org/) is a prerequisite for running the tests. Install composer globally, then run `composer install` to install required files. +2. Create a test app on [Facebook Developers](https://developers.facebook.com), then create `tests/FacebookTestCredentials.php` from `tests/FacebookTestCredentials.php.dist` and edit it to add your credentials. +3. The tests can be executed by running this command from the root directory: + +```bash +$ ./vendor/bin/phpunit +``` + +By default the tests will send live HTTP requests to the Graph API. If you are without an internet connection you can skip these tests by excluding the `integration` group. + +```bash +$ ./vendor/bin/phpunit --exclude-group integration +``` + + +## Contributing + +For us to accept contributions you will have to first have signed the [Contributor License Agreement](https://developers.facebook.com/opensource/cla). Please see [CONTRIBUTING](https://github.com/facebook/facebook-php-sdk-v4/blob/master/CONTRIBUTING.md) for details. + + +## License + +Please see the [license file](https://github.com/facebook/facebook-php-sdk-v4/blob/master/LICENSE) for more information. diff --git a/lib/facebook-graph-sdk/composer.json b/lib/facebook-graph-sdk/composer.json new file mode 100644 index 0000000..31e70cd --- /dev/null +++ b/lib/facebook-graph-sdk/composer.json @@ -0,0 +1,41 @@ +{ + "name": "facebook/php-sdk-v4", + "description": "Facebook SDK for PHP", + "keywords": ["facebook", "sdk"], + "type": "library", + "homepage": "https://github.com/facebook/facebook-php-sdk-v4", + "license": "Facebook Platform", + "authors": [ + { + "name": "Facebook", + "homepage": "https://github.com/facebook/facebook-php-sdk-v4/contributors" + } + ], + "require": { + "php": ">=5.4.0", + "ext-mbstring": "*" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "mockery/mockery": "~0.8", + "guzzlehttp/guzzle": "~5.0" + }, + "suggest": { + "guzzlehttp/guzzle": "Allows for implementation of the Guzzle HTTP client" + }, + "autoload": { + "psr-4": { + "Facebook\\": "src/Facebook/" + } + }, + "autoload-dev": { + "psr-4": { + "Facebook\\Tests\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + } +} diff --git a/lib/facebook-graph-sdk/phpunit.xml.dist b/lib/facebook-graph-sdk/phpunit.xml.dist new file mode 100644 index 0000000..f6707bd --- /dev/null +++ b/lib/facebook-graph-sdk/phpunit.xml.dist @@ -0,0 +1,22 @@ + + + + ./tests + + + + + ./src/Facebook + + + + + + diff --git a/lib/facebook-graph-sdk/src/Facebook/Authentication/AccessToken.php b/lib/facebook-graph-sdk/src/Facebook/Authentication/AccessToken.php new file mode 100644 index 0000000..582ea61 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Authentication/AccessToken.php @@ -0,0 +1,160 @@ +value = $accessToken; + if ($expiresAt) { + $this->setExpiresAtFromTimeStamp($expiresAt); + } + } + + /** + * Generate an app secret proof to sign a request to Graph. + * + * @param string $appSecret The app secret. + * + * @return string + */ + public function getAppSecretProof($appSecret) + { + return hash_hmac('sha256', $this->value, $appSecret); + } + + /** + * Getter for expiresAt. + * + * @return \DateTime|null + */ + public function getExpiresAt() + { + return $this->expiresAt; + } + + /** + * Determines whether or not this is an app access token. + * + * @return bool + */ + public function isAppAccessToken() + { + return strpos($this->value, '|') !== false; + } + + /** + * Determines whether or not this is a long-lived token. + * + * @return bool + */ + public function isLongLived() + { + if ($this->expiresAt) { + return $this->expiresAt->getTimestamp() > time() + (60 * 60 * 2); + } + + if ($this->isAppAccessToken()) { + return true; + } + + return false; + } + + /** + * Checks the expiration of the access token. + * + * @return boolean|null + */ + public function isExpired() + { + if ($this->getExpiresAt() instanceof \DateTime) { + return $this->getExpiresAt()->getTimestamp() < time(); + } + + if ($this->isAppAccessToken()) { + return false; + } + + return null; + } + + /** + * Returns the access token as a string. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the access token as a string. + * + * @return string + */ + public function __toString() + { + return $this->getValue(); + } + + /** + * Setter for expires_at. + * + * @param int $timeStamp + */ + protected function setExpiresAtFromTimeStamp($timeStamp) + { + $dt = new \DateTime(); + $dt->setTimestamp($timeStamp); + $this->expiresAt = $dt; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Authentication/AccessTokenMetadata.php b/lib/facebook-graph-sdk/src/Facebook/Authentication/AccessTokenMetadata.php new file mode 100644 index 0000000..f302a6d --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Authentication/AccessTokenMetadata.php @@ -0,0 +1,390 @@ +metadata = $metadata['data']; + + $this->castTimestampsToDateTime(); + } + + /** + * Returns a value from the metadata. + * + * @param string $field The property to retrieve. + * @param mixed $default The default to return if the property doesn't exist. + * + * @return mixed + */ + public function getField($field, $default = null) + { + if (isset($this->metadata[$field])) { + return $this->metadata[$field]; + } + + return $default; + } + + /** + * Returns a value from the metadata. + * + * @param string $field The property to retrieve. + * @param mixed $default The default to return if the property doesn't exist. + * + * @return mixed + * + * @deprecated 5.0.0 getProperty() has been renamed to getField() + * @todo v6: Remove this method + */ + public function getProperty($field, $default = null) + { + return $this->getField($field, $default); + } + + /** + * Returns a value from a child property in the metadata. + * + * @param string $parentField The parent property. + * @param string $field The property to retrieve. + * @param mixed $default The default to return if the property doesn't exist. + * + * @return mixed + */ + public function getChildProperty($parentField, $field, $default = null) + { + if (!isset($this->metadata[$parentField])) { + return $default; + } + + if (!isset($this->metadata[$parentField][$field])) { + return $default; + } + + return $this->metadata[$parentField][$field]; + } + + /** + * Returns a value from the error metadata. + * + * @param string $field The property to retrieve. + * @param mixed $default The default to return if the property doesn't exist. + * + * @return mixed + */ + public function getErrorProperty($field, $default = null) + { + return $this->getChildProperty('error', $field, $default); + } + + /** + * Returns a value from the "metadata" metadata. *Brain explodes* + * + * @param string $field The property to retrieve. + * @param mixed $default The default to return if the property doesn't exist. + * + * @return mixed + */ + public function getMetadataProperty($field, $default = null) + { + return $this->getChildProperty('metadata', $field, $default); + } + + /** + * The ID of the application this access token is for. + * + * @return string|null + */ + public function getAppId() + { + return $this->getField('app_id'); + } + + /** + * Name of the application this access token is for. + * + * @return string|null + */ + public function getApplication() + { + return $this->getField('application'); + } + + /** + * Any error that a request to the graph api + * would return due to the access token. + * + * @return bool|null + */ + public function isError() + { + return $this->getField('error') !== null; + } + + /** + * The error code for the error. + * + * @return int|null + */ + public function getErrorCode() + { + return $this->getErrorProperty('code'); + } + + /** + * The error message for the error. + * + * @return string|null + */ + public function getErrorMessage() + { + return $this->getErrorProperty('message'); + } + + /** + * The error subcode for the error. + * + * @return int|null + */ + public function getErrorSubcode() + { + return $this->getErrorProperty('subcode'); + } + + /** + * DateTime when this access token expires. + * + * @return \DateTime|null + */ + public function getExpiresAt() + { + return $this->getField('expires_at'); + } + + /** + * Whether the access token is still valid or not. + * + * @return boolean|null + */ + public function getIsValid() + { + return $this->getField('is_valid'); + } + + /** + * DateTime when this access token was issued. + * + * Note that the issued_at field is not returned + * for short-lived access tokens. + * + * @see https://developers.facebook.com/docs/facebook-login/access-tokens#debug + * + * @return \DateTime|null + */ + public function getIssuedAt() + { + return $this->getField('issued_at'); + } + + /** + * General metadata associated with the access token. + * Can contain data like 'sso', 'auth_type', 'auth_nonce'. + * + * @return array|null + */ + public function getMetadata() + { + return $this->getField('metadata'); + } + + /** + * The 'sso' child property from the 'metadata' parent property. + * + * @return string|null + */ + public function getSso() + { + return $this->getMetadataProperty('sso'); + } + + /** + * The 'auth_type' child property from the 'metadata' parent property. + * + * @return string|null + */ + public function getAuthType() + { + return $this->getMetadataProperty('auth_type'); + } + + /** + * The 'auth_nonce' child property from the 'metadata' parent property. + * + * @return string|null + */ + public function getAuthNonce() + { + return $this->getMetadataProperty('auth_nonce'); + } + + /** + * For impersonated access tokens, the ID of + * the page this token contains. + * + * @return string|null + */ + public function getProfileId() + { + return $this->getField('profile_id'); + } + + /** + * List of permissions that the user has granted for + * the app in this access token. + * + * @return array + */ + public function getScopes() + { + return $this->getField('scopes'); + } + + /** + * The ID of the user this access token is for. + * + * @return string|null + */ + public function getUserId() + { + return $this->getField('user_id'); + } + + /** + * Ensures the app ID from the access token + * metadata is what we expect. + * + * @param string $appId + * + * @throws FacebookSDKException + */ + public function validateAppId($appId) + { + if ($this->getAppId() !== $appId) { + throw new FacebookSDKException('Access token metadata contains unexpected app ID.', 401); + } + } + + /** + * Ensures the user ID from the access token + * metadata is what we expect. + * + * @param string $userId + * + * @throws FacebookSDKException + */ + public function validateUserId($userId) + { + if ($this->getUserId() !== $userId) { + throw new FacebookSDKException('Access token metadata contains unexpected user ID.', 401); + } + } + + /** + * Ensures the access token has not expired yet. + * + * @throws FacebookSDKException + */ + public function validateExpiration() + { + if (!$this->getExpiresAt() instanceof \DateTime) { + return; + } + + if ($this->getExpiresAt()->getTimestamp() < time()) { + throw new FacebookSDKException('Inspection of access token metadata shows that the access token has expired.', 401); + } + } + + /** + * Converts a unix timestamp into a DateTime entity. + * + * @param int $timestamp + * + * @return \DateTime + */ + private function convertTimestampToDateTime($timestamp) + { + $dt = new \DateTime(); + $dt->setTimestamp($timestamp); + + return $dt; + } + + /** + * Casts the unix timestamps as DateTime entities. + */ + private function castTimestampsToDateTime() + { + foreach (static::$dateProperties as $key) { + if (isset($this->metadata[$key])) { + $this->metadata[$key] = $this->convertTimestampToDateTime($this->metadata[$key]); + } + } + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Authentication/OAuth2Client.php b/lib/facebook-graph-sdk/src/Facebook/Authentication/OAuth2Client.php new file mode 100644 index 0000000..8e364ec --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Authentication/OAuth2Client.php @@ -0,0 +1,292 @@ +app = $app; + $this->client = $client; + $this->graphVersion = $graphVersion ?: Facebook::DEFAULT_GRAPH_VERSION; + } + + /** + * Returns the last FacebookRequest that was sent. + * Useful for debugging and testing. + * + * @return FacebookRequest|null + */ + public function getLastRequest() + { + return $this->lastRequest; + } + + /** + * Get the metadata associated with the access token. + * + * @param AccessToken|string $accessToken The access token to debug. + * + * @return AccessTokenMetadata + */ + public function debugToken($accessToken) + { + $accessToken = $accessToken instanceof AccessToken ? $accessToken->getValue() : $accessToken; + $params = ['input_token' => $accessToken]; + + $this->lastRequest = new FacebookRequest( + $this->app, + $this->app->getAccessToken(), + 'GET', + '/debug_token', + $params, + null, + $this->graphVersion + ); + $response = $this->client->sendRequest($this->lastRequest); + $metadata = $response->getDecodedBody(); + + return new AccessTokenMetadata($metadata); + } + + /** + * Generates an authorization URL to begin the process of authenticating a user. + * + * @param string $redirectUrl The callback URL to redirect to. + * @param array $scope An array of permissions to request. + * @param string $state The CSPRNG-generated CSRF value. + * @param array $params An array of parameters to generate URL. + * @param string $separator The separator to use in http_build_query(). + * + * @return string + */ + public function getAuthorizationUrl($redirectUrl, $state, array $scope = [], array $params = [], $separator = '&') + { + $params += [ + 'client_id' => $this->app->getId(), + 'state' => $state, + 'response_type' => 'code', + 'sdk' => 'php-sdk-' . Facebook::VERSION, + 'redirect_uri' => $redirectUrl, + 'scope' => implode(',', $scope) + ]; + + return static::BASE_AUTHORIZATION_URL . '/' . $this->graphVersion . '/dialog/oauth?' . http_build_query($params, null, $separator); + } + + /** + * Get a valid access token from a code. + * + * @param string $code + * @param string $redirectUri + * + * @return AccessToken + * + * @throws FacebookSDKException + */ + public function getAccessTokenFromCode($code, $redirectUri = '') + { + $params = [ + 'code' => $code, + 'redirect_uri' => $redirectUri, + ]; + + return $this->requestAnAccessToken($params); + } + + /** + * Exchanges a short-lived access token with a long-lived access token. + * + * @param AccessToken|string $accessToken + * + * @return AccessToken + * + * @throws FacebookSDKException + */ + public function getLongLivedAccessToken($accessToken) + { + $accessToken = $accessToken instanceof AccessToken ? $accessToken->getValue() : $accessToken; + $params = [ + 'grant_type' => 'fb_exchange_token', + 'fb_exchange_token' => $accessToken, + ]; + + return $this->requestAnAccessToken($params); + } + + /** + * Get a valid code from an access token. + * + * @param AccessToken|string $accessToken + * @param string $redirectUri + * + * @return AccessToken + * + * @throws FacebookSDKException + */ + public function getCodeFromLongLivedAccessToken($accessToken, $redirectUri = '') + { + $params = [ + 'redirect_uri' => $redirectUri, + ]; + + $response = $this->sendRequestWithClientParams('/oauth/client_code', $params, $accessToken); + $data = $response->getDecodedBody(); + + if (!isset($data['code'])) { + throw new FacebookSDKException('Code was not returned from Graph.', 401); + } + + return $data['code']; + } + + /** + * Send a request to the OAuth endpoint. + * + * @param array $params + * + * @return AccessToken + * + * @throws FacebookSDKException + */ + protected function requestAnAccessToken(array $params) + { + $response = $this->sendRequestWithClientParams('/oauth/access_token', $params); + $data = $response->getDecodedBody(); + + if (!isset($data['access_token'])) { + throw new FacebookSDKException('Access token was not returned from Graph.', 401); + } + + // Graph returns two different key names for expiration time + // on the same endpoint. Doh! :/ + $expiresAt = 0; + if (isset($data['expires'])) { + // For exchanging a short lived token with a long lived token. + // The expiration time in seconds will be returned as "expires". + $expiresAt = time() + $data['expires']; + } elseif (isset($data['expires_in'])) { + // For exchanging a code for a short lived access token. + // The expiration time in seconds will be returned as "expires_in". + // See: https://developers.facebook.com/docs/facebook-login/access-tokens#long-via-code + $expiresAt = time() + $data['expires_in']; + } + + return new AccessToken($data['access_token'], $expiresAt); + } + + /** + * Send a request to Graph with an app access token. + * + * @param string $endpoint + * @param array $params + * @param string|null $accessToken + * + * @return FacebookResponse + * + * @throws FacebookResponseException + */ + protected function sendRequestWithClientParams($endpoint, array $params, $accessToken = null) + { + $params += $this->getClientParams(); + + $accessToken = $accessToken ?: $this->app->getAccessToken(); + + $this->lastRequest = new FacebookRequest( + $this->app, + $accessToken, + 'GET', + $endpoint, + $params, + null, + $this->graphVersion + ); + + return $this->client->sendRequest($this->lastRequest); + } + + /** + * Returns the client_* params for OAuth requests. + * + * @return array + */ + protected function getClientParams() + { + return [ + 'client_id' => $this->app->getId(), + 'client_secret' => $this->app->getSecret(), + ]; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Exceptions/FacebookAuthenticationException.php b/lib/facebook-graph-sdk/src/Facebook/Exceptions/FacebookAuthenticationException.php new file mode 100644 index 0000000..449cf93 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Exceptions/FacebookAuthenticationException.php @@ -0,0 +1,33 @@ +response = $response; + $this->responseData = $response->getDecodedBody(); + + $errorMessage = $this->get('message', 'Unknown error from Graph.'); + $errorCode = $this->get('code', -1); + + parent::__construct($errorMessage, $errorCode, $previousException); + } + + /** + * A factory for creating the appropriate exception based on the response from Graph. + * + * @param FacebookResponse $response The response that threw the exception. + * + * @return FacebookResponseException + */ + public static function create(FacebookResponse $response) + { + $data = $response->getDecodedBody(); + + if (!isset($data['error']['code']) && isset($data['code'])) { + $data = ['error' => $data]; + } + + $code = isset($data['error']['code']) ? $data['error']['code'] : null; + $message = isset($data['error']['message']) ? $data['error']['message'] : 'Unknown error from Graph.'; + + $previousException = null; + + if (isset($data['error']['error_subcode'])) { + switch ($data['error']['error_subcode']) { + // Other authentication issues + case 458: + case 459: + case 460: + case 463: + case 464: + case 467: + return new static($response, new FacebookAuthenticationException($message, $code)); + } + } + + switch ($code) { + // Login status or token expired, revoked, or invalid + case 100: + case 102: + case 190: + return new static($response, new FacebookAuthenticationException($message, $code)); + + // Server issue, possible downtime + case 1: + case 2: + return new static($response, new FacebookServerException($message, $code)); + + // API Throttling + case 4: + case 17: + case 341: + return new static($response, new FacebookThrottleException($message, $code)); + + // Duplicate Post + case 506: + return new static($response, new FacebookClientException($message, $code)); + } + + // Missing Permissions + if ($code == 10 || ($code >= 200 && $code <= 299)) { + return new static($response, new FacebookAuthorizationException($message, $code)); + } + + // OAuth authentication error + if (isset($data['error']['type']) && $data['error']['type'] === 'OAuthException') { + return new static($response, new FacebookAuthenticationException($message, $code)); + } + + // All others + return new static($response, new FacebookOtherException($message, $code)); + } + + /** + * Checks isset and returns that or a default value. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + private function get($key, $default = null) + { + if (isset($this->responseData['error'][$key])) { + return $this->responseData['error'][$key]; + } + + return $default; + } + + /** + * Returns the HTTP status code + * + * @return int + */ + public function getHttpStatusCode() + { + return $this->response->getHttpStatusCode(); + } + + /** + * Returns the sub-error code + * + * @return int + */ + public function getSubErrorCode() + { + return $this->get('error_subcode', -1); + } + + /** + * Returns the error type + * + * @return string + */ + public function getErrorType() + { + return $this->get('type', ''); + } + + /** + * Returns the raw response used to create the exception. + * + * @return string + */ + public function getRawResponse() + { + return $this->response->getBody(); + } + + /** + * Returns the decoded response used to create the exception. + * + * @return array + */ + public function getResponseData() + { + return $this->responseData; + } + + /** + * Returns the response entity used to create the exception. + * + * @return FacebookResponse + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Exceptions/FacebookSDKException.php b/lib/facebook-graph-sdk/src/Facebook/Exceptions/FacebookSDKException.php new file mode 100644 index 0000000..03219b0 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Exceptions/FacebookSDKException.php @@ -0,0 +1,33 @@ +app = new FacebookApp($appId, $appSecret); + + $httpClientHandler = null; + if (isset($config['http_client_handler'])) { + if ($config['http_client_handler'] instanceof FacebookHttpClientInterface) { + $httpClientHandler = $config['http_client_handler']; + } elseif ($config['http_client_handler'] === 'curl') { + $httpClientHandler = new FacebookCurlHttpClient(); + } elseif ($config['http_client_handler'] === 'stream') { + $httpClientHandler = new FacebookStreamHttpClient(); + } elseif ($config['http_client_handler'] === 'guzzle') { + $httpClientHandler = new FacebookGuzzleHttpClient(); + } else { + throw new \InvalidArgumentException('The http_client_handler must be set to "curl", "stream", "guzzle", or be an instance of Facebook\HttpClients\FacebookHttpClientInterface'); + } + } + + $enableBeta = isset($config['enable_beta_mode']) && $config['enable_beta_mode'] === true; + $this->client = new FacebookClient($httpClientHandler, $enableBeta); + + if (isset($config['url_detection_handler'])) { + if ($config['url_detection_handler'] instanceof UrlDetectionInterface) { + $this->urlDetectionHandler = $config['url_detection_handler']; + } else { + throw new \InvalidArgumentException('The url_detection_handler must be an instance of Facebook\Url\UrlDetectionInterface'); + } + } + + if (isset($config['pseudo_random_string_generator'])) { + if ($config['pseudo_random_string_generator'] instanceof PseudoRandomStringGeneratorInterface) { + $this->pseudoRandomStringGenerator = $config['pseudo_random_string_generator']; + } elseif ($config['pseudo_random_string_generator'] === 'mcrypt') { + $this->pseudoRandomStringGenerator = new McryptPseudoRandomStringGenerator(); + } elseif ($config['pseudo_random_string_generator'] === 'openssl') { + $this->pseudoRandomStringGenerator = new OpenSslPseudoRandomStringGenerator(); + } elseif ($config['pseudo_random_string_generator'] === 'urandom') { + $this->pseudoRandomStringGenerator = new UrandomPseudoRandomStringGenerator(); + } else { + throw new \InvalidArgumentException('The pseudo_random_string_generator must be set to "mcrypt", "openssl", or "urandom", or be an instance of Facebook\PseudoRandomString\PseudoRandomStringGeneratorInterface'); + } + } + + if (isset($config['persistent_data_handler'])) { + if ($config['persistent_data_handler'] instanceof PersistentDataInterface) { + $this->persistentDataHandler = $config['persistent_data_handler']; + } elseif ($config['persistent_data_handler'] === 'session') { + $this->persistentDataHandler = new FacebookSessionPersistentDataHandler(); + } elseif ($config['persistent_data_handler'] === 'memory') { + $this->persistentDataHandler = new FacebookMemoryPersistentDataHandler(); + } else { + throw new \InvalidArgumentException('The persistent_data_handler must be set to "session", "memory", or be an instance of Facebook\PersistentData\PersistentDataInterface'); + } + } + + if (isset($config['default_access_token'])) { + $this->setDefaultAccessToken($config['default_access_token']); + } + + if (isset($config['default_graph_version'])) { + $this->defaultGraphVersion = $config['default_graph_version']; + } else { + // @todo v6: Throw an InvalidArgumentException if "default_graph_version" is not set + $this->defaultGraphVersion = static::DEFAULT_GRAPH_VERSION; + } + } + + /** + * Returns the FacebookApp entity. + * + * @return FacebookApp + */ + public function getApp() + { + return $this->app; + } + + /** + * Returns the FacebookClient service. + * + * @return FacebookClient + */ + public function getClient() + { + return $this->client; + } + + /** + * Returns the OAuth 2.0 client service. + * + * @return OAuth2Client + */ + public function getOAuth2Client() + { + if (!$this->oAuth2Client instanceof OAuth2Client) { + $app = $this->getApp(); + $client = $this->getClient(); + $this->oAuth2Client = new OAuth2Client($app, $client, $this->defaultGraphVersion); + } + + return $this->oAuth2Client; + } + + /** + * Returns the last response returned from Graph. + * + * @return FacebookResponse|FacebookBatchResponse|null + */ + public function getLastResponse() + { + return $this->lastResponse; + } + + /** + * Returns the URL detection handler. + * + * @return UrlDetectionInterface + */ + public function getUrlDetectionHandler() + { + if (!$this->urlDetectionHandler instanceof UrlDetectionInterface) { + $this->urlDetectionHandler = new FacebookUrlDetectionHandler(); + } + + return $this->urlDetectionHandler; + } + + /** + * Returns the default AccessToken entity. + * + * @return AccessToken|null + */ + public function getDefaultAccessToken() + { + return $this->defaultAccessToken; + } + + /** + * Sets the default access token to use with requests. + * + * @param AccessToken|string $accessToken The access token to save. + * + * @throws \InvalidArgumentException + */ + public function setDefaultAccessToken($accessToken) + { + if (is_string($accessToken)) { + $this->defaultAccessToken = new AccessToken($accessToken); + + return; + } + + if ($accessToken instanceof AccessToken) { + $this->defaultAccessToken = $accessToken; + + return; + } + + throw new \InvalidArgumentException('The default access token must be of type "string" or Facebook\AccessToken'); + } + + /** + * Returns the default Graph version. + * + * @return string + */ + public function getDefaultGraphVersion() + { + return $this->defaultGraphVersion; + } + + /** + * Returns the redirect login helper. + * + * @return FacebookRedirectLoginHelper + */ + public function getRedirectLoginHelper() + { + return new FacebookRedirectLoginHelper( + $this->getOAuth2Client(), + $this->persistentDataHandler, + $this->urlDetectionHandler, + $this->pseudoRandomStringGenerator + ); + } + + /** + * Returns the JavaScript helper. + * + * @return FacebookJavaScriptHelper + */ + public function getJavaScriptHelper() + { + return new FacebookJavaScriptHelper($this->app, $this->client, $this->defaultGraphVersion); + } + + /** + * Returns the canvas helper. + * + * @return FacebookCanvasHelper + */ + public function getCanvasHelper() + { + return new FacebookCanvasHelper($this->app, $this->client, $this->defaultGraphVersion); + } + + /** + * Returns the page tab helper. + * + * @return FacebookPageTabHelper + */ + public function getPageTabHelper() + { + return new FacebookPageTabHelper($this->app, $this->client, $this->defaultGraphVersion); + } + + /** + * Sends a GET request to Graph and returns the result. + * + * @param string $endpoint + * @param AccessToken|string|null $accessToken + * @param string|null $eTag + * @param string|null $graphVersion + * + * @return FacebookResponse + * + * @throws FacebookSDKException + */ + public function get($endpoint, $accessToken = null, $eTag = null, $graphVersion = null) + { + return $this->sendRequest( + 'GET', + $endpoint, + $params = [], + $accessToken, + $eTag, + $graphVersion + ); + } + + /** + * Sends a POST request to Graph and returns the result. + * + * @param string $endpoint + * @param array $params + * @param AccessToken|string|null $accessToken + * @param string|null $eTag + * @param string|null $graphVersion + * + * @return FacebookResponse + * + * @throws FacebookSDKException + */ + public function post($endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) + { + return $this->sendRequest( + 'POST', + $endpoint, + $params, + $accessToken, + $eTag, + $graphVersion + ); + } + + /** + * Sends a DELETE request to Graph and returns the result. + * + * @param string $endpoint + * @param array $params + * @param AccessToken|string|null $accessToken + * @param string|null $eTag + * @param string|null $graphVersion + * + * @return FacebookResponse + * + * @throws FacebookSDKException + */ + public function delete($endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) + { + return $this->sendRequest( + 'DELETE', + $endpoint, + $params, + $accessToken, + $eTag, + $graphVersion + ); + } + + /** + * Sends a request to Graph for the next page of results. + * + * @param GraphEdge $graphEdge The GraphEdge to paginate over. + * + * @return GraphEdge|null + * + * @throws FacebookSDKException + */ + public function next(GraphEdge $graphEdge) + { + return $this->getPaginationResults($graphEdge, 'next'); + } + + /** + * Sends a request to Graph for the previous page of results. + * + * @param GraphEdge $graphEdge The GraphEdge to paginate over. + * + * @return GraphEdge|null + * + * @throws FacebookSDKException + */ + public function previous(GraphEdge $graphEdge) + { + return $this->getPaginationResults($graphEdge, 'previous'); + } + + /** + * Sends a request to Graph for the next page of results. + * + * @param GraphEdge $graphEdge The GraphEdge to paginate over. + * @param string $direction The direction of the pagination: next|previous. + * + * @return GraphEdge|null + * + * @throws FacebookSDKException + */ + public function getPaginationResults(GraphEdge $graphEdge, $direction) + { + $paginationRequest = $graphEdge->getPaginationRequest($direction); + if (!$paginationRequest) { + return null; + } + + $this->lastResponse = $this->client->sendRequest($paginationRequest); + + // Keep the same GraphNode subclass + $subClassName = $graphEdge->getSubClassName(); + $graphEdge = $this->lastResponse->getGraphEdge($subClassName, false); + + return count($graphEdge) > 0 ? $graphEdge : null; + } + + /** + * Sends a request to Graph and returns the result. + * + * @param string $method + * @param string $endpoint + * @param array $params + * @param AccessToken|string|null $accessToken + * @param string|null $eTag + * @param string|null $graphVersion + * + * @return FacebookResponse + * + * @throws FacebookSDKException + */ + public function sendRequest($method, $endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) + { + $accessToken = $accessToken ?: $this->defaultAccessToken; + $graphVersion = $graphVersion ?: $this->defaultGraphVersion; + $request = $this->request($method, $endpoint, $params, $accessToken, $eTag, $graphVersion); + + return $this->lastResponse = $this->client->sendRequest($request); + } + + /** + * Sends a batched request to Graph and returns the result. + * + * @param array $requests + * @param AccessToken|string|null $accessToken + * @param string|null $graphVersion + * + * @return FacebookBatchResponse + * + * @throws FacebookSDKException + */ + public function sendBatchRequest(array $requests, $accessToken = null, $graphVersion = null) + { + $accessToken = $accessToken ?: $this->defaultAccessToken; + $graphVersion = $graphVersion ?: $this->defaultGraphVersion; + $batchRequest = new FacebookBatchRequest( + $this->app, + $requests, + $accessToken, + $graphVersion + ); + + return $this->lastResponse = $this->client->sendBatchRequest($batchRequest); + } + + /** + * Instantiates a new FacebookRequest entity. + * + * @param string $method + * @param string $endpoint + * @param array $params + * @param AccessToken|string|null $accessToken + * @param string|null $eTag + * @param string|null $graphVersion + * + * @return FacebookRequest + * + * @throws FacebookSDKException + */ + public function request($method, $endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null) + { + $accessToken = $accessToken ?: $this->defaultAccessToken; + $graphVersion = $graphVersion ?: $this->defaultGraphVersion; + + return new FacebookRequest( + $this->app, + $accessToken, + $method, + $endpoint, + $params, + $eTag, + $graphVersion + ); + } + + /** + * Factory to create FacebookFile's. + * + * @param string $pathToFile + * + * @return FacebookFile + * + * @throws FacebookSDKException + */ + public function fileToUpload($pathToFile) + { + return new FacebookFile($pathToFile); + } + + /** + * Factory to create FacebookVideo's. + * + * @param string $pathToFile + * + * @return FacebookVideo + * + * @throws FacebookSDKException + */ + public function videoToUpload($pathToFile) + { + return new FacebookVideo($pathToFile); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/FacebookApp.php b/lib/facebook-graph-sdk/src/Facebook/FacebookApp.php new file mode 100644 index 0000000..84956ce --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/FacebookApp.php @@ -0,0 +1,101 @@ +id = $id; + $this->secret = $secret; + } + + /** + * Returns the app ID. + * + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * Returns the app secret. + * + * @return string + */ + public function getSecret() + { + return $this->secret; + } + + /** + * Returns an app access token. + * + * @return AccessToken + */ + public function getAccessToken() + { + return new AccessToken($this->id . '|' . $this->secret); + } + + /** + * Serializes the FacebookApp entity as a string. + * + * @return string + */ + public function serialize() + { + return serialize([$this->id, $this->secret]); + } + + /** + * Unserializes a string as a FacebookApp entity. + * + * @param string $serialized + */ + public function unserialize($serialized) + { + list($id, $secret) = unserialize($serialized); + + $this->__construct($id, $secret); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/FacebookBatchRequest.php b/lib/facebook-graph-sdk/src/Facebook/FacebookBatchRequest.php new file mode 100644 index 0000000..33c489c --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/FacebookBatchRequest.php @@ -0,0 +1,303 @@ +add($requests); + } + + /** + * A a new request to the array. + * + * @param FacebookRequest|array $request + * @param string|null $name + * + * @return FacebookBatchRequest + * + * @throws \InvalidArgumentException + */ + public function add($request, $name = null) + { + if (is_array($request)) { + foreach ($request as $key => $req) { + $this->add($req, $key); + } + + return $this; + } + + if (!$request instanceof FacebookRequest) { + throw new \InvalidArgumentException('Argument for add() must be of type array or FacebookRequest.'); + } + + $this->addFallbackDefaults($request); + $requestToAdd = [ + 'name' => $name, + 'request' => $request, + ]; + + // File uploads + $attachedFiles = $this->extractFileAttachments($request); + if ($attachedFiles) { + $requestToAdd['attached_files'] = $attachedFiles; + } + $this->requests[] = $requestToAdd; + + return $this; + } + + /** + * Ensures that the FacebookApp and access token fall back when missing. + * + * @param FacebookRequest $request + * + * @throws FacebookSDKException + */ + public function addFallbackDefaults(FacebookRequest $request) + { + if (!$request->getApp()) { + $app = $this->getApp(); + if (!$app) { + throw new FacebookSDKException('Missing FacebookApp on FacebookRequest and no fallback detected on FacebookBatchRequest.'); + } + $request->setApp($app); + } + + if (!$request->getAccessToken()) { + $accessToken = $this->getAccessToken(); + if (!$accessToken) { + throw new FacebookSDKException('Missing access token on FacebookRequest and no fallback detected on FacebookBatchRequest.'); + } + $request->setAccessToken($accessToken); + } + } + + /** + * Extracts the files from a request. + * + * @param FacebookRequest $request + * + * @return string|null + * + * @throws FacebookSDKException + */ + public function extractFileAttachments(FacebookRequest $request) + { + if (!$request->containsFileUploads()) { + return null; + } + + $files = $request->getFiles(); + $fileNames = []; + foreach ($files as $file) { + $fileName = uniqid(); + $this->addFile($fileName, $file); + $fileNames[] = $fileName; + } + + $request->resetFiles(); + + // @TODO Does Graph support multiple uploads on one endpoint? + return implode(',', $fileNames); + } + + /** + * Return the FacebookRequest entities. + * + * @return array + */ + public function getRequests() + { + return $this->requests; + } + + /** + * Prepares the requests to be sent as a batch request. + * + * @return string + */ + public function prepareRequestsForBatch() + { + $this->validateBatchRequestCount(); + + $params = [ + 'batch' => $this->convertRequestsToJson(), + 'include_headers' => true, + ]; + $this->setParams($params); + } + + /** + * Converts the requests into a JSON(P) string. + * + * @return string + */ + public function convertRequestsToJson() + { + $requests = []; + foreach ($this->requests as $request) { + $attachedFiles = isset($request['attached_files']) ? $request['attached_files'] : null; + $requests[] = $this->requestEntityToBatchArray($request['request'], $request['name'], $attachedFiles); + } + + return json_encode($requests); + } + + /** + * Validate the request count before sending them as a batch. + * + * @throws FacebookSDKException + */ + public function validateBatchRequestCount() + { + $batchCount = count($this->requests); + if ($batchCount === 0) { + throw new FacebookSDKException('There are no batch requests to send.'); + } elseif ($batchCount > 50) { + // Per: https://developers.facebook.com/docs/graph-api/making-multiple-requests#limits + throw new FacebookSDKException('You cannot send more than 50 batch requests at a time.'); + } + } + + /** + * Converts a Request entity into an array that is batch-friendly. + * + * @param FacebookRequest $request The request entity to convert. + * @param string|null $requestName The name of the request. + * @param string|null $attachedFiles Names of files associated with the request. + * + * @return array + */ + public function requestEntityToBatchArray(FacebookRequest $request, $requestName = null, $attachedFiles = null) + { + $compiledHeaders = []; + $headers = $request->getHeaders(); + foreach ($headers as $name => $value) { + $compiledHeaders[] = $name . ': ' . $value; + } + + $batch = [ + 'headers' => $compiledHeaders, + 'method' => $request->getMethod(), + 'relative_url' => $request->getUrl(), + ]; + + // Since file uploads are moved to the root request of a batch request, + // the child requests will always be URL-encoded. + $body = $request->getUrlEncodedBody()->getBody(); + if ($body) { + $batch['body'] = $body; + } + + if (isset($requestName)) { + $batch['name'] = $requestName; + } + + if (isset($attachedFiles)) { + $batch['attached_files'] = $attachedFiles; + } + + // @TODO Add support for "omit_response_on_success" + // @TODO Add support for "depends_on" + // @TODO Add support for JSONP with "callback" + + return $batch; + } + + /** + * Get an iterator for the items. + * + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->requests); + } + + /** + * @inheritdoc + */ + public function offsetSet($offset, $value) + { + $this->add($value, $offset); + } + + /** + * @inheritdoc + */ + public function offsetExists($offset) + { + return isset($this->requests[$offset]); + } + + /** + * @inheritdoc + */ + public function offsetUnset($offset) + { + unset($this->requests[$offset]); + } + + /** + * @inheritdoc + */ + public function offsetGet($offset) + { + return isset($this->requests[$offset]) ? $this->requests[$offset] : null; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/FacebookBatchResponse.php b/lib/facebook-graph-sdk/src/Facebook/FacebookBatchResponse.php new file mode 100644 index 0000000..5ea765e --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/FacebookBatchResponse.php @@ -0,0 +1,154 @@ +batchRequest = $batchRequest; + + $request = $response->getRequest(); + $body = $response->getBody(); + $httpStatusCode = $response->getHttpStatusCode(); + $headers = $response->getHeaders(); + parent::__construct($request, $body, $httpStatusCode, $headers); + + $responses = $response->getDecodedBody(); + $this->setResponses($responses); + } + + /** + * Returns an array of FacebookResponse entities. + * + * @return array + */ + public function getResponses() + { + return $this->responses; + } + + /** + * The main batch response will be an array of requests so + * we need to iterate over all the responses. + * + * @param array $responses + */ + public function setResponses(array $responses) + { + $this->responses = []; + + foreach ($responses as $key => $graphResponse) { + $this->addResponse($key, $graphResponse); + } + } + + /** + * Add a response to the list. + * + * @param int $key + * @param array|null $response + */ + public function addResponse($key, $response) + { + $originalRequestName = isset($this->batchRequest[$key]['name']) ? $this->batchRequest[$key]['name'] : $key; + $originalRequest = isset($this->batchRequest[$key]['request']) ? $this->batchRequest[$key]['request'] : null; + + $httpResponseBody = isset($response['body']) ? $response['body'] : null; + $httpResponseCode = isset($response['code']) ? $response['code'] : null; + $httpResponseHeaders = isset($response['headers']) ? $response['headers'] : []; + + $this->responses[$originalRequestName] = new FacebookResponse( + $originalRequest, + $httpResponseBody, + $httpResponseCode, + $httpResponseHeaders + ); + } + + /** + * @inheritdoc + */ + public function getIterator() + { + return new ArrayIterator($this->responses); + } + + /** + * @inheritdoc + */ + public function offsetSet($offset, $value) + { + $this->addResponse($offset, $value); + } + + /** + * @inheritdoc + */ + public function offsetExists($offset) + { + return isset($this->responses[$offset]); + } + + /** + * @inheritdoc + */ + public function offsetUnset($offset) + { + unset($this->responses[$offset]); + } + + /** + * @inheritdoc + */ + public function offsetGet($offset) + { + return isset($this->responses[$offset]) ? $this->responses[$offset] : null; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/FacebookClient.php b/lib/facebook-graph-sdk/src/Facebook/FacebookClient.php new file mode 100644 index 0000000..b10762f --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/FacebookClient.php @@ -0,0 +1,250 @@ +httpClientHandler = $httpClientHandler ?: $this->detectHttpClientHandler(); + $this->enableBetaMode = $enableBeta; + } + + /** + * Sets the HTTP client handler. + * + * @param FacebookHttpClientInterface $httpClientHandler + */ + public function setHttpClientHandler(FacebookHttpClientInterface $httpClientHandler) + { + $this->httpClientHandler = $httpClientHandler; + } + + /** + * Returns the HTTP client handler. + * + * @return FacebookHttpClientInterface + */ + public function getHttpClientHandler() + { + return $this->httpClientHandler; + } + + /** + * Detects which HTTP client handler to use. + * + * @return FacebookHttpClientInterface + */ + public function detectHttpClientHandler() + { + return function_exists('curl_init') ? new FacebookCurlHttpClient() : new FacebookStreamHttpClient(); + } + + /** + * Toggle beta mode. + * + * @param boolean $betaMode + */ + public function enableBetaMode($betaMode = true) + { + $this->enableBetaMode = $betaMode; + } + + /** + * Returns the base Graph URL. + * + * @param boolean $postToVideoUrl Post to the video API if videos are being uploaded. + * + * @return string + */ + public function getBaseGraphUrl($postToVideoUrl = false) + { + if ($postToVideoUrl) { + return $this->enableBetaMode ? static::BASE_GRAPH_VIDEO_URL_BETA : static::BASE_GRAPH_VIDEO_URL; + } + + return $this->enableBetaMode ? static::BASE_GRAPH_URL_BETA : static::BASE_GRAPH_URL; + } + + /** + * Prepares the request for sending to the client handler. + * + * @param FacebookRequest $request + * + * @return array + */ + public function prepareRequestMessage(FacebookRequest $request) + { + $postToVideoUrl = $request->containsVideoUploads(); + $url = $this->getBaseGraphUrl($postToVideoUrl) . $request->getUrl(); + + // If we're sending files they should be sent as multipart/form-data + if ($request->containsFileUploads()) { + $requestBody = $request->getMultipartBody(); + $request->setHeaders([ + 'Content-Type' => 'multipart/form-data; boundary=' . $requestBody->getBoundary(), + ]); + } else { + $requestBody = $request->getUrlEncodedBody(); + $request->setHeaders([ + 'Content-Type' => 'application/x-www-form-urlencoded', + ]); + } + + return [ + $url, + $request->getMethod(), + $request->getHeaders(), + $requestBody->getBody(), + ]; + } + + /** + * Makes the request to Graph and returns the result. + * + * @param FacebookRequest $request + * + * @return FacebookResponse + * + * @throws FacebookSDKException + */ + public function sendRequest(FacebookRequest $request) + { + if (get_class($request) === 'FacebookRequest') { + $request->validateAccessToken(); + } + + list($url, $method, $headers, $body) = $this->prepareRequestMessage($request); + + // Since file uploads can take a while, we need to give more time for uploads + $timeOut = static::DEFAULT_REQUEST_TIMEOUT; + if ($request->containsFileUploads()) { + $timeOut = static::DEFAULT_FILE_UPLOAD_REQUEST_TIMEOUT; + } elseif ($request->containsVideoUploads()) { + $timeOut = static::DEFAULT_VIDEO_UPLOAD_REQUEST_TIMEOUT; + } + + // Should throw `FacebookSDKException` exception on HTTP client error. + // Don't catch to allow it to bubble up. + $rawResponse = $this->httpClientHandler->send($url, $method, $body, $headers, $timeOut); + + static::$requestCount++; + + $returnResponse = new FacebookResponse( + $request, + $rawResponse->getBody(), + $rawResponse->getHttpResponseCode(), + $rawResponse->getHeaders() + ); + + if ($returnResponse->isError()) { + throw $returnResponse->getThrownException(); + } + + return $returnResponse; + } + + /** + * Makes a batched request to Graph and returns the result. + * + * @param FacebookBatchRequest $request + * + * @return FacebookBatchResponse + * + * @throws FacebookSDKException + */ + public function sendBatchRequest(FacebookBatchRequest $request) + { + $request->prepareRequestsForBatch(); + $facebookResponse = $this->sendRequest($request); + + return new FacebookBatchResponse($request, $facebookResponse); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/FacebookRequest.php b/lib/facebook-graph-sdk/src/Facebook/FacebookRequest.php new file mode 100644 index 0000000..5e4083f --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/FacebookRequest.php @@ -0,0 +1,536 @@ +setApp($app); + $this->setAccessToken($accessToken); + $this->setMethod($method); + $this->setEndpoint($endpoint); + $this->setParams($params); + $this->setETag($eTag); + $this->graphVersion = $graphVersion ?: Facebook::DEFAULT_GRAPH_VERSION; + } + + /** + * Set the access token for this request. + * + * @param AccessToken|string + * + * @return FacebookRequest + */ + public function setAccessToken($accessToken) + { + $this->accessToken = $accessToken; + if ($accessToken instanceof AccessToken) { + $this->accessToken = $accessToken->getValue(); + } + + return $this; + } + + /** + * Sets the access token with one harvested from a URL or POST params. + * + * @param string $accessToken The access token. + * + * @return FacebookRequest + * + * @throws FacebookSDKException + */ + public function setAccessTokenFromParams($accessToken) + { + $existingAccessToken = $this->getAccessToken(); + if (!$existingAccessToken) { + $this->setAccessToken($accessToken); + } elseif ($accessToken !== $existingAccessToken) { + throw new FacebookSDKException('Access token mismatch. The access token provided in the FacebookRequest and the one provided in the URL or POST params do not match.'); + } + + return $this; + } + + /** + * Return the access token for this request. + * + * @return string|null + */ + public function getAccessToken() + { + return $this->accessToken; + } + + /** + * Return the access token for this request an an AccessToken entity. + * + * @return AccessToken|null + */ + public function getAccessTokenEntity() + { + return $this->accessToken ? new AccessToken($this->accessToken) : null; + } + + /** + * Set the FacebookApp entity used for this request. + * + * @param FacebookApp|null $app + */ + public function setApp(FacebookApp $app = null) + { + $this->app = $app; + } + + /** + * Return the FacebookApp entity used for this request. + * + * @return FacebookApp + */ + public function getApp() + { + return $this->app; + } + + /** + * Generate an app secret proof to sign this request. + * + * @return string|null + */ + public function getAppSecretProof() + { + if (!$accessTokenEntity = $this->getAccessTokenEntity()) { + return null; + } + + return $accessTokenEntity->getAppSecretProof($this->app->getSecret()); + } + + /** + * Validate that an access token exists for this request. + * + * @throws FacebookSDKException + */ + public function validateAccessToken() + { + $accessToken = $this->getAccessToken(); + if (!$accessToken) { + throw new FacebookSDKException('You must provide an access token.'); + } + } + + /** + * Set the HTTP method for this request. + * + * @param string + * + * @return FacebookRequest + */ + public function setMethod($method) + { + $this->method = strtoupper($method); + } + + /** + * Return the HTTP method for this request. + * + * @return string + */ + public function getMethod() + { + return $this->method; + } + + /** + * Validate that the HTTP method is set. + * + * @throws FacebookSDKException + */ + public function validateMethod() + { + if (!$this->method) { + throw new FacebookSDKException('HTTP method not specified.'); + } + + if (!in_array($this->method, ['GET', 'POST', 'DELETE'])) { + throw new FacebookSDKException('Invalid HTTP method specified.'); + } + } + + /** + * Set the endpoint for this request. + * + * @param string + * + * @return FacebookRequest + * + * @throws FacebookSDKException + */ + public function setEndpoint($endpoint) + { + // Harvest the access token from the endpoint to keep things in sync + $params = FacebookUrlManipulator::getParamsAsArray($endpoint); + if (isset($params['access_token'])) { + $this->setAccessTokenFromParams($params['access_token']); + } + + // Clean the token & app secret proof from the endpoint. + $filterParams = ['access_token', 'appsecret_proof']; + $this->endpoint = FacebookUrlManipulator::removeParamsFromUrl($endpoint, $filterParams); + + return $this; + } + + /** + * Return the HTTP method for this request. + * + * @return string + */ + public function getEndpoint() + { + // For batch requests, this will be empty + return $this->endpoint; + } + + /** + * Generate and return the headers for this request. + * + * @return array + */ + public function getHeaders() + { + $headers = static::getDefaultHeaders(); + + if ($this->eTag) { + $headers['If-None-Match'] = $this->eTag; + } + + return array_merge($this->headers, $headers); + } + + /** + * Set the headers for this request. + * + * @param array $headers + */ + public function setHeaders(array $headers) + { + $this->headers = array_merge($this->headers, $headers); + } + + /** + * Sets the eTag value. + * + * @param string $eTag + */ + public function setETag($eTag) + { + $this->eTag = $eTag; + } + + /** + * Set the params for this request. + * + * @param array $params + * + * @return FacebookRequest + * + * @throws FacebookSDKException + */ + public function setParams(array $params = []) + { + if (isset($params['access_token'])) { + $this->setAccessTokenFromParams($params['access_token']); + } + + // Don't let these buggers slip in. + unset($params['access_token'], $params['appsecret_proof']); + + // @TODO Refactor code above with this + //$params = $this->sanitizeAuthenticationParams($params); + $params = $this->sanitizeFileParams($params); + $this->dangerouslySetParams($params); + + return $this; + } + + /** + * Set the params for this request without filtering them first. + * + * @param array $params + * + * @return FacebookRequest + */ + public function dangerouslySetParams(array $params = []) + { + $this->params = array_merge($this->params, $params); + + return $this; + } + + /** + * Iterate over the params and pull out the file uploads. + * + * @param array $params + * + * @return array + */ + public function sanitizeFileParams(array $params) + { + foreach ($params as $key => $value) { + if ($value instanceof FacebookFile) { + $this->addFile($key, $value); + unset($params[$key]); + } + } + + return $params; + } + + /** + * Add a file to be uploaded. + * + * @param string $key + * @param FacebookFile $file + */ + public function addFile($key, FacebookFile $file) + { + $this->files[$key] = $file; + } + + /** + * Removes all the files from the upload queue. + */ + public function resetFiles() + { + $this->files = []; + } + + /** + * Get the list of files to be uploaded. + * + * @return array + */ + public function getFiles() + { + return $this->files; + } + + /** + * Let's us know if there is a file upload with this request. + * + * @return boolean + */ + public function containsFileUploads() + { + return !empty($this->files); + } + + /** + * Let's us know if there is a video upload with this request. + * + * @return boolean + */ + public function containsVideoUploads() + { + foreach ($this->files as $file) { + if ($file instanceof FacebookVideo) { + return true; + } + } + + return false; + } + + /** + * Returns the body of the request as multipart/form-data. + * + * @return RequestBodyMultipart + */ + public function getMultipartBody() + { + $params = $this->getPostParams(); + + return new RequestBodyMultipart($params, $this->files); + } + + /** + * Returns the body of the request as URL-encoded. + * + * @return RequestBodyUrlEncoded + */ + public function getUrlEncodedBody() + { + $params = $this->getPostParams(); + + return new RequestBodyUrlEncoded($params); + } + + /** + * Generate and return the params for this request. + * + * @return array + */ + public function getParams() + { + $params = $this->params; + + $accessToken = $this->getAccessToken(); + if ($accessToken) { + $params['access_token'] = $accessToken; + $params['appsecret_proof'] = $this->getAppSecretProof(); + } + + return $params; + } + + /** + * Only return params on POST requests. + * + * @return array + */ + public function getPostParams() + { + if ($this->getMethod() === 'POST') { + return $this->getParams(); + } + + return []; + } + + /** + * The graph version used for this request. + * + * @return string + */ + public function getGraphVersion() + { + return $this->graphVersion; + } + + /** + * Generate and return the URL for this request. + * + * @return string + */ + public function getUrl() + { + $this->validateMethod(); + + $graphVersion = FacebookUrlManipulator::forceSlashPrefix($this->graphVersion); + $endpoint = FacebookUrlManipulator::forceSlashPrefix($this->getEndpoint()); + + $url = $graphVersion . $endpoint; + + if ($this->getMethod() !== 'POST') { + $params = $this->getParams(); + $url = FacebookUrlManipulator::appendParamsToUrl($url, $params); + } + + return $url; + } + + /** + * Return the default headers that every request should use. + * + * @return array + */ + public static function getDefaultHeaders() + { + return [ + 'User-Agent' => 'fb-php-' . Facebook::VERSION, + 'Accept-Encoding' => '*', + ]; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/FacebookResponse.php b/lib/facebook-graph-sdk/src/Facebook/FacebookResponse.php new file mode 100644 index 0000000..ce55b14 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/FacebookResponse.php @@ -0,0 +1,410 @@ +request = $request; + $this->body = $body; + $this->httpStatusCode = $httpStatusCode; + $this->headers = $headers; + + $this->decodeBody(); + } + + /** + * Return the original request that returned this response. + * + * @return FacebookRequest + */ + public function getRequest() + { + return $this->request; + } + + /** + * Return the FacebookApp entity used for this response. + * + * @return FacebookApp + */ + public function getApp() + { + return $this->request->getApp(); + } + + /** + * Return the access token that was used for this response. + * + * @return string|null + */ + public function getAccessToken() + { + return $this->request->getAccessToken(); + } + + /** + * Return the HTTP status code for this response. + * + * @return int + */ + public function getHttpStatusCode() + { + return $this->httpStatusCode; + } + + /** + * Return the HTTP headers for this response. + * + * @return array + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Return the raw body response. + * + * @return string + */ + public function getBody() + { + return $this->body; + } + + /** + * Return the decoded body response. + * + * @return array + */ + public function getDecodedBody() + { + return $this->decodedBody; + } + + /** + * Get the app secret proof that was used for this response. + * + * @return string|null + */ + public function getAppSecretProof() + { + return $this->request->getAppSecretProof(); + } + + /** + * Get the ETag associated with the response. + * + * @return string|null + */ + public function getETag() + { + return isset($this->headers['ETag']) ? $this->headers['ETag'] : null; + } + + /** + * Get the version of Graph that returned this response. + * + * @return string|null + */ + public function getGraphVersion() + { + return isset($this->headers['Facebook-API-Version']) ? $this->headers['Facebook-API-Version'] : null; + } + + /** + * Returns true if Graph returned an error message. + * + * @return boolean + */ + public function isError() + { + return isset($this->decodedBody['error']); + } + + /** + * Throws the exception. + * + * @throws FacebookSDKException + */ + public function throwException() + { + throw $this->thrownException; + } + + /** + * Instantiates an exception to be thrown later. + */ + public function makeException() + { + $this->thrownException = FacebookResponseException::create($this); + } + + /** + * Returns the exception that was thrown for this request. + * + * @return FacebookSDKException|null + */ + public function getThrownException() + { + return $this->thrownException; + } + + /** + * Convert the raw response into an array if possible. + * + * Graph will return 2 types of responses: + * - JSON(P) + * Most responses from Grpah are JSON(P) + * - application/x-www-form-urlencoded key/value pairs + * Happens on the `/oauth/access_token` endpoint when exchanging + * a short-lived access token for a long-lived access token + * - And sometimes nothing :/ but that'd be a bug. + */ + public function decodeBody() + { + $this->decodedBody = json_decode($this->body, true); + + if ($this->decodedBody === null) { + $this->decodedBody = []; + parse_str($this->body, $this->decodedBody); + } elseif (is_bool($this->decodedBody)) { + // Backwards compatibility for Graph < 2.1. + // Mimics 2.1 responses. + // @TODO Remove this after Graph 2.0 is no longer supported + $this->decodedBody = ['success' => $this->decodedBody]; + } elseif (is_numeric($this->decodedBody)) { + $this->decodedBody = ['id' => $this->decodedBody]; + } + + if (!is_array($this->decodedBody)) { + $this->decodedBody = []; + } + + if ($this->isError()) { + $this->makeException(); + } + } + + /** + * Instantiate a new GraphObject from response. + * + * @param string|null $subclassName The GraphNode sub class to cast to. + * + * @return \Facebook\GraphNodes\GraphObject + * + * @throws FacebookSDKException + * + * @deprecated 5.0.0 getGraphObject() has been renamed to getGraphNode() + * @todo v6: Remove this method + */ + public function getGraphObject($subclassName = null) + { + return $this->getGraphNode($subclassName); + } + + /** + * Instantiate a new GraphNode from response. + * + * @param string|null $subclassName The GraphNode sub class to cast to. + * + * @return \Facebook\GraphNodes\GraphNode + * + * @throws FacebookSDKException + */ + public function getGraphNode($subclassName = null) + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphNode($subclassName); + } + + /** + * Convenience method for creating a GraphAlbum collection. + * + * @return \Facebook\GraphNodes\GraphAlbum + * + * @throws FacebookSDKException + */ + public function getGraphAlbum() + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphAlbum(); + } + + /** + * Convenience method for creating a GraphPage collection. + * + * @return \Facebook\GraphNodes\GraphPage + * + * @throws FacebookSDKException + */ + public function getGraphPage() + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphPage(); + } + + /** + * Convenience method for creating a GraphSessionInfo collection. + * + * @return \Facebook\GraphNodes\GraphSessionInfo + * + * @throws FacebookSDKException + */ + public function getGraphSessionInfo() + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphSessionInfo(); + } + + /** + * Convenience method for creating a GraphUser collection. + * + * @return \Facebook\GraphNodes\GraphUser + * + * @throws FacebookSDKException + */ + public function getGraphUser() + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphUser(); + } + + /** + * Convenience method for creating a GraphEvent collection. + * + * @return \Facebook\GraphNodes\GraphEvent + * + * @throws FacebookSDKException + */ + public function getGraphEvent() + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphEvent(); + } + + /** + * Convenience method for creating a GraphGroup collection. + * + * @return \Facebook\GraphNodes\GraphGroup + * + * @throws FacebookSDKException + */ + public function getGraphGroup() + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphGroup(); + } + + /** + * Instantiate a new GraphList from response. + * + * @param string|null $subclassName The GraphNode sub class to cast list items to. + * @param boolean $auto_prefix Toggle to auto-prefix the subclass name. + * + * @return \Facebook\GraphNodes\GraphList + * + * @throws FacebookSDKException + * + * @deprecated 5.0.0 getGraphList() has been renamed to getGraphEdge() + * @todo v6: Remove this method + */ + public function getGraphList($subclassName = null, $auto_prefix = true) + { + return $this->getGraphEdge($subclassName, $auto_prefix); + } + + /** + * Instantiate a new GraphEdge from response. + * + * @param string|null $subclassName The GraphNode sub class to cast list items to. + * @param boolean $auto_prefix Toggle to auto-prefix the subclass name. + * + * @return \Facebook\GraphNodes\GraphEdge + * + * @throws FacebookSDKException + */ + public function getGraphEdge($subclassName = null, $auto_prefix = true) + { + $factory = new GraphNodeFactory($this); + + return $factory->makeGraphEdge($subclassName, $auto_prefix); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/FileUpload/FacebookFile.php b/lib/facebook-graph-sdk/src/Facebook/FileUpload/FacebookFile.php new file mode 100644 index 0000000..f8b9905 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/FileUpload/FacebookFile.php @@ -0,0 +1,135 @@ +path = $filePath; + $this->open(); + } + + /** + * Closes the stream when destructed. + */ + public function __destruct() + { + $this->close(); + } + + /** + * Opens a stream for the file. + * + * @throws FacebookSDKException + */ + public function open() + { + if (!$this->isRemoteFile($this->path) && !is_readable($this->path)) { + throw new FacebookSDKException('Failed to create FacebookFile entity. Unable to read resource: ' . $this->path . '.'); + } + + $this->stream = fopen($this->path, 'r'); + + if (!$this->stream) { + throw new FacebookSDKException('Failed to create FacebookFile entity. Unable to open resource: ' . $this->path . '.'); + } + } + + /** + * Stops the file stream. + */ + public function close() + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + } + + /** + * Return the contents of the file. + * + * @return string + */ + public function getContents() + { + return stream_get_contents($this->stream); + } + + /** + * Return the name of the file. + * + * @return string + */ + public function getFileName() + { + return basename($this->path); + } + + /** + * Return the mimetype of the file. + * + * @return string + */ + public function getMimetype() + { + return Mimetypes::getInstance()->fromFilename($this->path) ?: 'text/plain'; + } + + /** + * Returns true if the path to the file is remote. + * + * @param string $pathToFile + * + * @return boolean + */ + protected function isRemoteFile($pathToFile) + { + return preg_match('/^(https?|ftp):\/\/.*/', $pathToFile) === 1; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/FileUpload/FacebookVideo.php b/lib/facebook-graph-sdk/src/Facebook/FileUpload/FacebookVideo.php new file mode 100644 index 0000000..1e8c55a --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/FileUpload/FacebookVideo.php @@ -0,0 +1,33 @@ + 'text/vnd.in3d.3dml', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gpp', + '7z' => 'application/x-7z-compressed', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-aac', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/pkix-attr-cert', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'apk' => 'application/vnd.android.package-archive', + 'application' => 'application/x-ms-application', + 'apr' => 'application/vnd.lotus-approach', + 'asa' => 'text/plain', + 'asax' => 'application/octet-stream', + 'asc' => 'application/pgp-signature', + 'ascx' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'ashx' => 'text/plain', + 'asm' => 'text/x-asm', + 'asmx' => 'text/plain', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asp' => 'text/plain', + 'aspx' => 'text/plain', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/basic', + 'avi' => 'video/x-msvideo', + 'aw' => 'application/applixware', + 'axd' => 'text/plain', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azw' => 'application/vnd.amazon.ebook', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'bmi' => 'application/vnd.bmi', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'btif' => 'image/prs.btif', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'cab' => 'application/vnd.ms-cab-compressed', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cc' => 'text/x-c', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfc' => 'application/x-coldfusion', + 'cfm' => 'application/x-coldfusion', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/java-vm', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'cs' => 'text/plain', + 'csh' => 'application/x-csh', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/x-msdownload', + 'dmg' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.document.macroenabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroenabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvi' => 'application/x-dvi', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es3' => 'application/vnd.eszigno3+xml', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'exe' => 'application/x-msdownload', + 'exi' => 'application/exi', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/x-f4v', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gdl' => 'model/vnd.gdl', + 'geo' => 'application/vnd.dynageo', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gph' => 'application/vnd.flographit', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxt' => 'application/vnd.geonext', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hdf' => 'application/x-hdf', + 'hh' => 'text/x-c', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hta' => 'application/octet-stream', + 'htc' => 'text/html', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/octet-stream', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'java' => 'text/x-java-source', + 'jisp' => 'application/vnd.jisp', + 'jlt' => 'application/vnd.hp-jlyt', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jpm' => 'video/jpm', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm1v' => 'video/mpeg', + 'm21' => 'application/mp21', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'audio/x-mpegurl', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/mp4', + 'm4u' => 'video/vnd.mpegurl', + 'm4v' => 'video/mp4', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp21' => 'application/mp21', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msty' => 'application/vnd.muvee.style', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nsf' => 'application/vnd.lotus-notes', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'oprc' => 'application/vnd.palm', + 'org' => 'application/vnd.lotus-organizer', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'application/x-font-otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p10' => 'application/pkcs10', + 'p12' => 'application/x-pkcs12', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/vnd.palm', + 'pdf' => 'application/pdf', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp-encrypted', + 'php' => 'text/x-php', + 'phps' => 'application/x-httpd-phps', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'application/x-mobipocket-ebook', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'image/vnd.adobe.photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-pn-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rb' => 'text/plain', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'resx' => 'text/xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'application/vnd.rn-realmedia', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rnc' => 'application/relax-ng-compact-syntax', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsd' => 'application/rsd+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'rtx' => 'text/richtext', + 's' => 'text/x-asm', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shf' => 'application/shf+xml', + 'sig' => 'application/pgp-signature', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil+xml', + 'smil' => 'application/smil+xml', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'src' => 'application/x-wais-source', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'application/vnd.ms-pki.stl', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'sub' => 'image/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tmo' => 'application/vnd.tmobile-livetv', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trm' => 'application/x-msterminal', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'application/x-font-ttf', + 'ttf' => 'application/x-font-ttf', + 'ttl' => 'text/turtle', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u32' => 'application/x-authorware-bin', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvx' => 'application/vnd.dece.unspecified', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'weba' => 'audio/webm', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'application/x-msmetafile', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-ms-wmz', + 'woff' => 'application/x-font-woff', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x32' => 'application/x-authorware-bin', + 'x3d' => 'application/vnd.hzn-3d-crossword', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xml' => 'application/xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml' + ]; + + /** + * Get a singleton instance of the class + * + * @return self + * @codeCoverageIgnore + */ + public static function getInstance() + { + if (!self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Get a mimetype value from a file extension + * + * @param string $extension File extension + * + * @return string|null + */ + public function fromExtension($extension) + { + $extension = strtolower($extension); + + return isset($this->mimetypes[$extension]) ? $this->mimetypes[$extension] : null; + } + + /** + * Get a mimetype from a filename + * + * @param string $filename Filename to generate a mimetype from + * + * @return string|null + */ + public function fromFilename($filename) + { + return $this->fromExtension(pathinfo($filename, PATHINFO_EXTENSION)); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/Collection.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/Collection.php new file mode 100644 index 0000000..cac010b --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/Collection.php @@ -0,0 +1,242 @@ +items = $items; + } + + /** + * Gets the value of a field from the Graph node. + * + * @param string $name The field to retrieve. + * @param mixed $default The default to return if the field doesn't exist. + * + * @return mixed + */ + public function getField($name, $default = null) + { + if (isset($this->items[$name])) { + return $this->items[$name]; + } + + return $default ?: null; + } + + /** + * Gets the value of the named property for this graph object. + * + * @param string $name The property to retrieve. + * @param mixed $default The default to return if the property doesn't exist. + * + * @return mixed + * + * @deprecated 5.0.0 getProperty() has been renamed to getField() + * @todo v6: Remove this method + */ + public function getProperty($name, $default = null) + { + return $this->getField($name, $default); + } + + /** + * Returns a list of all fields set on the object. + * + * @return array + */ + public function getFieldNames() + { + return array_keys($this->items); + } + + /** + * Returns a list of all properties set on the object. + * + * @return array + * + * @deprecated 5.0.0 getPropertyNames() has been renamed to getFieldNames() + * @todo v6: Remove this method + */ + public function getPropertyNames() + { + return $this->getFieldNames(); + } + + /** + * Get all of the items in the collection. + * + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * Get the collection of items as a plain array. + * + * @return array + */ + public function asArray() + { + return array_map(function ($value) { + return $value instanceof Collection ? $value->asArray() : $value; + }, $this->items); + } + + /** + * Run a map over each of the items. + * + * @param \Closure $callback + * + * @return static + */ + public function map(\Closure $callback) + { + return new static(array_map($callback, $this->items, array_keys($this->items))); + } + + /** + * Get the collection of items as JSON. + * + * @param int $options + * + * @return string + */ + public function asJson($options = 0) + { + return json_encode($this->asArray(), $options); + } + + /** + * Count the number of items in the collection. + * + * @return int + */ + public function count() + { + return count($this->items); + } + + /** + * Get an iterator for the items. + * + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * Determine if an item exists at an offset. + * + * @param mixed $key + * + * @return bool + */ + public function offsetExists($key) + { + return array_key_exists($key, $this->items); + } + + /** + * Get an item at a given offset. + * + * @param mixed $key + * + * @return mixed + */ + public function offsetGet($key) + { + return $this->items[$key]; + } + + /** + * Set the item at a given offset. + * + * @param mixed $key + * @param mixed $value + * + * @return void + */ + public function offsetSet($key, $value) + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * Unset the item at a given offset. + * + * @param string $key + * + * @return void + */ + public function offsetUnset($key) + { + unset($this->items[$key]); + } + + /** + * Convert the collection to its string representation. + * + * @return string + */ + public function __toString() + { + return $this->asJson(); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphAchievement.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphAchievement.php new file mode 100644 index 0000000..3fba815 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphAchievement.php @@ -0,0 +1,113 @@ + '\Facebook\GraphNodes\GraphUser', + 'application' => '\Facebook\GraphNodes\GraphApplication', + ]; + + /** + * Returns the ID for the achievement. + * + * @return string|null + */ + public function getId() + { + return $this->getField('id'); + } + + /** + * Returns the user who achieved this. + * + * @return GraphUser|null + */ + public function getFrom() + { + return $this->getField('from'); + } + + /** + * Returns the time at which this was achieved. + * + * @return \DateTime|null + */ + public function getPublishTime() + { + return $this->getField('publish_time'); + } + + /** + * Returns the app in which the user achieved this. + * + * @return GraphApplication|null + */ + public function getApplication() + { + return $this->getField('application'); + } + + /** + * Returns information about the achievement type this instance is connected with. + * + * @return array|null + */ + public function getData() + { + return $this->getField('data'); + } + + /** + * Returns the type of achievement. + * + * @see https://developers.facebook.com/docs/graph-api/reference/v2.2/achievement + * + * @return string + */ + public function getType() + { + return 'game.achievement'; + } + + /** + * Indicates whether gaining the achievement published a feed story for the user. + * + * @return boolean|null + */ + public function isNoFeedStory() + { + return $this->getField('no_feed_story'); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphAlbum.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphAlbum.php new file mode 100644 index 0000000..50d1f2c --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphAlbum.php @@ -0,0 +1,183 @@ + '\Facebook\GraphNodes\GraphUser', + 'place' => '\Facebook\GraphNodes\GraphPage', + ]; + + /** + * Returns the ID for the album. + * + * @return string|null + */ + public function getId() + { + return $this->getField('id'); + } + + /** + * Returns whether the viewer can upload photos to this album. + * + * @return boolean|null + */ + public function getCanUpload() + { + return $this->getField('can_upload'); + } + + /** + * Returns the number of photos in this album. + * + * @return int|null + */ + public function getCount() + { + return $this->getField('count'); + } + + /** + * Returns the ID of the album's cover photo. + * + * @return string|null + */ + public function getCoverPhoto() + { + return $this->getField('cover_photo'); + } + + /** + * Returns the time the album was initially created. + * + * @return \DateTime|null + */ + public function getCreatedTime() + { + return $this->getField('created_time'); + } + + /** + * Returns the time the album was updated. + * + * @return \DateTime|null + */ + public function getUpdatedTime() + { + return $this->getField('updated_time'); + } + + /** + * Returns the description of the album. + * + * @return string|null + */ + public function getDescription() + { + return $this->getField('description'); + } + + /** + * Returns profile that created the album. + * + * @return GraphUser|null + */ + public function getFrom() + { + return $this->getField('from'); + } + + /** + * Returns profile that created the album. + * + * @return GraphPage|null + */ + public function getPlace() + { + return $this->getField('place'); + } + + /** + * Returns a link to this album on Facebook. + * + * @return string|null + */ + public function getLink() + { + return $this->getField('link'); + } + + /** + * Returns the textual location of the album. + * + * @return string|null + */ + public function getLocation() + { + return $this->getField('location'); + } + + /** + * Returns the title of the album. + * + * @return string|null + */ + public function getName() + { + return $this->getField('name'); + } + + /** + * Returns the privacy settings for the album. + * + * @return string|null + */ + public function getPrivacy() + { + return $this->getField('privacy'); + } + + /** + * Returns the type of the album. + * + * enum{ profile, mobile, wall, normal, album } + * + * @return string|null + */ + public function getType() + { + return $this->getField('type'); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphApplication.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphApplication.php new file mode 100644 index 0000000..69b09bb --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphApplication.php @@ -0,0 +1,43 @@ +getField('id'); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphCoverPhoto.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphCoverPhoto.php new file mode 100644 index 0000000..ee60750 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphCoverPhoto.php @@ -0,0 +1,72 @@ +getField('id'); + } + + /** + * Returns the source of cover if it exists + * + * @return string|null + */ + public function getSource() + { + return $this->getField('source'); + } + + /** + * Returns the offset_x of cover if it exists + * + * @return int|null + */ + public function getOffsetX() + { + return $this->getField('offset_x'); + } + + /** + * Returns the offset_y of cover if it exists + * + * @return int|null + */ + public function getOffsetY() + { + return $this->getField('offset_y'); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphEdge.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphEdge.php new file mode 100644 index 0000000..95f3284 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphEdge.php @@ -0,0 +1,260 @@ +request = $request; + $this->metaData = $metaData; + $this->parentEdgeEndpoint = $parentEdgeEndpoint; + $this->subclassName = $subclassName; + + parent::__construct($data); + } + + /** + * Gets the parent Graph edge endpoint that generated the list. + * + * @return string|null + */ + public function getParentGraphEdge() + { + return $this->parentEdgeEndpoint; + } + + /** + * Gets the subclass name that the child GraphNode's are cast as. + * + * @return string|null + */ + public function getSubClassName() + { + return $this->subclassName; + } + + /** + * Returns the raw meta data associated with this GraphEdge. + * + * @return array + */ + public function getMetaData() + { + return $this->metaData; + } + + /** + * Returns the next cursor if it exists. + * + * @return string|null + */ + public function getNextCursor() + { + return $this->getCursor('after'); + } + + /** + * Returns the previous cursor if it exists. + * + * @return string|null + */ + public function getPreviousCursor() + { + return $this->getCursor('before'); + } + + /** + * Returns the cursor for a specific direction if it exists. + * + * @param string $direction The direction of the page: after|before + * + * @return string|null + */ + public function getCursor($direction) + { + if (isset($this->metaData['paging']['cursors'][$direction])) { + return $this->metaData['paging']['cursors'][$direction]; + } + + return null; + } + + /** + * Generates a pagination URL based on a cursor. + * + * @param string $direction The direction of the page: next|previous + * + * @return string|null + * + * @throws FacebookSDKException + */ + public function getPaginationUrl($direction) + { + $this->validateForPagination(); + + // Do we have a paging URL? + if (isset($this->metaData['paging'][$direction])) { + // Graph returns the full URL with all the original params. + // We just want the endpoint though. + $pageUrl = $this->metaData['paging'][$direction]; + + return FacebookUrlManipulator::baseGraphUrlEndpoint($pageUrl); + } + + // Do we have a cursor to work with? + $cursorDirection = $direction === 'next' ? 'after' : 'before'; + $cursor = $this->getCursor($cursorDirection); + if (!$cursor) { + return null; + } + + // If we don't know the ID of the parent node, this ain't gonna work. + if (!$this->parentEdgeEndpoint) { + return null; + } + + // We have the parent node ID, paging cursor & original request. + // These were the ingredients chosen to create the perfect little URL. + $pageUrl = $this->parentEdgeEndpoint . '?' . $cursorDirection . '=' . urlencode($cursor); + + // Pull in the original params + $originalUrl = $this->request->getUrl(); + $pageUrl = FacebookUrlManipulator::mergeUrlParams($originalUrl, $pageUrl); + + return FacebookUrlManipulator::forceSlashPrefix($pageUrl); + } + + /** + * Validates whether or not we can paginate on this request. + * + * @throws FacebookSDKException + */ + public function validateForPagination() + { + if ($this->request->getMethod() !== 'GET') { + throw new FacebookSDKException('You can only paginate on a GET request.', 720); + } + } + + /** + * Gets the request object needed to make a next|previous page request. + * + * @param string $direction The direction of the page: next|previous + * + * @return FacebookRequest|null + * + * @throws FacebookSDKException + */ + public function getPaginationRequest($direction) + { + $pageUrl = $this->getPaginationUrl($direction); + if (!$pageUrl) { + return null; + } + + $newRequest = clone $this->request; + $newRequest->setEndpoint($pageUrl); + + return $newRequest; + } + + /** + * Gets the request object needed to make a "next" page request. + * + * @return FacebookRequest|null + * + * @throws FacebookSDKException + */ + public function getNextPageRequest() + { + return $this->getPaginationRequest('next'); + } + + /** + * Gets the request object needed to make a "previous" page request. + * + * @return FacebookRequest|null + * + * @throws FacebookSDKException + */ + public function getPreviousPageRequest() + { + return $this->getPaginationRequest('previous'); + } + + /** + * The total number of results according to Graph if it exists. + * + * This will be returned if the summary=true modifier is present in the request. + * + * @return int|null + */ + public function getTotalCount() + { + if (isset($this->metaData['summary']['total_count'])) { + return $this->metaData['summary']['total_count']; + } + + return null; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphEvent.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphEvent.php new file mode 100644 index 0000000..19ff2fb --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphEvent.php @@ -0,0 +1,242 @@ + '\Facebook\GraphNodes\GraphCoverPhoto', + 'place' => '\Facebook\GraphNodes\GraphPage', + 'picture' => '\Facebook\GraphNodes\GraphPicture', + 'parent_group' => '\Facebook\GraphNodes\GraphGroup', + ]; + + /** + * Returns the `id` (The event ID) as string if present. + * + * @return string|null + */ + public function getId() + { + return $this->getField('id'); + } + + /** + * Returns the `cover` (Cover picture) as GraphCoverPhoto if present. + * + * @return GraphCoverPhoto|null + */ + public function getCover() + { + return $this->getField('cover'); + } + + /** + * Returns the `description` (Long-form description) as string if present. + * + * @return string|null + */ + public function getDescription() + { + return $this->getField('description'); + } + + /** + * Returns the `end_time` (End time, if one has been set) as DateTime if present. + * + * @return \DateTime|null + */ + public function getEndTime() + { + return $this->getField('end_time'); + } + + /** + * Returns the `is_date_only` (Whether the event only has a date specified, but no time) as bool if present. + * + * @return bool|null + */ + public function getIsDateOnly() + { + return $this->getField('is_date_only'); + } + + /** + * Returns the `name` (Event name) as string if present. + * + * @return string|null + */ + public function getName() + { + return $this->getField('name'); + } + + /** + * Returns the `owner` (The profile that created the event) as GraphNode if present. + * + * @return GraphNode|null + */ + public function getOwner() + { + return $this->getField('owner'); + } + + /** + * Returns the `parent_group` (The group the event belongs to) as GraphGroup if present. + * + * @return GraphGroup|null + */ + public function getParentGroup() + { + return $this->getField('parent_group'); + } + + /** + * Returns the `place` (Event Place information) as GraphPage if present. + * + * @return GraphPage|null + */ + public function getPlace() + { + return $this->getField('place'); + } + + /** + * Returns the `privacy` (Who can see the event) as string if present. + * + * @return string|null + */ + public function getPrivacy() + { + return $this->getField('privacy'); + } + + /** + * Returns the `start_time` (Start time) as DateTime if present. + * + * @return \DateTime|null + */ + public function getStartTime() + { + return $this->getField('start_time'); + } + + /** + * Returns the `ticket_uri` (The link users can visit to buy a ticket to this event) as string if present. + * + * @return string|null + */ + public function getTicketUri() + { + return $this->getField('ticket_uri'); + } + + /** + * Returns the `timezone` (Timezone) as string if present. + * + * @return string|null + */ + public function getTimezone() + { + return $this->getField('timezone'); + } + + /** + * Returns the `updated_time` (Last update time) as DateTime if present. + * + * @return \DateTime|null + */ + public function getUpdatedTime() + { + return $this->getField('updated_time'); + } + + /** + * Returns the `picture` (Event picture) as GraphPicture if present. + * + * @return GraphPicture|null + */ + public function getPicture() + { + return $this->getField('picture'); + } + + /** + * Returns the `attending_count` (Number of people attending the event) as int if present. + * + * @return int|null + */ + public function getAttendingCount() + { + return $this->getField('attending_count'); + } + + /** + * Returns the `declined_count` (Number of people who declined the event) as int if present. + * + * @return int|null + */ + public function getDeclinedCount() + { + return $this->getField('declined_count'); + } + + /** + * Returns the `maybe_count` (Number of people who maybe going to the event) as int if present. + * + * @return int|null + */ + public function getMaybeCount() + { + return $this->getField('maybe_count'); + } + + /** + * Returns the `noreply_count` (Number of people who did not reply to the event) as int if present. + * + * @return int|null + */ + public function getNoreplyCount() + { + return $this->getField('noreply_count'); + } + + /** + * Returns the `invited_count` (Number of people invited to the event) as int if present. + * + * @return int|null + */ + public function getInvitedCount() + { + return $this->getField('invited_count'); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphGroup.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphGroup.php new file mode 100644 index 0000000..07a4dbd --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphGroup.php @@ -0,0 +1,171 @@ + '\Facebook\GraphNodes\GraphCoverPhoto', + 'venue' => '\Facebook\GraphNodes\GraphLocation', + ]; + + /** + * Returns the `id` (The Group ID) as string if present. + * + * @return string|null + */ + public function getId() + { + return $this->getField('id'); + } + + /** + * Returns the `cover` (The cover photo of the Group) as GraphCoverPhoto if present. + * + * @return GraphCoverPhoto|null + */ + public function getCover() + { + return $this->getField('cover'); + } + + /** + * Returns the `description` (A brief description of the Group) as string if present. + * + * @return string|null + */ + public function getDescription() + { + return $this->getField('description'); + } + + /** + * Returns the `email` (The email address to upload content to the Group. Only current members of the Group can use this) as string if present. + * + * @return string|null + */ + public function getEmail() + { + return $this->getField('email'); + } + + /** + * Returns the `icon` (The URL for the Group's icon) as string if present. + * + * @return string|null + */ + public function getIcon() + { + return $this->getField('icon'); + } + + /** + * Returns the `link` (The Group's website) as string if present. + * + * @return string|null + */ + public function getLink() + { + return $this->getField('link'); + } + + /** + * Returns the `name` (The name of the Group) as string if present. + * + * @return string|null + */ + public function getName() + { + return $this->getField('name'); + } + + /** + * Returns the `member_request_count` (Number of people asking to join the group.) as int if present. + * + * @return int|null + */ + public function getMemberRequestCount() + { + return $this->getField('member_request_count'); + } + + /** + * Returns the `owner` (The profile that created this Group) as GraphNode if present. + * + * @return GraphNode|null + */ + public function getOwner() + { + return $this->getField('owner'); + } + + /** + * Returns the `parent` (The parent Group of this Group, if it exists) as GraphNode if present. + * + * @return GraphNode|null + */ + public function getParent() + { + return $this->getField('parent'); + } + + /** + * Returns the `privacy` (The privacy setting of the Group) as string if present. + * + * @return string|null + */ + public function getPrivacy() + { + return $this->getField('privacy'); + } + + /** + * Returns the `updated_time` (The last time the Group was updated (this includes changes in the Group's properties and changes in posts and comments if user can see them)) as \DateTime if present. + * + * @return \DateTime|null + */ + public function getUpdatedTime() + { + return $this->getField('updated_time'); + } + + /** + * Returns the `venue` (The location for the Group) as GraphLocation if present. + * + * @return GraphLocation|null + */ + public function getVenue() + { + return $this->getField('venue'); + } + +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphList.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphList.php new file mode 100644 index 0000000..a60a07a --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphList.php @@ -0,0 +1,36 @@ +getField('street'); + } + + /** + * Returns the city component of the location + * + * @return string|null + */ + public function getCity() + { + return $this->getField('city'); + } + + /** + * Returns the state component of the location + * + * @return string|null + */ + public function getState() + { + return $this->getField('state'); + } + + /** + * Returns the country component of the location + * + * @return string|null + */ + public function getCountry() + { + return $this->getField('country'); + } + + /** + * Returns the zipcode component of the location + * + * @return string|null + */ + public function getZip() + { + return $this->getField('zip'); + } + + /** + * Returns the latitude component of the location + * + * @return float|null + */ + public function getLatitude() + { + return $this->getField('latitude'); + } + + /** + * Returns the street component of the location + * + * @return float|null + */ + public function getLongitude() + { + return $this->getField('longitude'); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphNode.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphNode.php new file mode 100644 index 0000000..0d2f504 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphNode.php @@ -0,0 +1,185 @@ +castItems($data)); + } + + /** + * Iterates over an array and detects the types each node + * should be cast to and returns all the items as an array. + * + * @TODO Add auto-casting to AccessToken entities. + * + * @param array $data The array to iterate over. + * + * @return array + */ + public function castItems(array $data) + { + $items = []; + + foreach ($data as $k => $v) { + if ($this->shouldCastAsDateTime($k) + && (is_numeric($v) + || $k === 'birthday' + || $this->isIso8601DateString($v)) + ) { + $items[$k] = $this->castToDateTime($v); + } else { + $items[$k] = $v; + } + } + + return $items; + } + + /** + * Uncasts any auto-casted datatypes. + * Basically the reverse of castItems(). + * + * @return array + */ + public function uncastItems() + { + $items = $this->asArray(); + + return array_map(function ($v) { + if ($v instanceof \DateTime) { + return $v->format(\DateTime::ISO8601); + } + + return $v; + }, $items); + } + + /** + * Get the collection of items as JSON. + * + * @param int $options + * + * @return string + */ + public function asJson($options = 0) + { + return json_encode($this->uncastItems(), $options); + } + + /** + * Detects an ISO 8601 formatted string. + * + * @param string $string + * + * @return boolean + * + * @see https://developers.facebook.com/docs/graph-api/using-graph-api/#readmodifiers + * @see http://www.cl.cam.ac.uk/~mgk25/iso-time.html + * @see http://en.wikipedia.org/wiki/ISO_8601 + */ + public function isIso8601DateString($string) + { + // This insane regex was yoinked from here: + // http://www.pelagodesign.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/ + // ...and I'm all like: + // http://thecodinglove.com/post/95378251969/when-code-works-and-i-dont-know-why + $crazyInsaneRegexThatSomehowDetectsIso8601 = '/^([\+-]?\d{4}(?!\d{2}\b))' + . '((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?' + . '|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d' + . '|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])' + . '((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d' + . '([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/'; + + return preg_match($crazyInsaneRegexThatSomehowDetectsIso8601, $string) === 1; + } + + /** + * Determines if a value from Graph should be cast to DateTime. + * + * @param string $key + * + * @return boolean + */ + public function shouldCastAsDateTime($key) + { + return in_array($key, [ + 'created_time', + 'updated_time', + 'start_time', + 'end_time', + 'backdated_time', + 'issued_at', + 'expires_at', + 'birthday', + 'publish_time' + ], true); + } + + /** + * Casts a date value from Graph to DateTime. + * + * @param int|string $value + * + * @return \DateTime + */ + public function castToDateTime($value) + { + if (is_int($value)) { + $dt = new \DateTime(); + $dt->setTimestamp($value); + } else { + $dt = new \DateTime($value); + } + + return $dt; + } + + /** + * Getter for $graphObjectMap. + * + * @return array + */ + public static function getObjectMap() + { + return static::$graphObjectMap; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphNodeFactory.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphNodeFactory.php new file mode 100644 index 0000000..e1bedd9 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphNodeFactory.php @@ -0,0 +1,392 @@ +response = $response; + $this->decodedBody = $response->getDecodedBody(); + } + + /** + * Tries to convert a FacebookResponse entity into a GraphNode. + * + * @param string|null $subclassName The GraphNode sub class to cast to. + * + * @return GraphNode + * + * @throws FacebookSDKException + */ + public function makeGraphNode($subclassName = null) + { + $this->validateResponseAsArray(); + $this->validateResponseCastableAsGraphNode(); + + return $this->castAsGraphNodeOrGraphEdge($this->decodedBody, $subclassName); + } + + /** + * Convenience method for creating a GraphAchievement collection. + * + * @return GraphAchievement + * + * @throws FacebookSDKException + */ + public function makeGraphAchievement() + { + return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphAchievement'); + } + + /** + * Convenience method for creating a GraphAlbum collection. + * + * @return GraphAlbum + * + * @throws FacebookSDKException + */ + public function makeGraphAlbum() + { + return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphAlbum'); + } + + /** + * Convenience method for creating a GraphPage collection. + * + * @return GraphPage + * + * @throws FacebookSDKException + */ + public function makeGraphPage() + { + return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphPage'); + } + + /** + * Convenience method for creating a GraphSessionInfo collection. + * + * @return GraphSessionInfo + * + * @throws FacebookSDKException + */ + public function makeGraphSessionInfo() + { + return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphSessionInfo'); + } + + /** + * Convenience method for creating a GraphUser collection. + * + * @return GraphUser + * + * @throws FacebookSDKException + */ + public function makeGraphUser() + { + return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphUser'); + } + + /** + * Convenience method for creating a GraphEvent collection. + * + * @return GraphEvent + * + * @throws FacebookSDKException + */ + public function makeGraphEvent() + { + return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphEvent'); + } + + /** + * Convenience method for creating a GraphGroup collection. + * + * @return GraphGroup + * + * @throws FacebookSDKException + */ + public function makeGraphGroup() + { + return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphGroup'); + } + + /** + * Tries to convert a FacebookResponse entity into a GraphEdge. + * + * @param string|null $subclassName The GraphNode sub class to cast the list items to. + * @param boolean $auto_prefix Toggle to auto-prefix the subclass name. + * + * @return GraphEdge + * + * @throws FacebookSDKException + */ + public function makeGraphEdge($subclassName = null, $auto_prefix = true) + { + $this->validateResponseAsArray(); + $this->validateResponseCastableAsGraphEdge(); + + if ($subclassName && $auto_prefix) { + $subclassName = static::BASE_GRAPH_OBJECT_PREFIX . $subclassName; + } + + return $this->castAsGraphNodeOrGraphEdge($this->decodedBody, $subclassName); + } + + /** + * Validates the decoded body. + * + * @throws FacebookSDKException + */ + public function validateResponseAsArray() + { + if (!is_array($this->decodedBody)) { + throw new FacebookSDKException('Unable to get response from Graph as array.', 620); + } + } + + /** + * Validates that the return data can be cast as a GraphNode. + * + * @throws FacebookSDKException + */ + public function validateResponseCastableAsGraphNode() + { + if (isset($this->decodedBody['data']) && static::isCastableAsGraphEdge($this->decodedBody['data'])) { + throw new FacebookSDKException( + 'Unable to convert response from Graph to a GraphNode because the response looks like a GraphEdge. Try using GraphNodeFactory::makeGraphEdge() instead.', + 620 + ); + } + } + + /** + * Validates that the return data can be cast as a GraphEdge. + * + * @throws FacebookSDKException + */ + public function validateResponseCastableAsGraphEdge() + { + if (!(isset($this->decodedBody['data']) && static::isCastableAsGraphEdge($this->decodedBody['data']))) { + throw new FacebookSDKException( + 'Unable to convert response from Graph to a GraphEdge because the response does not look like a GraphEdge. Try using GraphNodeFactory::makeGraphNode() instead.', + 620 + ); + } + } + + /** + * Safely instantiates a GraphNode of $subclassName. + * + * @param array $data The array of data to iterate over. + * @param string|null $subclassName The subclass to cast this collection to. + * + * @return GraphNode + * + * @throws FacebookSDKException + */ + public function safelyMakeGraphNode(array $data, $subclassName = null) + { + $subclassName = $subclassName ?: static::BASE_GRAPH_NODE_CLASS; + static::validateSubclass($subclassName); + + // Remember the parent node ID + $parentNodeId = isset($data['id']) ? $data['id'] : null; + + $items = []; + + foreach ($data as $k => $v) { + // Array means could be recurable + if (is_array($v)) { + // Detect any smart-casting from the $graphObjectMap array. + // This is always empty on the GraphNode collection, but subclasses can define + // their own array of smart-casting types. + $graphObjectMap = $subclassName::getObjectMap(); + $objectSubClass = isset($graphObjectMap[$k]) + ? $graphObjectMap[$k] + : null; + + // Could be a GraphEdge or GraphNode + $items[$k] = $this->castAsGraphNodeOrGraphEdge($v, $objectSubClass, $k, $parentNodeId); + } else { + $items[$k] = $v; + } + } + + return new $subclassName($items); + } + + /** + * Takes an array of values and determines how to cast each node. + * + * @param array $data The array of data to iterate over. + * @param string|null $subclassName The subclass to cast this collection to. + * @param string|null $parentKey The key of this data (Graph edge). + * @param string|null $parentNodeId The parent Graph node ID. + * + * @return GraphNode|GraphEdge + * + * @throws FacebookSDKException + */ + public function castAsGraphNodeOrGraphEdge(array $data, $subclassName = null, $parentKey = null, $parentNodeId = null) + { + if (isset($data['data'])) { + // Create GraphEdge + if (static::isCastableAsGraphEdge($data['data'])) { + return $this->safelyMakeGraphEdge($data, $subclassName, $parentKey, $parentNodeId); + } + // Sometimes Graph is a weirdo and returns a GraphNode under the "data" key + $data = $data['data']; + } + + // Create GraphNode + return $this->safelyMakeGraphNode($data, $subclassName); + } + + /** + * Return an array of GraphNode's. + * + * @param array $data The array of data to iterate over. + * @param string|null $subclassName The GraphNode subclass to cast each item in the list to. + * @param string|null $parentKey The key of this data (Graph edge). + * @param string|null $parentNodeId The parent Graph node ID. + * + * @return GraphEdge + * + * @throws FacebookSDKException + */ + public function safelyMakeGraphEdge(array $data, $subclassName = null, $parentKey = null, $parentNodeId = null) + { + if (!isset($data['data'])) { + throw new FacebookSDKException('Cannot cast data to GraphEdge. Expected a "data" key.', 620); + } + + $dataList = []; + foreach ($data['data'] as $graphNode) { + $dataList[] = $this->safelyMakeGraphNode($graphNode, $subclassName, $parentKey, $parentNodeId); + } + + $metaData = $this->getMetaData($data); + + // We'll need to make an edge endpoint for this in case it's a GraphEdge (for cursor pagination) + $parentGraphEdgeEndpoint = $parentNodeId && $parentKey ? '/' . $parentNodeId . '/' . $parentKey : null; + $className = static::BASE_GRAPH_EDGE_CLASS; + + return new $className($this->response->getRequest(), $dataList, $metaData, $parentGraphEdgeEndpoint, $subclassName); + } + + /** + * Get the meta data from a list in a Graph response. + * + * @param array $data The Graph response. + * + * @return array + */ + public function getMetaData(array $data) + { + unset($data['data']); + + return $data; + } + + /** + * Determines whether or not the data should be cast as a GraphEdge. + * + * @param array $data + * + * @return boolean + */ + public static function isCastableAsGraphEdge(array $data) + { + if ($data === []) { + return true; + } + + // Checks for a sequential numeric array which would be a GraphEdge + return array_keys($data) === range(0, count($data) - 1); + } + + /** + * Ensures that the subclass in question is valid. + * + * @param string $subclassName The GraphNode subclass to validate. + * + * @throws FacebookSDKException + */ + public static function validateSubclass($subclassName) + { + if ($subclassName == static::BASE_GRAPH_NODE_CLASS || is_subclass_of($subclassName, static::BASE_GRAPH_NODE_CLASS)) { + return; + } + + throw new FacebookSDKException('The given subclass "' . $subclassName . '" is not valid. Cannot cast to an object that is not a GraphNode subclass.', 620); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphObject.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphObject.php new file mode 100644 index 0000000..bb8f8e4 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphObject.php @@ -0,0 +1,36 @@ +makeGraphNode($subclassName); + } + + /** + * Convenience method for creating a GraphEvent collection. + * + * @return GraphEvent + * + * @throws FacebookSDKException + */ + public function makeGraphEvent() + { + return $this->makeGraphObject(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphEvent'); + } + + /** + * Tries to convert a FacebookResponse entity into a GraphEdge. + * + * @param string|null $subclassName The GraphNode sub class to cast the list items to. + * @param boolean $auto_prefix Toggle to auto-prefix the subclass name. + * + * @return GraphEdge + * + * @deprecated 5.0.0 GraphObjectFactory has been renamed to GraphNodeFactory + */ + public function makeGraphList($subclassName = null, $auto_prefix = true) + { + return $this->makeGraphEdge($subclassName, $auto_prefix); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphPage.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphPage.php new file mode 100644 index 0000000..ab8e31a --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphPage.php @@ -0,0 +1,125 @@ + '\Facebook\GraphNodes\GraphPage', + 'global_brand_parent_page' => '\Facebook\GraphNodes\GraphPage', + 'location' => '\Facebook\GraphNodes\GraphLocation', + ]; + + /** + * Returns the ID for the user's page as a string if present. + * + * @return string|null + */ + public function getId() + { + return $this->getField('id'); + } + + /** + * Returns the Category for the user's page as a string if present. + * + * @return string|null + */ + public function getCategory() + { + return $this->getField('category'); + } + + /** + * Returns the Name of the user's page as a string if present. + * + * @return string|null + */ + public function getName() + { + return $this->getField('name'); + } + + /** + * Returns the best available Page on Facebook. + * + * @return GraphPage|null + */ + public function getBestPage() + { + return $this->getField('best_page'); + } + + /** + * Returns the brand's global (parent) Page. + * + * @return GraphPage|null + */ + public function getGlobalBrandParentPage() + { + return $this->getField('global_brand_parent_page'); + } + + /** + * Returns the location of this place. + * + * @return GraphLocation|null + */ + public function getLocation() + { + return $this->getField('location'); + } + + /** + * Returns the page access token for the admin user. + * + * Only available in the `/me/accounts` context. + * + * @return string|null + */ + public function getAccessToken() + { + return $this->getField('access_token'); + } + + /** + * Returns the roles of the page admin user. + * + * Only available in the `/me/accounts` context. + * + * @return array|null + */ + public function getPerms() + { + return $this->getField('perms'); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphPicture.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphPicture.php new file mode 100644 index 0000000..bfd37fa --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphPicture.php @@ -0,0 +1,72 @@ +getField('is_silhouette'); + } + + /** + * Returns the url of user picture if it exists + * + * @return string|null + */ + public function getUrl() + { + return $this->getField('url'); + } + + /** + * Returns the width of user picture if it exists + * + * @return int|null + */ + public function getWidth() + { + return $this->getField('width'); + } + + /** + * Returns the height of user picture if it exists + * + * @return int|null + */ + public function getHeight() + { + return $this->getField('height'); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphSessionInfo.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphSessionInfo.php new file mode 100644 index 0000000..3c9e2ff --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphSessionInfo.php @@ -0,0 +1,102 @@ +getField('app_id'); + } + + /** + * Returns the application name the token was issued for. + * + * @return string|null + */ + public function getApplication() + { + return $this->getField('application'); + } + + /** + * Returns the date & time that the token expires. + * + * @return \DateTime|null + */ + public function getExpiresAt() + { + return $this->getField('expires_at'); + } + + /** + * Returns whether the token is valid. + * + * @return boolean + */ + public function getIsValid() + { + return $this->getField('is_valid'); + } + + /** + * Returns the date & time the token was issued at. + * + * @return \DateTime|null + */ + public function getIssuedAt() + { + return $this->getField('issued_at'); + } + + /** + * Returns the scope permissions associated with the token. + * + * @return array + */ + public function getScopes() + { + return $this->getField('scopes'); + } + + /** + * Returns the login id of the user associated with the token. + * + * @return string|null + */ + public function getUserId() + { + return $this->getField('user_id'); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphUser.php b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphUser.php new file mode 100644 index 0000000..cb9ddbb --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/GraphNodes/GraphUser.php @@ -0,0 +1,162 @@ + '\Facebook\GraphNodes\GraphPage', + 'location' => '\Facebook\GraphNodes\GraphPage', + 'significant_other' => '\Facebook\GraphNodes\GraphUser', + 'picture' => '\Facebook\GraphNodes\GraphPicture', + ]; + + /** + * Returns the ID for the user as a string if present. + * + * @return string|null + */ + public function getId() + { + return $this->getField('id'); + } + + /** + * Returns the name for the user as a string if present. + * + * @return string|null + */ + public function getName() + { + return $this->getField('name'); + } + + /** + * Returns the first name for the user as a string if present. + * + * @return string|null + */ + public function getFirstName() + { + return $this->getField('first_name'); + } + + /** + * Returns the middle name for the user as a string if present. + * + * @return string|null + */ + public function getMiddleName() + { + return $this->getField('middle_name'); + } + + /** + * Returns the last name for the user as a string if present. + * + * @return string|null + */ + public function getLastName() + { + return $this->getField('last_name'); + } + + /** + * Returns the gender for the user as a string if present. + * + * @return string|null + */ + public function getGender() + { + return $this->getField('gender'); + } + + /** + * Returns the Facebook URL for the user as a string if available. + * + * @return string|null + */ + public function getLink() + { + return $this->getField('link'); + } + + /** + * Returns the users birthday, if available. + * + * @return \DateTime|null + */ + public function getBirthday() + { + return $this->getField('birthday'); + } + + /** + * Returns the current location of the user as a GraphPage. + * + * @return GraphPage|null + */ + public function getLocation() + { + return $this->getField('location'); + } + + /** + * Returns the current location of the user as a GraphPage. + * + * @return GraphPage|null + */ + public function getHometown() + { + return $this->getField('hometown'); + } + + /** + * Returns the current location of the user as a GraphUser. + * + * @return GraphUser|null + */ + public function getSignificantOther() + { + return $this->getField('significant_other'); + } + + /** + * Returns the picture of the user as a GraphPicture + * + * @return GraphPicture|null + */ + public function getPicture() + { + return $this->getField('picture'); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookCanvasHelper.php b/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookCanvasHelper.php new file mode 100644 index 0000000..8068526 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookCanvasHelper.php @@ -0,0 +1,52 @@ +signedRequest ? $this->signedRequest->get('app_data') : null; + } + + /** + * Get raw signed request from POST. + * + * @return string|null + */ + public function getRawSignedRequest() + { + return $this->getRawSignedRequestFromPost() ?: null; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookJavaScriptHelper.php b/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookJavaScriptHelper.php new file mode 100644 index 0000000..5d406b5 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookJavaScriptHelper.php @@ -0,0 +1,42 @@ +getRawSignedRequestFromCookie(); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookPageTabHelper.php b/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookPageTabHelper.php new file mode 100644 index 0000000..ee43f5e --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookPageTabHelper.php @@ -0,0 +1,95 @@ +signedRequest) { + return; + } + + $this->pageData = $this->signedRequest->get('page'); + } + + /** + * Returns a value from the page data. + * + * @param string $key + * @param mixed|null $default + * + * @return mixed|null + */ + public function getPageData($key, $default = null) + { + if (isset($this->pageData[$key])) { + return $this->pageData[$key]; + } + + return $default; + } + + /** + * Returns true if the user is an admin. + * + * @return boolean + */ + public function isAdmin() + { + return $this->getPageData('admin') === true; + } + + /** + * Returns the page id if available. + * + * @return string|null + */ + public function getPageId() + { + return $this->getPageData('id'); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookRedirectLoginHelper.php b/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookRedirectLoginHelper.php new file mode 100644 index 0000000..144a5b4 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookRedirectLoginHelper.php @@ -0,0 +1,360 @@ +oAuth2Client = $oAuth2Client; + $this->persistentDataHandler = $persistentDataHandler ?: new FacebookSessionPersistentDataHandler(); + $this->urlDetectionHandler = $urlHandler ?: new FacebookUrlDetectionHandler(); + $this->pseudoRandomStringGenerator = $prsg ?: $this->detectPseudoRandomStringGenerator(); + } + + /** + * Returns the persistent data handler. + * + * @return PersistentDataInterface + */ + public function getPersistentDataHandler() + { + return $this->persistentDataHandler; + } + + /** + * Returns the URL detection handler. + * + * @return UrlDetectionInterface + */ + public function getUrlDetectionHandler() + { + return $this->urlDetectionHandler; + } + + /** + * Returns the cryptographically secure pseudo-random string generator. + * + * @return PseudoRandomStringGeneratorInterface + */ + public function getPseudoRandomStringGenerator() + { + return $this->pseudoRandomStringGenerator; + } + + /** + * Detects which pseudo-random string generator to use. + * + * @return PseudoRandomStringGeneratorInterface + * + * @throws FacebookSDKException + */ + public function detectPseudoRandomStringGenerator() + { + // Since openssl_random_pseudo_bytes() can sometimes return non-cryptographically + // secure pseudo-random strings (in rare cases), we check for mcrypt_create_iv() first. + if (function_exists('mcrypt_create_iv')) { + return new McryptPseudoRandomStringGenerator(); + } + + if (function_exists('openssl_random_pseudo_bytes')) { + return new OpenSslPseudoRandomStringGenerator(); + } + + if (!ini_get('open_basedir') && is_readable('/dev/urandom')) { + return new UrandomPseudoRandomStringGenerator(); + } + + throw new FacebookSDKException('Unable to detect a cryptographically secure pseudo-random string generator.'); + } + + /** + * Stores CSRF state and returns a URL to which the user should be sent to in order to continue the login process with Facebook. + * + * @param string $redirectUrl The URL Facebook should redirect users to after login. + * @param array $scope List of permissions to request during login. + * @param array $params An array of parameters to generate URL. + * @param string $separator The separator to use in http_build_query(). + * + * @return string + */ + private function makeUrl($redirectUrl, array $scope, array $params = [], $separator = '&') + { + $state = $this->pseudoRandomStringGenerator->getPseudoRandomString(static::CSRF_LENGTH); + $this->persistentDataHandler->set('state', $state); + + return $this->oAuth2Client->getAuthorizationUrl($redirectUrl, $state, $scope, $params, $separator); + } + + /** + * Returns the URL to send the user in order to login to Facebook. + * + * @param string $redirectUrl The URL Facebook should redirect users to after login. + * @param array $scope List of permissions to request during login. + * @param string $separator The separator to use in http_build_query(). + * + * @return string + */ + public function getLoginUrl($redirectUrl, array $scope = [], $separator = '&') + { + return $this->makeUrl($redirectUrl, $scope, [], $separator); + } + + /** + * Returns the URL to send the user in order to log out of Facebook. + * + * @param AccessToken|string $accessToken The access token that will be logged out. + * @param string $next The url Facebook should redirect the user to after a successful logout. + * @param string $separator The separator to use in http_build_query(). + * + * @return string + * + * @throws FacebookSDKException + */ + public function getLogoutUrl($accessToken, $next, $separator = '&') + { + if (!$accessToken instanceof AccessToken) { + $accessToken = new AccessToken($accessToken); + } + + if ($accessToken->isAppAccessToken()) { + throw new FacebookSDKException('Cannot generate a logout URL with an app access token.', 722); + } + + $params = [ + 'next' => $next, + 'access_token' => $accessToken->getValue(), + ]; + + return 'https://www.facebook.com/logout.php?' . http_build_query($params, null, $separator); + } + + /** + * Returns the URL to send the user in order to login to Facebook with permission(s) to be re-asked. + * + * @param string $redirectUrl The URL Facebook should redirect users to after login. + * @param array $scope List of permissions to request during login. + * @param string $separator The separator to use in http_build_query(). + * + * @return string + */ + public function getReRequestUrl($redirectUrl, array $scope = [], $separator = '&') + { + $params = ['auth_type' => 'rerequest']; + + return $this->makeUrl($redirectUrl, $scope, $params, $separator); + } + + /** + * Returns the URL to send the user in order to login to Facebook with user to be re-authenticated. + * + * @param string $redirectUrl The URL Facebook should redirect users to after login. + * @param array $scope List of permissions to request during login. + * @param string $separator The separator to use in http_build_query(). + * + * @return string + */ + public function getReAuthenticationUrl($redirectUrl, array $scope = [], $separator = '&') + { + $params = ['auth_type' => 'reauthenticate']; + + return $this->makeUrl($redirectUrl, $scope, $params, $separator); + } + + /** + * Takes a valid code from a login redirect, and returns an AccessToken entity. + * + * @param string|null $redirectUrl The redirect URL. + * + * @return AccessToken|null + * + * @throws FacebookSDKException + */ + public function getAccessToken($redirectUrl = null) + { + if (!$code = $this->getCode()) { + return null; + } + + $this->validateCsrf(); + + $redirectUrl = $redirectUrl ?: $this->urlDetectionHandler->getCurrentUrl(); + // At minimum we need to remove the state param + $redirectUrl = FacebookUrlManipulator::removeParamsFromUrl($redirectUrl, ['state']); + + return $this->oAuth2Client->getAccessTokenFromCode($code, $redirectUrl); + } + + /** + * Validate the request against a cross-site request forgery. + * + * @throws FacebookSDKException + */ + protected function validateCsrf() + { + $state = $this->getState(); + $savedState = $this->persistentDataHandler->get('state'); + + if (!$state || !$savedState) { + throw new FacebookSDKException('Cross-site request forgery validation failed. Required param "state" missing.'); + } + + $savedLen = strlen($savedState); + $givenLen = strlen($state); + + if ($savedLen !== $givenLen) { + throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.'); + } + + $result = 0; + for ($i = 0; $i < $savedLen; $i++) { + $result |= ord($state[$i]) ^ ord($savedState[$i]); + } + + if ($result !== 0) { + throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.'); + } + } + + /** + * Return the code. + * + * @return string|null + */ + protected function getCode() + { + return $this->getInput('code'); + } + + /** + * Return the state. + * + * @return string|null + */ + protected function getState() + { + return $this->getInput('state'); + } + + /** + * Return the error code. + * + * @return string|null + */ + public function getErrorCode() + { + return $this->getInput('error_code'); + } + + /** + * Returns the error. + * + * @return string|null + */ + public function getError() + { + return $this->getInput('error'); + } + + /** + * Returns the error reason. + * + * @return string|null + */ + public function getErrorReason() + { + return $this->getInput('error_reason'); + } + + /** + * Returns the error description. + * + * @return string|null + */ + public function getErrorDescription() + { + return $this->getInput('error_description'); + } + + /** + * Returns a value from a GET param. + * + * @param string $key + * + * @return string|null + */ + private function getInput($key) + { + return isset($_GET[$key]) ? $_GET[$key] : null; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookSignedRequestFromInputHelper.php b/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookSignedRequestFromInputHelper.php new file mode 100644 index 0000000..aafa246 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Helpers/FacebookSignedRequestFromInputHelper.php @@ -0,0 +1,166 @@ +app = $app; + $graphVersion = $graphVersion ?: Facebook::DEFAULT_GRAPH_VERSION; + $this->oAuth2Client = new OAuth2Client($this->app, $client, $graphVersion); + + $this->instantiateSignedRequest(); + } + + /** + * Instantiates a new SignedRequest entity. + * + * @param string|null + */ + public function instantiateSignedRequest($rawSignedRequest = null) + { + $rawSignedRequest = $rawSignedRequest ?: $this->getRawSignedRequest(); + + if (!$rawSignedRequest) { + return; + } + + $this->signedRequest = new SignedRequest($this->app, $rawSignedRequest); + } + + /** + * Returns an AccessToken entity from the signed request. + * + * @return AccessToken|null + * + * @throws \Facebook\Exceptions\FacebookSDKException + */ + public function getAccessToken() + { + if ($this->signedRequest && $this->signedRequest->hasOAuthData()) { + $code = $this->signedRequest->get('code'); + $accessToken = $this->signedRequest->get('oauth_token'); + + if ($code && !$accessToken) { + return $this->oAuth2Client->getAccessTokenFromCode($code); + } + + $expiresAt = $this->signedRequest->get('expires', 0); + + return new AccessToken($accessToken, $expiresAt); + } + + return null; + } + + /** + * Returns the SignedRequest entity. + * + * @return SignedRequest|null + */ + public function getSignedRequest() + { + return $this->signedRequest; + } + + /** + * Returns the user_id if available. + * + * @return string|null + */ + public function getUserId() + { + return $this->signedRequest ? $this->signedRequest->getUserId() : null; + } + + /** + * Get raw signed request from input. + * + * @return string|null + */ + abstract public function getRawSignedRequest(); + + /** + * Get raw signed request from POST input. + * + * @return string|null + */ + public function getRawSignedRequestFromPost() + { + if (isset($_POST['signed_request'])) { + return $_POST['signed_request']; + } + + return null; + } + + /** + * Get raw signed request from cookie set from the Javascript SDK. + * + * @return string|null + */ + public function getRawSignedRequestFromCookie() + { + if (isset($_COOKIE['fbsr_' . $this->app->getId()])) { + return $_COOKIE['fbsr_' . $this->app->getId()]; + } + + return null; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Http/GraphRawResponse.php b/lib/facebook-graph-sdk/src/Facebook/Http/GraphRawResponse.php new file mode 100644 index 0000000..583d303 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Http/GraphRawResponse.php @@ -0,0 +1,137 @@ +httpResponseCode = (int)$httpStatusCode; + } + + if (is_array($headers)) { + $this->headers = $headers; + } else { + $this->setHeadersFromString($headers); + } + + $this->body = $body; + } + + /** + * Return the response headers. + * + * @return array + */ + public function getHeaders() + { + return $this->headers; + } + + /** + * Return the body of the response. + * + * @return string + */ + public function getBody() + { + return $this->body; + } + + /** + * Return the HTTP response code. + * + * @return int + */ + public function getHttpResponseCode() + { + return $this->httpResponseCode; + } + + /** + * Sets the HTTP response code from a raw header. + * + * @param string $rawResponseHeader + */ + public function setHttpResponseCodeFromHeader($rawResponseHeader) + { + preg_match('|HTTP/\d\.\d\s+(\d+)\s+.*|', $rawResponseHeader, $match); + $this->httpResponseCode = (int)$match[1]; + } + + /** + * Parse the raw headers and set as an array. + * + * @param string $rawHeaders The raw headers from the response. + */ + protected function setHeadersFromString($rawHeaders) + { + // Normalize line breaks + $rawHeaders = str_replace("\r\n", "\n", $rawHeaders); + + // There will be multiple headers if a 301 was followed + // or a proxy was followed, etc + $headerCollection = explode("\n\n", trim($rawHeaders)); + // We just want the last response (at the end) + $rawHeader = array_pop($headerCollection); + + $headerComponents = explode("\n", $rawHeader); + foreach ($headerComponents as $line) { + if (strpos($line, ': ') === false) { + $this->setHttpResponseCodeFromHeader($line); + } else { + list($key, $value) = explode(': ', $line); + $this->headers[$key] = $value; + } + } + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Http/RequestBodyInterface.php b/lib/facebook-graph-sdk/src/Facebook/Http/RequestBodyInterface.php new file mode 100644 index 0000000..97e0a2e --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Http/RequestBodyInterface.php @@ -0,0 +1,39 @@ +params = $params; + $this->files = $files; + $this->boundary = $boundary ?: uniqid(); + } + + /** + * @inheritdoc + */ + public function getBody() + { + $body = ''; + + // Compile normal params + $params = $this->getNestedParams($this->params); + foreach ($params as $k => $v) { + $body .= $this->getParamString($k, $v); + } + + // Compile files + foreach ($this->files as $k => $v) { + $body .= $this->getFileString($k, $v); + } + + // Peace out + $body .= "--{$this->boundary}--\r\n"; + + return $body; + } + + /** + * Get the boundary + * + * @return string + */ + public function getBoundary() + { + return $this->boundary; + } + + /** + * Get the string needed to transfer a file. + * + * @param string $name + * @param FacebookFile $file + * + * @return string + */ + private function getFileString($name, FacebookFile $file) + { + return sprintf( + "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"%s\r\n\r\n%s\r\n", + $this->boundary, + $name, + $file->getFileName(), + $this->getFileHeaders($file), + $file->getContents() + ); + } + + /** + * Get the string needed to transfer a POST field. + * + * @param string $name + * @param string $value + * + * @return string + */ + private function getParamString($name, $value) + { + return sprintf( + "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n", + $this->boundary, + $name, + $value + ); + } + + /** + * Returns the params as an array of nested params. + * + * @param array $params + * + * @return array + */ + private function getNestedParams(array $params) + { + $query = http_build_query($params, null, '&'); + $params = explode('&', $query); + $result = []; + + foreach ($params as $param) { + list($key, $value) = explode('=', $param, 2); + $result[urldecode($key)] = urldecode($value); + } + + return $result; + } + + /** + * Get the headers needed before transferring the content of a POST file. + * + * @param FacebookFile $file + * + * @return string + */ + protected function getFileHeaders(FacebookFile $file) + { + return "\r\nContent-Type: {$file->getMimetype()}"; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Http/RequestBodyUrlEncoded.php b/lib/facebook-graph-sdk/src/Facebook/Http/RequestBodyUrlEncoded.php new file mode 100644 index 0000000..77c2b64 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Http/RequestBodyUrlEncoded.php @@ -0,0 +1,55 @@ +params = $params; + } + + /** + * @inheritdoc + */ + public function getBody() + { + return http_build_query($this->params, null, '&'); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookCurl.php b/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookCurl.php new file mode 100644 index 0000000..e5d124a --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookCurl.php @@ -0,0 +1,129 @@ +curl = curl_init(); + } + + /** + * Set a curl option + * + * @param $key + * @param $value + */ + public function setopt($key, $value) + { + curl_setopt($this->curl, $key, $value); + } + + /** + * Set an array of options to a curl resource + * + * @param array $options + */ + public function setoptArray(array $options) + { + curl_setopt_array($this->curl, $options); + } + + /** + * Send a curl request + * + * @return mixed + */ + public function exec() + { + return curl_exec($this->curl); + } + + /** + * Return the curl error number + * + * @return int + */ + public function errno() + { + return curl_errno($this->curl); + } + + /** + * Return the curl error message + * + * @return string + */ + public function error() + { + return curl_error($this->curl); + } + + /** + * Get info from a curl reference + * + * @param $type + * + * @return mixed + */ + public function getinfo($type) + { + return curl_getinfo($this->curl, $type); + } + + /** + * Get the currently installed curl version + * + * @return array + */ + public function version() + { + return curl_version(); + } + + /** + * Close the resource connection to curl + */ + public function close() + { + curl_close($this->curl); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookCurlHttpClient.php b/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookCurlHttpClient.php new file mode 100644 index 0000000..955ac06 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookCurlHttpClient.php @@ -0,0 +1,210 @@ +facebookCurl = $facebookCurl ?: new FacebookCurl(); + } + + /** + * @inheritdoc + */ + public function send($url, $method, $body, array $headers, $timeOut) + { + $this->openConnection($url, $method, $body, $headers, $timeOut); + $this->sendRequest(); + + if ($curlErrorCode = $this->facebookCurl->errno()) { + throw new FacebookSDKException($this->facebookCurl->error(), $curlErrorCode); + } + + // Separate the raw headers from the raw body + list($rawHeaders, $rawBody) = $this->extractResponseHeadersAndBody(); + + $this->closeConnection(); + + return new GraphRawResponse($rawHeaders, $rawBody); + } + + /** + * Opens a new curl connection. + * + * @param string $url The endpoint to send the request to. + * @param string $method The request method. + * @param string $body The body of the request. + * @param array $headers The request headers. + * @param int $timeOut The timeout in seconds for the request. + */ + public function openConnection($url, $method, $body, array $headers, $timeOut) + { + $options = [ + CURLOPT_CUSTOMREQUEST => $method, + CURLOPT_HTTPHEADER => $this->compileRequestHeaders($headers), + CURLOPT_URL => $url, + CURLOPT_CONNECTTIMEOUT => 10, + CURLOPT_TIMEOUT => $timeOut, + CURLOPT_RETURNTRANSFER => true, // Follow 301 redirects + CURLOPT_HEADER => true, // Enable header processing + CURLOPT_SSL_VERIFYHOST => 2, + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_CAINFO => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem', + ]; + + if ($method !== "GET") { + $options[CURLOPT_POSTFIELDS] = $body; + } + + $this->facebookCurl->init(); + $this->facebookCurl->setoptArray($options); + } + + /** + * Closes an existing curl connection + */ + public function closeConnection() + { + $this->facebookCurl->close(); + } + + /** + * Send the request and get the raw response from curl + */ + public function sendRequest() + { + $this->rawResponse = $this->facebookCurl->exec(); + } + + /** + * Compiles the request headers into a curl-friendly format. + * + * @param array $headers The request headers. + * + * @return array + */ + public function compileRequestHeaders(array $headers) + { + $return = []; + + foreach ($headers as $key => $value) { + $return[] = $key . ': ' . $value; + } + + return $return; + } + + /** + * Extracts the headers and the body into a two-part array + * + * @return array + */ + public function extractResponseHeadersAndBody() + { + $headerSize = $this->getHeaderSize(); + + $rawHeaders = mb_substr($this->rawResponse, 0, $headerSize); + $rawBody = mb_substr($this->rawResponse, $headerSize); + + return [trim($rawHeaders), trim($rawBody)]; + } + + /** + * Return proper header size + * + * @return integer + */ + private function getHeaderSize() + { + $headerSize = $this->facebookCurl->getinfo(CURLINFO_HEADER_SIZE); + // This corrects a Curl bug where header size does not account + // for additional Proxy headers. + if ($this->needsCurlProxyFix()) { + // Additional way to calculate the request body size. + if (preg_match('/Content-Length: (\d+)/', $this->rawResponse, $m)) { + $headerSize = mb_strlen($this->rawResponse) - $m[1]; + } elseif (stripos($this->rawResponse, self::CONNECTION_ESTABLISHED) !== false) { + $headerSize += mb_strlen(self::CONNECTION_ESTABLISHED); + } + } + + return $headerSize; + } + + /** + * Detect versions of Curl which report incorrect header lengths when + * using Proxies. + * + * @return boolean + */ + private function needsCurlProxyFix() + { + $ver = $this->facebookCurl->version(); + $version = $ver['version_number']; + + return $version < self::CURL_PROXY_QUIRK_VER; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookGuzzleHttpClient.php b/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookGuzzleHttpClient.php new file mode 100644 index 0000000..6f2a1c6 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookGuzzleHttpClient.php @@ -0,0 +1,97 @@ +guzzleClient = $guzzleClient ?: new Client(); + } + + /** + * @inheritdoc + */ + public function send($url, $method, $body, array $headers, $timeOut) + { + $options = [ + 'headers' => $headers, + 'body' => $body, + 'timeout' => $timeOut, + 'connect_timeout' => 10, + 'verify' => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem', + ]; + $request = $this->guzzleClient->createRequest($method, $url, $options); + + try { + $rawResponse = $this->guzzleClient->send($request); + } catch (RequestException $e) { + $rawResponse = $e->getResponse(); + + if ($e->getPrevious() instanceof RingException || !$rawResponse instanceof ResponseInterface) { + throw new FacebookSDKException($e->getMessage(), $e->getCode()); + } + } + + $rawHeaders = $this->getHeadersAsString($rawResponse); + $rawBody = $rawResponse->getBody(); + $httpStatusCode = $rawResponse->getStatusCode(); + + return new GraphRawResponse($rawHeaders, $rawBody, $httpStatusCode); + } + + /** + * Returns the Guzzle array of headers as a string. + * + * @param ResponseInterface $response The Guzzle response. + * + * @return string + */ + public function getHeadersAsString(ResponseInterface $response) + { + $headers = $response->getHeaders(); + $rawHeaders = []; + foreach ($headers as $name => $values) { + $rawHeaders[] = $name . ": " . implode(", ", $values); + } + + return implode("\r\n", $rawHeaders); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookHttpClientInterface.php b/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookHttpClientInterface.php new file mode 100644 index 0000000..0029bc0 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookHttpClientInterface.php @@ -0,0 +1,47 @@ +stream = stream_context_create($options); + } + + /** + * The response headers from the stream wrapper + * + * @return array|null + */ + public function getResponseHeaders() + { + return $this->responseHeaders; + } + + /** + * Send a stream wrapped request + * + * @param string $url + * + * @return mixed + */ + public function fileGetContents($url) + { + $rawResponse = file_get_contents($url, false, $this->stream); + $this->responseHeaders = $http_response_header; + + return $rawResponse; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookStreamHttpClient.php b/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookStreamHttpClient.php new file mode 100644 index 0000000..b157514 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/HttpClients/FacebookStreamHttpClient.php @@ -0,0 +1,94 @@ +facebookStream = $facebookStream ?: new FacebookStream(); + } + + /** + * @inheritdoc + */ + public function send($url, $method, $body, array $headers, $timeOut) + { + $options = [ + 'http' => [ + 'method' => $method, + 'header' => $this->compileHeader($headers), + 'content' => $body, + 'timeout' => $timeOut, + 'ignore_errors' => true + ], + 'ssl' => [ + 'verify_peer' => true, + 'verify_peer_name' => true, + 'allow_self_signed' => true, // All root certificates are self-signed + 'cafile' => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem', + ], + ]; + + $this->facebookStream->streamContextCreate($options); + $rawBody = $this->facebookStream->fileGetContents($url); + $rawHeaders = $this->facebookStream->getResponseHeaders(); + + if ($rawBody === false || !$rawHeaders) { + throw new FacebookSDKException('Stream returned an empty response', 660); + } + + $rawHeaders = implode("\r\n", $rawHeaders); + + return new GraphRawResponse($rawHeaders, $rawBody); + } + + /** + * Formats the headers for use in the stream wrapper. + * + * @param array $headers The request headers. + * + * @return string + */ + public function compileHeader(array $headers) + { + $header = []; + foreach ($headers as $k => $v) { + $header[] = $k . ': ' . $v; + } + + return implode("\r\n", $header); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/HttpClients/certs/DigiCertHighAssuranceEVRootCA.pem b/lib/facebook-graph-sdk/src/Facebook/HttpClients/certs/DigiCertHighAssuranceEVRootCA.pem new file mode 100644 index 0000000..9e6810a --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/HttpClients/certs/DigiCertHighAssuranceEVRootCA.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm ++9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW +PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM +xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB +Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 +hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg +EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA +FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec +nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z +eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF +hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 +Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe +vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep ++OkuE6N36B9K +-----END CERTIFICATE----- diff --git a/lib/facebook-graph-sdk/src/Facebook/PersistentData/FacebookMemoryPersistentDataHandler.php b/lib/facebook-graph-sdk/src/Facebook/PersistentData/FacebookMemoryPersistentDataHandler.php new file mode 100644 index 0000000..93a6686 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/PersistentData/FacebookMemoryPersistentDataHandler.php @@ -0,0 +1,53 @@ +sessionData[$key]) ? $this->sessionData[$key] : null; + } + + /** + * @inheritdoc + */ + public function set($key, $value) + { + $this->sessionData[$key] = $value; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/PersistentData/FacebookSessionPersistentDataHandler.php b/lib/facebook-graph-sdk/src/Facebook/PersistentData/FacebookSessionPersistentDataHandler.php new file mode 100644 index 0000000..698bfd0 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/PersistentData/FacebookSessionPersistentDataHandler.php @@ -0,0 +1,76 @@ +sessionPrefix . $key])) { + return $_SESSION[$this->sessionPrefix . $key]; + } + + return null; + } + + /** + * @inheritdoc + */ + public function set($key, $value) + { + $_SESSION[$this->sessionPrefix . $key] = $value; + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/PersistentData/PersistentDataInterface.php b/lib/facebook-graph-sdk/src/Facebook/PersistentData/PersistentDataInterface.php new file mode 100644 index 0000000..bd7e072 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/PersistentData/PersistentDataInterface.php @@ -0,0 +1,49 @@ +validateLength($length); + + $binaryString = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + + if ($binaryString === false) { + throw new FacebookSDKException( + static::ERROR_MESSAGE . + 'mcrypt_create_iv() returned an error.' + ); + } + + return $this->binToHex($binaryString, $length); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/PseudoRandomString/OpenSslPseudoRandomStringGenerator.php b/lib/facebook-graph-sdk/src/Facebook/PseudoRandomString/OpenSslPseudoRandomStringGenerator.php new file mode 100644 index 0000000..f4ea6b8 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/PseudoRandomString/OpenSslPseudoRandomStringGenerator.php @@ -0,0 +1,67 @@ +validateLength($length); + + $wasCryptographicallyStrong = false; + $binaryString = openssl_random_pseudo_bytes($length, $wasCryptographicallyStrong); + + if ($binaryString === false) { + throw new FacebookSDKException(static::ERROR_MESSAGE . 'openssl_random_pseudo_bytes() returned an unknown error.'); + } + + if ($wasCryptographicallyStrong !== true) { + throw new FacebookSDKException(static::ERROR_MESSAGE . 'openssl_random_pseudo_bytes() returned a pseudo-random string but it was not cryptographically secure and cannot be used.'); + } + + return $this->binToHex($binaryString, $length); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/PseudoRandomString/PseudoRandomStringGeneratorInterface.php b/lib/facebook-graph-sdk/src/Facebook/PseudoRandomString/PseudoRandomStringGeneratorInterface.php new file mode 100644 index 0000000..970330c --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/PseudoRandomString/PseudoRandomStringGeneratorInterface.php @@ -0,0 +1,45 @@ +validateLength($length); + + $stream = fopen('/dev/urandom', 'rb'); + if (!is_resource($stream)) { + throw new FacebookSDKException( + static::ERROR_MESSAGE . + 'Unable to open stream to /dev/urandom.' + ); + } + + if (!defined('HHVM_VERSION')) { + stream_set_read_buffer($stream, 0); + } + + $binaryString = fread($stream, $length); + fclose($stream); + + if (!$binaryString) { + throw new FacebookSDKException( + static::ERROR_MESSAGE . + 'Stream to /dev/urandom returned no data.' + ); + } + + return $this->binToHex($binaryString, $length); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/SignedRequest.php b/lib/facebook-graph-sdk/src/Facebook/SignedRequest.php new file mode 100644 index 0000000..77099a3 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/SignedRequest.php @@ -0,0 +1,332 @@ +app = $facebookApp; + + if (!$rawSignedRequest) { + return; + } + + $this->rawSignedRequest = $rawSignedRequest; + + $this->parse(); + } + + /** + * Returns the raw signed request data. + * + * @return string|null + */ + public function getRawSignedRequest() + { + return $this->rawSignedRequest; + } + + /** + * Returns the parsed signed request data. + * + * @return array|null + */ + public function getPayload() + { + return $this->payload; + } + + /** + * Returns a property from the signed request data if available. + * + * @param string $key + * @param mixed|null $default + * + * @return mixed|null + */ + public function get($key, $default = null) + { + if (isset($this->payload[$key])) { + return $this->payload[$key]; + } + + return $default; + } + + /** + * Returns user_id from signed request data if available. + * + * @return string|null + */ + public function getUserId() + { + return $this->get('user_id'); + } + + /** + * Checks for OAuth data in the payload. + * + * @return boolean + */ + public function hasOAuthData() + { + return $this->get('oauth_token') || $this->get('code'); + } + + /** + * Creates a signed request from an array of data. + * + * @param array $payload + * + * @return string + */ + public function make(array $payload) + { + $payload['algorithm'] = isset($payload['algorithm']) ? $payload['algorithm'] : 'HMAC-SHA256'; + $payload['issued_at'] = isset($payload['issued_at']) ? $payload['issued_at'] : time(); + $encodedPayload = $this->base64UrlEncode(json_encode($payload)); + + $hashedSig = $this->hashSignature($encodedPayload); + $encodedSig = $this->base64UrlEncode($hashedSig); + + return $encodedSig . '.' . $encodedPayload; + } + + /** + * Validates and decodes a signed request and saves + * the payload to an array. + */ + protected function parse() + { + list($encodedSig, $encodedPayload) = $this->split(); + + // Signature validation + $sig = $this->decodeSignature($encodedSig); + $hashedSig = $this->hashSignature($encodedPayload); + $this->validateSignature($hashedSig, $sig); + + $this->payload = $this->decodePayload($encodedPayload); + + // Payload validation + $this->validateAlgorithm(); + } + + /** + * Splits a raw signed request into signature and payload. + * + * @returns array + * + * @throws FacebookSDKException + */ + protected function split() + { + if (strpos($this->rawSignedRequest, '.') === false) { + throw new FacebookSDKException('Malformed signed request.', 606); + } + + return explode('.', $this->rawSignedRequest, 2); + } + + /** + * Decodes the raw signature from a signed request. + * + * @param string $encodedSig + * + * @returns string + * + * @throws FacebookSDKException + */ + protected function decodeSignature($encodedSig) + { + $sig = $this->base64UrlDecode($encodedSig); + + if (!$sig) { + throw new FacebookSDKException('Signed request has malformed encoded signature data.', 607); + } + + return $sig; + } + + /** + * Decodes the raw payload from a signed request. + * + * @param string $encodedPayload + * + * @returns array + * + * @throws FacebookSDKException + */ + protected function decodePayload($encodedPayload) + { + $payload = $this->base64UrlDecode($encodedPayload); + + if ($payload) { + $payload = json_decode($payload, true); + } + + if (!is_array($payload)) { + throw new FacebookSDKException('Signed request has malformed encoded payload data.', 607); + } + + return $payload; + } + + /** + * Validates the algorithm used in a signed request. + * + * @throws FacebookSDKException + */ + protected function validateAlgorithm() + { + if ($this->get('algorithm') !== 'HMAC-SHA256') { + throw new FacebookSDKException('Signed request is using the wrong algorithm.', 605); + } + } + + /** + * Hashes the signature used in a signed request. + * + * @param string $encodedData + * + * @return string + * + * @throws FacebookSDKException + */ + protected function hashSignature($encodedData) + { + $hashedSig = hash_hmac( + 'sha256', + $encodedData, + $this->app->getSecret(), + $raw_output = true + ); + + if (!$hashedSig) { + throw new FacebookSDKException('Unable to hash signature from encoded payload data.', 602); + } + + return $hashedSig; + } + + /** + * Validates the signature used in a signed request. + * + * @param string $hashedSig + * @param string $sig + * + * @throws FacebookSDKException + */ + protected function validateSignature($hashedSig, $sig) + { + if (mb_strlen($hashedSig) === mb_strlen($sig)) { + $validate = 0; + for ($i = 0; $i < mb_strlen($sig); $i++) { + $validate |= ord($hashedSig[$i]) ^ ord($sig[$i]); + } + if ($validate === 0) { + return; + } + } + + throw new FacebookSDKException('Signed request has an invalid signature.', 602); + } + + /** + * Base64 decoding which replaces characters: + * + instead of - + * / instead of _ + * + * @link http://en.wikipedia.org/wiki/Base64#URL_applications + * + * @param string $input base64 url encoded input + * + * @return string decoded string + */ + public function base64UrlDecode($input) + { + $urlDecodedBase64 = strtr($input, '-_', '+/'); + $this->validateBase64($urlDecodedBase64); + + return base64_decode($urlDecodedBase64); + } + + /** + * Base64 encoding which replaces characters: + * + instead of - + * / instead of _ + * + * @link http://en.wikipedia.org/wiki/Base64#URL_applications + * + * @param string $input string to encode + * + * @return string base64 url encoded input + */ + public function base64UrlEncode($input) + { + return strtr(base64_encode($input), '+/', '-_'); + } + + /** + * Validates a base64 string. + * + * @param string $input base64 value to validate + * + * @throws FacebookSDKException + */ + protected function validateBase64($input) + { + if (!preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $input)) { + throw new FacebookSDKException('Signed request contains malformed base64 encoding.', 608); + } + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Url/FacebookUrlDetectionHandler.php b/lib/facebook-graph-sdk/src/Facebook/Url/FacebookUrlDetectionHandler.php new file mode 100644 index 0000000..5fbb9ce --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Url/FacebookUrlDetectionHandler.php @@ -0,0 +1,163 @@ +getHttpScheme() . '://' . $this->getHostName() . $this->getServerVar('REQUEST_URI'); + } + + /** + * Get the currently active URL scheme. + * + * @return string + */ + protected function getHttpScheme() + { + return $this->isBehindSsl() ? 'https' : 'http'; + } + + /** + * Tries to detect if the server is running behind an SSL. + * + * @return boolean + */ + protected function isBehindSsl() + { + // Check for proxy first + $protocol = $this->getHeader('X_FORWARDED_PROTO'); + if ($protocol) { + return $this->protocolWithActiveSsl($protocol); + } + + $protocol = $this->getServerVar('HTTPS'); + if ($protocol) { + return $this->protocolWithActiveSsl($protocol); + } + + return (string)$this->getServerVar('SERVER_PORT') === '443'; + } + + /** + * Detects an active SSL protocol value. + * + * @param string $protocol + * + * @return boolean + */ + protected function protocolWithActiveSsl($protocol) + { + $protocol = strtolower((string)$protocol); + + return in_array($protocol, ['on', '1', 'https', 'ssl'], true); + } + + /** + * Tries to detect the host name of the server. + * + * Some elements adapted from + * + * @see https://github.com/symfony/HttpFoundation/blob/master/Request.php + * + * @return string + */ + protected function getHostName() + { + // Check for proxy first + if ($host = $this->getHeader('X_FORWARDED_HOST')) { + $elements = explode(',', $host); + $host = $elements[count($elements) - 1]; + } elseif (!$host = $this->getHeader('HOST')) { + if (!$host = $this->getServerVar('SERVER_NAME')) { + $host = $this->getServerVar('SERVER_ADDR'); + } + } + + // trim and remove port number from host + // host is lowercase as per RFC 952/2181 + $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); + + // Port number + $scheme = $this->getHttpScheme(); + $port = $this->getCurrentPort(); + $appendPort = ':' . $port; + + // Don't append port number if a normal port. + if (($scheme == 'http' && $port == '80') || ($scheme == 'https' && $port == '443')) { + $appendPort = ''; + } + + return $host . $appendPort; + } + + protected function getCurrentPort() + { + // Check for proxy first + $port = $this->getHeader('X_FORWARDED_PORT'); + if ($port) { + return (string)$port; + } + + $protocol = (string)$this->getHeader('X_FORWARDED_PROTO'); + if ($protocol === 'https') { + return '443'; + } + + return (string)$this->getServerVar('SERVER_PORT'); + } + + /** + * Returns the a value from the $_SERVER super global. + * + * @param string $key + * + * @return string + */ + protected function getServerVar($key) + { + return isset($_SERVER[$key]) ? $_SERVER[$key] : ''; + } + + /** + * Gets a value from the HTTP request headers. + * + * @param string $key + * + * @return string + */ + protected function getHeader($key) + { + return $this->getServerVar('HTTP_' . $key); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Url/FacebookUrlManipulator.php b/lib/facebook-graph-sdk/src/Facebook/Url/FacebookUrlManipulator.php new file mode 100644 index 0000000..20a0299 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Url/FacebookUrlManipulator.php @@ -0,0 +1,167 @@ + 0) { + $query = '?' . http_build_query($params, null, '&'); + } + } + + $scheme = isset($parts['scheme']) ? $parts['scheme'] . '://' : ''; + $host = isset($parts['host']) ? $parts['host'] : ''; + $port = isset($parts['port']) ? ':' . $parts['port'] : ''; + $path = isset($parts['path']) ? $parts['path'] : ''; + $fragment = isset($parts['fragment']) ? '#' . $parts['fragment'] : ''; + + return $scheme . $host . $port . $path . $query . $fragment; + } + + /** + * Gracefully appends params to the URL. + * + * @param string $url The URL that will receive the params. + * @param array $newParams The params to append to the URL. + * + * @return string + */ + public static function appendParamsToUrl($url, array $newParams = []) + { + if (!$newParams) { + return $url; + } + + if (strpos($url, '?') === false) { + return $url . '?' . http_build_query($newParams, null, '&'); + } + + list($path, $query) = explode('?', $url, 2); + $existingParams = []; + parse_str($query, $existingParams); + + // Favor params from the original URL over $newParams + $newParams = array_merge($newParams, $existingParams); + + // Sort for a predicable order + ksort($newParams); + + return $path . '?' . http_build_query($newParams, null, '&'); + } + + /** + * Returns the params from a URL in the form of an array. + * + * @param string $url The URL to parse the params from. + * + * @return array + */ + public static function getParamsAsArray($url) + { + $query = parse_url($url, PHP_URL_QUERY); + if (!$query) { + return []; + } + $params = []; + parse_str($query, $params); + + return $params; + } + + /** + * Adds the params of the first URL to the second URL. + * + * Any params that already exist in the second URL will go untouched. + * + * @param string $urlToStealFrom The URL harvest the params from. + * @param string $urlToAddTo The URL that will receive the new params. + * + * @return string The $urlToAddTo with any new params from $urlToStealFrom. + */ + public static function mergeUrlParams($urlToStealFrom, $urlToAddTo) + { + $newParams = static::getParamsAsArray($urlToStealFrom); + // Nothing new to add, return as-is + if (!$newParams) { + return $urlToAddTo; + } + + return static::appendParamsToUrl($urlToAddTo, $newParams); + } + + /** + * Check for a "/" prefix and prepend it if not exists. + * + * @param string|null $string + * + * @return string|null + */ + public static function forceSlashPrefix($string) + { + if (!$string) { + return $string; + } + + return strpos($string, '/') === 0 ? $string : '/' . $string; + } + + /** + * Trims off the hostname and Graph version from a URL. + * + * @param string $urlToTrim The URL the needs the surgery. + * + * @return string The $urlToTrim with the hostname and Graph version removed. + */ + public static function baseGraphUrlEndpoint($urlToTrim) + { + return '/' . preg_replace('/^https:\/\/.+\.facebook\.com(\/v.+?)?\//', '', $urlToTrim); + } +} diff --git a/lib/facebook-graph-sdk/src/Facebook/Url/UrlDetectionInterface.php b/lib/facebook-graph-sdk/src/Facebook/Url/UrlDetectionInterface.php new file mode 100644 index 0000000..764a606 --- /dev/null +++ b/lib/facebook-graph-sdk/src/Facebook/Url/UrlDetectionInterface.php @@ -0,0 +1,39 @@ + [ + 'app_id' => '123', + 'application' => 'Foo App', + 'error' => [ + 'code' => 190, + 'message' => 'Foo error message.', + 'subcode' => 463, + ], + 'issued_at' => 1422110200, + 'expires_at' => 1422115200, + 'is_valid' => false, + 'metadata' => [ + 'sso' => 'iphone-sso', + 'auth_type' => 'rerequest', + 'auth_nonce' => 'no-replicatey', + ], + 'scopes' => ['public_profile', 'basic_info', 'user_friends'], + 'profile_id' => '1000', + 'user_id' => '1337', + ], + ]; + + public function testDatesGetCastToDateTime() + { + $metadata = new AccessTokenMetadata($this->graphResponseData); + + $expires = $metadata->getExpiresAt(); + $issuedAt = $metadata->getIssuedAt(); + + $this->assertInstanceOf('DateTime', $expires); + $this->assertInstanceOf('DateTime', $issuedAt); + } + + public function testAllTheGettersReturnTheProperValue() + { + $metadata = new AccessTokenMetadata($this->graphResponseData); + + $this->assertEquals('123', $metadata->getAppId()); + $this->assertEquals('Foo App', $metadata->getApplication()); + $this->assertTrue($metadata->isError(), 'Expected an error'); + $this->assertEquals('190', $metadata->getErrorCode()); + $this->assertEquals('Foo error message.', $metadata->getErrorMessage()); + $this->assertEquals('463', $metadata->getErrorSubcode()); + $this->assertFalse($metadata->getIsValid(), 'Expected the access token to not be valid'); + $this->assertEquals('iphone-sso', $metadata->getSso()); + $this->assertEquals('rerequest', $metadata->getAuthType()); + $this->assertEquals('no-replicatey', $metadata->getAuthNonce()); + $this->assertEquals('1000', $metadata->getProfileId()); + $this->assertEquals(['public_profile', 'basic_info', 'user_friends'], $metadata->getScopes()); + $this->assertEquals('1337', $metadata->getUserId()); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testInvalidMetadataWillThrow() + { + new AccessTokenMetadata(['foo' => 'bar']); + } + + public function testAnExpectedAppIdWillNotThrow() + { + $metadata = new AccessTokenMetadata($this->graphResponseData); + $metadata->validateAppId('123'); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testAnUnexpectedAppIdWillThrow() + { + $metadata = new AccessTokenMetadata($this->graphResponseData); + $metadata->validateAppId('foo'); + } + + public function testAnExpectedUserIdWillNotThrow() + { + $metadata = new AccessTokenMetadata($this->graphResponseData); + $metadata->validateUserId('1337'); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testAnUnexpectedUserIdWillThrow() + { + $metadata = new AccessTokenMetadata($this->graphResponseData); + $metadata->validateUserId('foo'); + } + + public function testAnActiveAccessTokenWillNotThrow() + { + $this->graphResponseData['data']['expires_at'] = time() + 1000; + $metadata = new AccessTokenMetadata($this->graphResponseData); + $metadata->validateExpiration(); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testAnExpiredAccessTokenWillThrow() + { + $this->graphResponseData['data']['expires_at'] = time() - 1000; + $metadata = new AccessTokenMetadata($this->graphResponseData); + $metadata->validateExpiration(); + } +} diff --git a/lib/facebook-graph-sdk/tests/Authentication/AccessTokenTest.php b/lib/facebook-graph-sdk/tests/Authentication/AccessTokenTest.php new file mode 100644 index 0000000..d66a5ba --- /dev/null +++ b/lib/facebook-graph-sdk/tests/Authentication/AccessTokenTest.php @@ -0,0 +1,111 @@ +assertEquals('foo_token', $accessToken->getValue()); + $this->assertEquals('foo_token', (string)$accessToken); + } + + public function testAnAppSecretProofWillBeProperlyGenerated() + { + $accessToken = new AccessToken('foo_token'); + + $appSecretProof = $accessToken->getAppSecretProof('shhhhh!is.my.secret'); + + $this->assertEquals('796ba0d8a6b339e476a7b166a9e8ac0a395f7de736dc37de5f2f4397f5854eb8', $appSecretProof); + } + + public function testAnAppAccessTokenCanBeDetected() + { + $normalToken = new AccessToken('foo_token'); + $isNormalToken = $normalToken->isAppAccessToken(); + + $this->assertFalse($isNormalToken, 'Normal access token not expected to look like an app access token.'); + + $appToken = new AccessToken('123|secret'); + $isAppToken = $appToken->isAppAccessToken(); + + $this->assertTrue($isAppToken, 'App access token expected to look like an app access token.'); + } + + public function testShortLivedAccessTokensCanBeDetected() + { + $anHourAndAHalf = time() + (1.5 * 60); + $accessToken = new AccessToken('foo_token', $anHourAndAHalf); + + $isLongLived = $accessToken->isLongLived(); + + $this->assertFalse($isLongLived, 'Expected access token to be short lived.'); + } + + public function testLongLivedAccessTokensCanBeDetected() + { + $accessToken = new AccessToken('foo_token', $this->aWeekFromNow()); + + $isLongLived = $accessToken->isLongLived(); + + $this->assertTrue($isLongLived, 'Expected access token to be long lived.'); + } + + public function testAnAppAccessTokenDoesNotExpire() + { + $appToken = new AccessToken('123|secret'); + $hasExpired = $appToken->isExpired(); + + $this->assertFalse($hasExpired, 'App access token not expected to expire.'); + } + + public function testAnAccessTokenCanExpire() + { + $expireTime = time() - 100; + $appToken = new AccessToken('foo_token', $expireTime); + $hasExpired = $appToken->isExpired(); + + $this->assertTrue($hasExpired, 'Expected 100 second old access token to be expired.'); + } + + public function testAccessTokenCanBeSerialized() + { + $accessToken = new AccessToken('foo', time(), 'bar'); + + $newAccessToken = unserialize(serialize($accessToken)); + + $this->assertEquals((string)$accessToken, (string)$newAccessToken); + $this->assertEquals($accessToken->getExpiresAt(), $newAccessToken->getExpiresAt()); + } + + private function aWeekFromNow() + { + return time() + (60 * 60 * 24 * 7);//a week from now + } +} diff --git a/lib/facebook-graph-sdk/tests/Authentication/FooFacebookClientForOAuth2Test.php b/lib/facebook-graph-sdk/tests/Authentication/FooFacebookClientForOAuth2Test.php new file mode 100644 index 0000000..1199b00 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/Authentication/FooFacebookClientForOAuth2Test.php @@ -0,0 +1,58 @@ +response = '{"data":{"user_id":"444"}}'; + } + + public function setAccessTokenResponse() + { + $this->response = '{"access_token":"my_access_token","expires":"1422115200"}'; + } + + public function setCodeResponse() + { + $this->response = '{"code":"my_neat_code"}'; + } + + public function sendRequest(FacebookRequest $request) + { + return new FacebookResponse( + $request, + $this->response, + 200, + [] + ); + } +} diff --git a/lib/facebook-graph-sdk/tests/Authentication/OAuth2ClientTest.php b/lib/facebook-graph-sdk/tests/Authentication/OAuth2ClientTest.php new file mode 100644 index 0000000..72a8e2a --- /dev/null +++ b/lib/facebook-graph-sdk/tests/Authentication/OAuth2ClientTest.php @@ -0,0 +1,167 @@ +client = new FooFacebookClientForOAuth2Test(); + $this->oauth = new OAuth2Client($app, $this->client, static::TESTING_GRAPH_VERSION); + } + + public function testCanGetMetadataFromAnAccessToken() + { + $this->client->setMetadataResponse(); + + $metadata = $this->oauth->debugToken('baz_token'); + + $this->assertInstanceOf('Facebook\Authentication\AccessTokenMetadata', $metadata); + $this->assertEquals('444', $metadata->getUserId()); + + $expectedParams = [ + 'input_token' => 'baz_token', + 'access_token' => '123|foo_secret', + 'appsecret_proof' => 'de753c58fd58b03afca2340bbaeb4ecf987b5de4c09e39a63c944dd25efbc234', + ]; + + $request = $this->oauth->getLastRequest(); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/debug_token', $request->getEndpoint()); + $this->assertEquals($expectedParams, $request->getParams()); + $this->assertEquals(static::TESTING_GRAPH_VERSION, $request->getGraphVersion()); + } + + public function testCanBuildAuthorizationUrl() + { + $scope = ['email', 'base_foo']; + $authUrl = $this->oauth->getAuthorizationUrl('https://foo.bar', 'foo_state', $scope, ['foo' => 'bar'], '*'); + + $this->assertContains('*', $authUrl); + + $expectedUrl = 'https://www.facebook.com/' . static::TESTING_GRAPH_VERSION . '/dialog/oauth?'; + $this->assertTrue(strpos($authUrl, $expectedUrl) === 0, 'Unexpected base authorization URL returned from getAuthorizationUrl().'); + + $params = [ + 'client_id' => '123', + 'redirect_uri' => 'https://foo.bar', + 'state' => 'foo_state', + 'sdk' => 'php-sdk-' . Facebook::VERSION, + 'scope' => implode(',', $scope), + 'foo' => 'bar', + ]; + foreach ($params as $key => $value) { + $this->assertContains($key . '=' . urlencode($value), $authUrl); + } + } + + public function testCanGetAccessTokenFromCode() + { + $this->client->setAccessTokenResponse(); + + $accessToken = $this->oauth->getAccessTokenFromCode('bar_code', 'foo_uri'); + + $this->assertInstanceOf('Facebook\Authentication\AccessToken', $accessToken); + $this->assertEquals('my_access_token', $accessToken->getValue()); + + $expectedParams = [ + 'code' => 'bar_code', + 'redirect_uri' => 'foo_uri', + 'client_id' => '123', + 'client_secret' => 'foo_secret', + 'access_token' => '123|foo_secret', + 'appsecret_proof' => 'de753c58fd58b03afca2340bbaeb4ecf987b5de4c09e39a63c944dd25efbc234', + ]; + + $request = $this->oauth->getLastRequest(); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('/oauth/access_token', $request->getEndpoint()); + $this->assertEquals($expectedParams, $request->getParams()); + $this->assertEquals(static::TESTING_GRAPH_VERSION, $request->getGraphVersion()); + } + + public function testCanGetLongLivedAccessToken() + { + $this->client->setAccessTokenResponse(); + + $accessToken = $this->oauth->getLongLivedAccessToken('short_token'); + + $this->assertEquals('my_access_token', $accessToken->getValue()); + + $expectedParams = [ + 'grant_type' => 'fb_exchange_token', + 'fb_exchange_token' => 'short_token', + 'client_id' => '123', + 'client_secret' => 'foo_secret', + 'access_token' => '123|foo_secret', + 'appsecret_proof' => 'de753c58fd58b03afca2340bbaeb4ecf987b5de4c09e39a63c944dd25efbc234', + ]; + + $request = $this->oauth->getLastRequest(); + $this->assertEquals($expectedParams, $request->getParams()); + } + + public function testCanGetCodeFromLongLivedAccessToken() + { + $this->client->setCodeResponse(); + + $code = $this->oauth->getCodeFromLongLivedAccessToken('long_token', 'foo_uri'); + + $this->assertEquals('my_neat_code', $code); + + $expectedParams = [ + 'access_token' => 'long_token', + 'redirect_uri' => 'foo_uri', + 'client_id' => '123', + 'client_secret' => 'foo_secret', + 'appsecret_proof' => '7e91300ea91be4166282611d4fc700b473466f3ea2981dafbf492fc096995bf1', + ]; + + $request = $this->oauth->getLastRequest(); + $this->assertEquals($expectedParams, $request->getParams()); + $this->assertEquals('/oauth/client_code', $request->getEndpoint()); + } +} diff --git a/lib/facebook-graph-sdk/tests/Exceptions/FacebookResponseExceptionTest.php b/lib/facebook-graph-sdk/tests/Exceptions/FacebookResponseExceptionTest.php new file mode 100644 index 0000000..107a9b9 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/Exceptions/FacebookResponseExceptionTest.php @@ -0,0 +1,278 @@ +request = new FacebookRequest(new FacebookApp('123', 'foo')); + } + + public function testAuthenticationExceptions() + { + $params = [ + 'error' => [ + 'code' => 100, + 'message' => 'errmsg', + 'error_subcode' => 0, + 'type' => 'exception' + ], + ]; + + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookAuthenticationException', $exception->getPrevious()); + $this->assertEquals(100, $exception->getCode()); + $this->assertEquals(0, $exception->getSubErrorCode()); + $this->assertEquals('exception', $exception->getErrorType()); + $this->assertEquals('errmsg', $exception->getMessage()); + $this->assertEquals(json_encode($params), $exception->getRawResponse()); + $this->assertEquals(401, $exception->getHttpStatusCode()); + + $params['error']['code'] = 102; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookAuthenticationException', $exception->getPrevious()); + $this->assertEquals(102, $exception->getCode()); + + $params['error']['code'] = 190; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookAuthenticationException', $exception->getPrevious()); + $this->assertEquals(190, $exception->getCode()); + + $params['error']['type'] = 'OAuthException'; + $params['error']['code'] = 0; + $params['error']['error_subcode'] = 458; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookAuthenticationException', $exception->getPrevious()); + $this->assertEquals(458, $exception->getSubErrorCode()); + + $params['error']['error_subcode'] = 460; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookAuthenticationException', $exception->getPrevious()); + $this->assertEquals(460, $exception->getSubErrorCode()); + + $params['error']['error_subcode'] = 463; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookAuthenticationException', $exception->getPrevious()); + $this->assertEquals(463, $exception->getSubErrorCode()); + + $params['error']['error_subcode'] = 467; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookAuthenticationException', $exception->getPrevious()); + $this->assertEquals(467, $exception->getSubErrorCode()); + + $params['error']['error_subcode'] = 0; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookAuthenticationException', $exception->getPrevious()); + $this->assertEquals(0, $exception->getSubErrorCode()); + } + + public function testServerExceptions() + { + $params = [ + 'error' => [ + 'code' => 1, + 'message' => 'errmsg', + 'error_subcode' => 0, + 'type' => 'exception' + ], + ]; + + $response = new FacebookResponse($this->request, json_encode($params), 500); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookServerException', $exception->getPrevious()); + $this->assertEquals(1, $exception->getCode()); + $this->assertEquals(0, $exception->getSubErrorCode()); + $this->assertEquals('exception', $exception->getErrorType()); + $this->assertEquals('errmsg', $exception->getMessage()); + $this->assertEquals(json_encode($params), $exception->getRawResponse()); + $this->assertEquals(500, $exception->getHttpStatusCode()); + + $params['error']['code'] = 2; + $response = new FacebookResponse($this->request, json_encode($params), 500); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookServerException', $exception->getPrevious()); + $this->assertEquals(2, $exception->getCode()); + } + + public function testThrottleExceptions() + { + $params = [ + 'error' => [ + 'code' => 4, + 'message' => 'errmsg', + 'error_subcode' => 0, + 'type' => 'exception' + ], + ]; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookThrottleException', $exception->getPrevious()); + $this->assertEquals(4, $exception->getCode()); + $this->assertEquals(0, $exception->getSubErrorCode()); + $this->assertEquals('exception', $exception->getErrorType()); + $this->assertEquals('errmsg', $exception->getMessage()); + $this->assertEquals(json_encode($params), $exception->getRawResponse()); + $this->assertEquals(401, $exception->getHttpStatusCode()); + + $params['error']['code'] = 17; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookThrottleException', $exception->getPrevious()); + $this->assertEquals(17, $exception->getCode()); + + $params['error']['code'] = 341; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookThrottleException', $exception->getPrevious()); + $this->assertEquals(341, $exception->getCode()); + } + + public function testUserIssueExceptions() + { + $params = [ + 'error' => [ + 'code' => 230, + 'message' => 'errmsg', + 'error_subcode' => 459, + 'type' => 'exception' + ], + ]; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookAuthenticationException', $exception->getPrevious()); + $this->assertEquals(230, $exception->getCode()); + $this->assertEquals(459, $exception->getSubErrorCode()); + $this->assertEquals('exception', $exception->getErrorType()); + $this->assertEquals('errmsg', $exception->getMessage()); + $this->assertEquals(json_encode($params), $exception->getRawResponse()); + $this->assertEquals(401, $exception->getHttpStatusCode()); + + $params['error']['error_subcode'] = 464; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookAuthenticationException', $exception->getPrevious()); + $this->assertEquals(464, $exception->getSubErrorCode()); + } + + public function testAuthorizationExceptions() + { + $params = [ + 'error' => [ + 'code' => 10, + 'message' => 'errmsg', + 'error_subcode' => 0, + 'type' => 'exception' + ], + ]; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookAuthorizationException', $exception->getPrevious()); + $this->assertEquals(10, $exception->getCode()); + $this->assertEquals(0, $exception->getSubErrorCode()); + $this->assertEquals('exception', $exception->getErrorType()); + $this->assertEquals('errmsg', $exception->getMessage()); + $this->assertEquals(json_encode($params), $exception->getRawResponse()); + $this->assertEquals(401, $exception->getHttpStatusCode()); + + $params['error']['code'] = 200; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookAuthorizationException', $exception->getPrevious()); + $this->assertEquals(200, $exception->getCode()); + + $params['error']['code'] = 250; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookAuthorizationException', $exception->getPrevious()); + $this->assertEquals(250, $exception->getCode()); + + $params['error']['code'] = 299; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookAuthorizationException', $exception->getPrevious()); + $this->assertEquals(299, $exception->getCode()); + } + + public function testClientExceptions() + { + $params = [ + 'error' => [ + 'code' => 506, + 'message' => 'errmsg', + 'error_subcode' => 0, + 'type' => 'exception' + ], + ]; + $response = new FacebookResponse($this->request, json_encode($params), 401); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookClientException', $exception->getPrevious()); + $this->assertEquals(506, $exception->getCode()); + $this->assertEquals(0, $exception->getSubErrorCode()); + $this->assertEquals('exception', $exception->getErrorType()); + $this->assertEquals('errmsg', $exception->getMessage()); + $this->assertEquals(json_encode($params), $exception->getRawResponse()); + $this->assertEquals(401, $exception->getHttpStatusCode()); + } + + public function testOtherException() + { + $params = [ + 'error' => [ + 'code' => 42, + 'message' => 'ship love', + 'error_subcode' => 0, + 'type' => 'feature' + ], + ]; + $response = new FacebookResponse($this->request, json_encode($params), 200); + $exception = FacebookResponseException::create($response); + $this->assertInstanceOf('Facebook\Exceptions\FacebookOtherException', $exception->getPrevious()); + $this->assertEquals(42, $exception->getCode()); + $this->assertEquals(0, $exception->getSubErrorCode()); + $this->assertEquals('feature', $exception->getErrorType()); + $this->assertEquals('ship love', $exception->getMessage()); + $this->assertEquals(json_encode($params), $exception->getRawResponse()); + $this->assertEquals(200, $exception->getHttpStatusCode()); + } +} diff --git a/lib/facebook-graph-sdk/tests/FacebookAppTest.php b/lib/facebook-graph-sdk/tests/FacebookAppTest.php new file mode 100644 index 0000000..d1b453d --- /dev/null +++ b/lib/facebook-graph-sdk/tests/FacebookAppTest.php @@ -0,0 +1,66 @@ +app = new FacebookApp('id', 'secret'); + } + + public function testGetId() + { + $this->assertEquals('id', $this->app->getId()); + } + + public function testGetSecret() + { + $this->assertEquals('secret', $this->app->getSecret()); + } + + public function testAnAppAccessTokenCanBeGenerated() + { + $accessToken = $this->app->getAccessToken(); + + $this->assertInstanceOf('Facebook\Authentication\AccessToken', $accessToken); + $this->assertEquals('id|secret', (string)$accessToken); + } + + public function testSerialization() + { + $newApp = unserialize(serialize($this->app)); + + $this->assertInstanceOf('Facebook\FacebookApp', $newApp); + $this->assertEquals('id', $newApp->getId()); + $this->assertEquals('secret', $newApp->getSecret()); + } +} diff --git a/lib/facebook-graph-sdk/tests/FacebookBatchRequestTest.php b/lib/facebook-graph-sdk/tests/FacebookBatchRequestTest.php new file mode 100755 index 0000000..cef0586 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/FacebookBatchRequestTest.php @@ -0,0 +1,381 @@ +app = new FacebookApp('123', 'foo_secret'); + } + + public function testABatchRequestWillInstantiateWithTheProperProperties() + { + $batchRequest = new FacebookBatchRequest($this->app, [], 'foo_token', 'v0.1337'); + + $this->assertSame($this->app, $batchRequest->getApp()); + $this->assertEquals('foo_token', $batchRequest->getAccessToken()); + $this->assertEquals('POST', $batchRequest->getMethod()); + $this->assertEquals('', $batchRequest->getEndpoint()); + $this->assertEquals('v0.1337', $batchRequest->getGraphVersion()); + } + + public function testEmptyRequestWillFallbackToBatchDefaults() + { + $request = new FacebookRequest(); + + $this->createBatchRequest()->addFallbackDefaults($request); + + $this->assertRequestContainsAppAndToken($request, $this->app, 'foo_token'); + } + + public function testRequestWithTokenOnlyWillFallbackToBatchDefaults() + { + $request = new FacebookRequest(null, 'bar_token'); + + $this->createBatchRequest()->addFallbackDefaults($request); + + $this->assertRequestContainsAppAndToken($request, $this->app, 'bar_token'); + } + + public function testRequestWithAppOnlyWillFallbackToBatchDefaults() + { + $customApp = new FacebookApp('1337', 'bar_secret'); + $request = new FacebookRequest($customApp); + + $this->createBatchRequest()->addFallbackDefaults($request); + + $this->assertRequestContainsAppAndToken($request, $customApp, 'foo_token'); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testWillThrowWhenNoThereIsNoAppFallback() + { + $batchRequest = new FacebookBatchRequest(); + + $batchRequest->addFallbackDefaults(new FacebookRequest(null, 'foo_token')); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testWillThrowWhenNoThereIsNoAccessTokenFallback() + { + $request = new FacebookBatchRequest(); + + $request->addFallbackDefaults(new FacebookRequest($this->app)); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testAnInvalidTypeGivenToAddWillThrow() + { + $request = new FacebookBatchRequest(); + + $request->add('foo'); + } + + public function testAddingRequestsWillBeFormattedInAnArrayProperly() + { + $requests = [ + null => new FacebookRequest(null, null, 'GET', '/foo'), + 'my-second-request' => new FacebookRequest(null, null, 'POST', '/bar', ['foo' => 'bar']), + 'my-third-request' => new FacebookRequest(null, null, 'DELETE', '/baz') + ]; + + $batchRequest = $this->createBatchRequest(); + $batchRequest->add($requests[null]); + $batchRequest->add($requests['my-second-request'], 'my-second-request'); + $batchRequest->add($requests['my-third-request'], 'my-third-request'); + + $formattedRequests = $batchRequest->getRequests(); + + $this->assertRequestsMatch($requests, $formattedRequests); + } + + public function testANumericArrayOfRequestsCanBeAdded() + { + $requests = [ + new FacebookRequest(null, null, 'GET', '/foo'), + new FacebookRequest(null, null, 'POST', '/bar', ['foo' => 'bar']), + new FacebookRequest(null, null, 'DELETE', '/baz'), + ]; + + $formattedRequests = $this->createBatchRequestWithRequests($requests)->getRequests(); + + $this->assertRequestsMatch($requests, $formattedRequests); + } + + public function testAnAssociativeArrayOfRequestsCanBeAdded() + { + $requests = [ + 'req-one' => new FacebookRequest(null, null, 'GET', '/foo'), + 'req-two' => new FacebookRequest(null, null, 'POST', '/bar', ['foo' => 'bar']), + 'req-three' => new FacebookRequest(null, null, 'DELETE', '/baz'), + ]; + + $formattedRequests = $this->createBatchRequestWithRequests($requests)->getRequests(); + + $this->assertRequestsMatch($requests, $formattedRequests); + } + + public function testRequestsCanBeInjectedIntoConstructor() + { + $requests = [ + new FacebookRequest(null, null, 'GET', '/foo'), + new FacebookRequest(null, null, 'POST', '/bar', ['foo' => 'bar']), + new FacebookRequest(null, null, 'DELETE', '/baz'), + ]; + + $batchRequest = new FacebookBatchRequest($this->app, $requests, 'foo_token'); + $formattedRequests = $batchRequest->getRequests(); + + $this->assertRequestsMatch($requests, $formattedRequests); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testAZeroRequestCountWithThrow() + { + $batchRequest = new FacebookBatchRequest($this->app, [], 'foo_token'); + + $batchRequest->validateBatchRequestCount(); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testMoreThanFiftyRequestsWillThrow() + { + $batchRequest = $this->createBatchRequest(); + + $this->createAndAppendRequestsTo($batchRequest, 51); + + $batchRequest->validateBatchRequestCount(); + } + + public function testLessOrEqualThanFiftyRequestsWillNotThrow() + { + $batchRequest = $this->createBatchRequest(); + + $this->createAndAppendRequestsTo($batchRequest, 50); + + $batchRequest->validateBatchRequestCount(); + } + + /** + * @dataProvider requestsAndExpectedResponsesProvider + */ + public function testBatchRequestEntitiesProperlyGetConvertedToAnArray($request, $expectedArray) + { + $batchRequest = $this->createBatchRequest(); + $batchRequest->add($request, 'foo_name'); + + $requests = $batchRequest->getRequests(); + $batchRequestArray = $batchRequest->requestEntityToBatchArray($requests[0]['request'], $requests[0]['name']); + + $this->assertEquals($expectedArray, $batchRequestArray); + } + + public function requestsAndExpectedResponsesProvider() + { + $headers = $this->defaultHeaders(); + $apiVersion = Facebook::DEFAULT_GRAPH_VERSION; + + return [ + [ + new FacebookRequest(null, null, 'GET', '/foo', ['foo' => 'bar']), + [ + 'headers' => $headers, + 'method' => 'GET', + 'relative_url' => '/' . $apiVersion . '/foo?foo=bar&access_token=foo_token&appsecret_proof=df4256903ba4e23636cc142117aa632133d75c642bd2a68955be1443bd14deb9', + 'name' => 'foo_name', + ], + ], + [ + new FacebookRequest(null, null, 'POST', '/bar', ['bar' => 'baz']), + [ + 'headers' => $headers, + 'method' => 'POST', + 'relative_url' => '/' . $apiVersion . '/bar', + 'body' => 'bar=baz&access_token=foo_token&appsecret_proof=df4256903ba4e23636cc142117aa632133d75c642bd2a68955be1443bd14deb9', + 'name' => 'foo_name', + ], + ], + [ + new FacebookRequest(null, null, 'DELETE', '/bar'), + [ + 'headers' => $headers, + 'method' => 'DELETE', + 'relative_url' => '/' . $apiVersion . '/bar?access_token=foo_token&appsecret_proof=df4256903ba4e23636cc142117aa632133d75c642bd2a68955be1443bd14deb9', + 'name' => 'foo_name', + ], + ], + ]; + } + + public function testBatchRequestsWithFilesGetConvertedToAnArray() + { + $request = new FacebookRequest(null, null, 'POST', '/bar', [ + 'message' => 'foobar', + 'source' => new FacebookFile(__DIR__ . '/foo.txt'), + ]); + + $batchRequest = $this->createBatchRequest(); + $batchRequest->add($request, 'foo_name'); + + $requests = $batchRequest->getRequests(); + + $attachedFiles = $requests[0]['attached_files']; + + $batchRequestArray = $batchRequest->requestEntityToBatchArray( + $requests[0]['request'], + $requests[0]['name'], + $attachedFiles + ); + + $this->assertEquals([ + 'headers' => $this->defaultHeaders(), + 'method' => 'POST', + 'relative_url' => '/' . Facebook::DEFAULT_GRAPH_VERSION . '/bar', + 'body' => 'message=foobar&access_token=foo_token&appsecret_proof=df4256903ba4e23636cc142117aa632133d75c642bd2a68955be1443bd14deb9', + 'name' => 'foo_name', + 'attached_files' => $attachedFiles, + ], $batchRequestArray); + } + + public function testPreppingABatchRequestProperlySetsThePostParams() + { + $batchRequest = $this->createBatchRequest(); + $batchRequest->add(new FacebookRequest(null, 'bar_token', 'GET', '/foo'), 'foo_name'); + $batchRequest->add(new FacebookRequest(null, null, 'POST', '/bar', ['foo' => 'bar'])); + $batchRequest->prepareRequestsForBatch(); + + $params = $batchRequest->getParams(); + + $expectedHeaders = json_encode($this->defaultHeaders()); + $version = Facebook::DEFAULT_GRAPH_VERSION; + $expectedBatchParams = [ + 'batch' => '[{"headers":' . $expectedHeaders . ',"method":"GET","relative_url":"\\/' . $version . '\\/foo?access_token=bar_token&appsecret_proof=2ceec40b7b9fd7d38fff1767b766bcc6b1f9feb378febac4612c156e6a8354bd","name":"foo_name"},' + . '{"headers":' . $expectedHeaders . ',"method":"POST","relative_url":"\\/' . $version . '\\/bar","body":"foo=bar&access_token=foo_token&appsecret_proof=df4256903ba4e23636cc142117aa632133d75c642bd2a68955be1443bd14deb9"}]', + 'include_headers' => true, + 'access_token' => 'foo_token', + 'appsecret_proof' => 'df4256903ba4e23636cc142117aa632133d75c642bd2a68955be1443bd14deb9', + ]; + $this->assertEquals($expectedBatchParams, $params); + } + + public function testPreppingABatchRequestProperlyMovesTheFiles() + { + $batchRequest = $this->createBatchRequest(); + $batchRequest->add(new FacebookRequest(null, 'bar_token', 'GET', '/foo'), 'foo_name'); + $batchRequest->add(new FacebookRequest(null, null, 'POST', '/me/photos', [ + 'message' => 'foobar', + 'source' => new FacebookFile(__DIR__ . '/foo.txt'), + ])); + $batchRequest->prepareRequestsForBatch(); + + $params = $batchRequest->getParams(); + $files = $batchRequest->getFiles(); + + $attachedFiles = implode(',', array_keys($files)); + + $expectedHeaders = json_encode($this->defaultHeaders()); + $version = Facebook::DEFAULT_GRAPH_VERSION; + $expectedBatchParams = [ + 'batch' => '[{"headers":' . $expectedHeaders . ',"method":"GET","relative_url":"\\/' . $version . '\\/foo?access_token=bar_token&appsecret_proof=2ceec40b7b9fd7d38fff1767b766bcc6b1f9feb378febac4612c156e6a8354bd","name":"foo_name"},' + . '{"headers":' . $expectedHeaders . ',"method":"POST","relative_url":"\\/' . $version . '\\/me\\/photos","body":"message=foobar&access_token=foo_token&appsecret_proof=df4256903ba4e23636cc142117aa632133d75c642bd2a68955be1443bd14deb9","attached_files":"' . $attachedFiles . '"}]', + 'include_headers' => true, + 'access_token' => 'foo_token', + 'appsecret_proof' => 'df4256903ba4e23636cc142117aa632133d75c642bd2a68955be1443bd14deb9', + ]; + $this->assertEquals($expectedBatchParams, $params); + } + + private function assertRequestContainsAppAndToken(FacebookRequest $request, FacebookApp $expectedApp, $expectedToken) + { + $app = $request->getApp(); + $token = $request->getAccessToken(); + + $this->assertSame($expectedApp, $app); + $this->assertEquals($expectedToken, $token); + } + + private function defaultHeaders() + { + $headers = []; + foreach (FacebookRequest::getDefaultHeaders() as $name => $value) { + $headers[] = $name . ': ' . $value; + } + + return $headers; + } + + private function createAndAppendRequestsTo(FacebookBatchRequest $batchRequest, $number) + { + for ($i = 0; $i < $number; $i++) { + $batchRequest->add(new FacebookRequest()); + } + } + + private function createBatchRequest() + { + return new FacebookBatchRequest($this->app, [], 'foo_token'); + } + + private function createBatchRequestWithRequests(array $requests) + { + $batchRequest = $this->createBatchRequest(); + $batchRequest->add($requests); + + return $batchRequest; + } + + private function assertRequestsMatch($requests, $formattedRequests) + { + $expectedRequests = []; + foreach ($requests as $name => $request) { + $expectedRequests[] = [ + 'name' => $name, + 'request' => $request + ]; + } + $this->assertEquals($expectedRequests, $formattedRequests); + } +} diff --git a/lib/facebook-graph-sdk/tests/FacebookBatchResponseTest.php b/lib/facebook-graph-sdk/tests/FacebookBatchResponseTest.php new file mode 100755 index 0000000..dec92a1 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/FacebookBatchResponseTest.php @@ -0,0 +1,138 @@ +app = new FacebookApp('123', 'foo_secret'); + $this->request = new FacebookRequest( + $this->app, + 'foo_token', + 'POST', + '/', + ['batch' => 'foo'], + 'foo_eTag', + 'v1337' + ); + } + + public function testASuccessfulJsonBatchResponseWillBeDecoded() + { + $graphResponseJson = '['; + // Single Graph object. + $graphResponseJson .= '{"code":200,"headers":[{"name":"Connection","value":"close"},{"name":"Last-Modified","value":"2013-12-24T00:34:20+0000"},{"name":"Facebook-API-Version","value":"v2.0"},{"name":"ETag","value":"\"fooTag\""},{"name":"Content-Type","value":"text\/javascript; charset=UTF-8"},{"name":"Pragma","value":"no-cache"},{"name":"Access-Control-Allow-Origin","value":"*"},{"name":"Cache-Control","value":"private, no-cache, no-store, must-revalidate"},{"name":"Expires","value":"Sat, 01 Jan 2000 00:00:00 GMT"}],"body":"{\"id\":\"123\",\"name\":\"Foo McBar\",\"updated_time\":\"2013-12-24T00:34:20+0000\",\"verified\":true}"}'; + // Paginated list of Graph objects. + $graphResponseJson .= ',{"code":200,"headers":[{"name":"Connection","value":"close"},{"name":"Facebook-API-Version","value":"v1.0"},{"name":"ETag","value":"\"barTag\""},{"name":"Content-Type","value":"text\/javascript; charset=UTF-8"},{"name":"Pragma","value":"no-cache"},{"name":"Access-Control-Allow-Origin","value":"*"},{"name":"Cache-Control","value":"private, no-cache, no-store, must-revalidate"},{"name":"Expires","value":"Sat, 01 Jan 2000 00:00:00 GMT"}],"body":"{\"data\":[{\"id\":\"1337\",\"story\":\"Foo story.\"},{\"id\":\"1338\",\"story\":\"Bar story.\"}],\"paging\":{\"previous\":\"previous_url\",\"next\":\"next_url\"}}"}'; + // After POST operation. + $graphResponseJson .= ',{"code":200,"headers":[{"name":"Connection","value":"close"},{"name":"Expires","value":"Sat, 01 Jan 2000 00:00:00 GMT"},{"name":"Cache-Control","value":"private, no-cache, no-store, must-revalidate"},{"name":"Access-Control-Allow-Origin","value":"*"},{"name":"Pragma","value":"no-cache"},{"name":"Content-Type","value":"text\/javascript; charset=UTF-8"},{"name":"Facebook-API-Version","value":"v2.0"}],"body":"{\"id\":\"123_1337\"}"}'; + // After DELETE operation. + $graphResponseJson .= ',{"code":200,"headers":[{"name":"Connection","value":"close"},{"name":"Expires","value":"Sat, 01 Jan 2000 00:00:00 GMT"},{"name":"Cache-Control","value":"private, no-cache, no-store, must-revalidate"},{"name":"Access-Control-Allow-Origin","value":"*"},{"name":"Pragma","value":"no-cache"},{"name":"Content-Type","value":"text\/javascript; charset=UTF-8"},{"name":"Facebook-API-Version","value":"v2.0"}],"body":"true"}'; + $graphResponseJson .= ']'; + $response = new FacebookResponse($this->request, $graphResponseJson, 200); + $batchRequest = new FacebookBatchRequest($this->app, [ + new FacebookRequest($this->app, 'token'), + new FacebookRequest($this->app, 'token'), + new FacebookRequest($this->app, 'token'), + new FacebookRequest($this->app, 'token'), + ]); + $batchResponse = new FacebookBatchResponse($batchRequest, $response); + + $decodedResponses = $batchResponse->getResponses(); + + // Single Graph object. + $this->assertFalse($decodedResponses[0]->isError(), 'Did not expect Response to return an error for single Graph object.'); + $this->assertInstanceOf('Facebook\GraphNodes\GraphNode', $decodedResponses[0]->getGraphNode()); + // Paginated list of Graph objects. + $this->assertFalse($decodedResponses[1]->isError(), 'Did not expect Response to return an error for paginated list of Graph objects.'); + $graphEdge = $decodedResponses[1]->getGraphEdge(); + $this->assertInstanceOf('Facebook\GraphNodes\GraphNode', $graphEdge[0]); + $this->assertInstanceOf('Facebook\GraphNodes\GraphNode', $graphEdge[1]); + } + + public function testABatchResponseCanBeIteratedOver() + { + $graphResponseJson = '['; + $graphResponseJson .= '{"code":200,"headers":[],"body":"{\"foo\":\"bar\"}"}'; + $graphResponseJson .= ',{"code":200,"headers":[],"body":"{\"foo\":\"bar\"}"}'; + $graphResponseJson .= ',{"code":200,"headers":[],"body":"{\"foo\":\"bar\"}"}'; + $graphResponseJson .= ']'; + $response = new FacebookResponse($this->request, $graphResponseJson, 200); + $batchRequest = new FacebookBatchRequest($this->app, [ + 'req_one' => new FacebookRequest($this->app, 'token'), + 'req_two' => new FacebookRequest($this->app, 'token'), + 'req_three' => new FacebookRequest($this->app, 'token'), + ]); + $batchResponse = new FacebookBatchResponse($batchRequest, $response); + + $this->assertInstanceOf('IteratorAggregate', $batchResponse); + + foreach ($batchResponse as $key => $responseEntity) { + $this->assertTrue(in_array($key, ['req_one', 'req_two', 'req_three'])); + $this->assertInstanceOf('Facebook\FacebookResponse', $responseEntity); + } + } + + public function testTheOriginalRequestCanBeObtainedForEachRequest() + { + $graphResponseJson = '['; + $graphResponseJson .= '{"code":200,"headers":[],"body":"{\"foo\":\"bar\"}"}'; + $graphResponseJson .= ',{"code":200,"headers":[],"body":"{\"foo\":\"bar\"}"}'; + $graphResponseJson .= ',{"code":200,"headers":[],"body":"{\"foo\":\"bar\"}"}'; + $graphResponseJson .= ']'; + $response = new FacebookResponse($this->request, $graphResponseJson, 200); + + $requests = [ + new FacebookRequest($this->app, 'foo_token_one', 'GET', '/me'), + new FacebookRequest($this->app, 'foo_token_two', 'POST', '/you'), + new FacebookRequest($this->app, 'foo_token_three', 'DELETE', '/123456'), + ]; + + $batchRequest = new FacebookBatchRequest($this->app, $requests); + $batchResponse = new FacebookBatchResponse($batchRequest, $response); + + $this->assertInstanceOf('Facebook\FacebookResponse', $batchResponse[0]); + $this->assertInstanceOf('Facebook\FacebookRequest', $batchResponse[0]->getRequest()); + $this->assertEquals('foo_token_one', $batchResponse[0]->getAccessToken()); + $this->assertEquals('foo_token_two', $batchResponse[1]->getAccessToken()); + $this->assertEquals('foo_token_three', $batchResponse[2]->getAccessToken()); + } +} diff --git a/lib/facebook-graph-sdk/tests/FacebookClientTest.php b/lib/facebook-graph-sdk/tests/FacebookClientTest.php new file mode 100644 index 0000000..6e9bb6c --- /dev/null +++ b/lib/facebook-graph-sdk/tests/FacebookClientTest.php @@ -0,0 +1,308 @@ +fbApp = new FacebookApp('id', 'shhhh!'); + $this->fbClient = new FacebookClient(new MyFooClientHandler()); + } + + public function testACustomHttpClientCanBeInjected() + { + $handler = new MyFooClientHandler(); + $client = new FacebookClient($handler); + $httpHandler = $client->getHttpClientHandler(); + + $this->assertInstanceOf('Facebook\Tests\MyFooClientHandler', $httpHandler); + } + + public function testTheHttpClientWillFallbackToDefault() + { + $client = new FacebookClient(); + $httpHandler = $client->getHttpClientHandler(); + + if (function_exists('curl_init')) { + $this->assertInstanceOf('Facebook\HttpClients\FacebookCurlHttpClient', $httpHandler); + } else { + $this->assertInstanceOf('Facebook\HttpClients\FacebookStreamHttpClient', $httpHandler); + } + } + + public function testBetaModeCanBeDisabledOrEnabledViaConstructor() + { + $client = new FacebookClient(null, false); + $url = $client->getBaseGraphUrl(); + $this->assertEquals(FacebookClient::BASE_GRAPH_URL, $url); + + $client = new FacebookClient(null, true); + $url = $client->getBaseGraphUrl(); + $this->assertEquals(FacebookClient::BASE_GRAPH_URL_BETA, $url); + } + + public function testBetaModeCanBeDisabledOrEnabledViaMethod() + { + $client = new FacebookClient(); + $client->enableBetaMode(false); + $url = $client->getBaseGraphUrl(); + $this->assertEquals(FacebookClient::BASE_GRAPH_URL, $url); + + $client->enableBetaMode(true); + $url = $client->getBaseGraphUrl(); + $this->assertEquals(FacebookClient::BASE_GRAPH_URL_BETA, $url); + } + + public function testGraphVideoUrlCanBeSet() + { + $client = new FacebookClient(); + $client->enableBetaMode(false); + $url = $client->getBaseGraphUrl($postToVideoUrl = true); + $this->assertEquals(FacebookClient::BASE_GRAPH_VIDEO_URL, $url); + + $client->enableBetaMode(true); + $url = $client->getBaseGraphUrl($postToVideoUrl = true); + $this->assertEquals(FacebookClient::BASE_GRAPH_VIDEO_URL_BETA, $url); + } + + public function testAFacebookRequestEntityCanBeUsedToSendARequestToGraph() + { + $fbRequest = new FacebookRequest($this->fbApp, 'token', 'GET', '/foo'); + $response = $this->fbClient->sendRequest($fbRequest); + + $this->assertInstanceOf('Facebook\FacebookResponse', $response); + $this->assertEquals(200, $response->getHttpStatusCode()); + $this->assertEquals('{"data":[{"id":"123","name":"Foo"},{"id":"1337","name":"Bar"}]}', $response->getBody()); + } + + public function testAFacebookBatchRequestEntityCanBeUsedToSendABatchRequestToGraph() + { + $fbRequests = [ + new FacebookRequest($this->fbApp, 'token', 'GET', '/foo'), + new FacebookRequest($this->fbApp, 'token', 'POST', '/bar'), + ]; + $fbBatchRequest = new FacebookBatchRequest($this->fbApp, $fbRequests); + + $fbBatchClient = new FacebookClient(new MyFooBatchClientHandler()); + $response = $fbBatchClient->sendBatchRequest($fbBatchRequest); + + $this->assertInstanceOf('Facebook\FacebookBatchResponse', $response); + $this->assertEquals('GET', $response[0]->getRequest()->getMethod()); + $this->assertEquals('POST', $response[1]->getRequest()->getMethod()); + } + + public function testAFacebookBatchRequestWillProperlyBatchFiles() + { + $fbRequests = [ + new FacebookRequest($this->fbApp, 'token', 'POST', '/photo', [ + 'message' => 'foobar', + 'source' => new FacebookFile(__DIR__ . '/foo.txt'), + ]), + new FacebookRequest($this->fbApp, 'token', 'POST', '/video', [ + 'message' => 'foobar', + 'source' => new FacebookVideo(__DIR__ . '/foo.txt'), + ]), + ]; + $fbBatchRequest = new FacebookBatchRequest($this->fbApp, $fbRequests); + $fbBatchRequest->prepareRequestsForBatch(); + + list($url, $method, $headers, $body) = $this->fbClient->prepareRequestMessage($fbBatchRequest); + + $this->assertEquals(FacebookClient::BASE_GRAPH_VIDEO_URL . '/' . Facebook::DEFAULT_GRAPH_VERSION, $url); + $this->assertEquals('POST', $method); + $this->assertContains('multipart/form-data; boundary=', $headers['Content-Type']); + $this->assertContains('Content-Disposition: form-data; name="batch"', $body); + $this->assertContains('Content-Disposition: form-data; name="include_headers"', $body); + $this->assertContains('"name":0,"attached_files":', $body); + $this->assertContains('"name":1,"attached_files":', $body); + $this->assertContains('"; filename="foo.txt"', $body); + } + + public function testARequestOfParamsWillBeUrlEncoded() + { + $fbRequest = new FacebookRequest($this->fbApp, 'token', 'POST', '/foo', ['foo' => 'bar']); + $response = $this->fbClient->sendRequest($fbRequest); + + $headersSent = $response->getRequest()->getHeaders(); + + $this->assertEquals('application/x-www-form-urlencoded', $headersSent['Content-Type']); + } + + public function testARequestWithFilesWillBeMultipart() + { + $myFile = new FacebookFile(__DIR__ . '/foo.txt'); + $fbRequest = new FacebookRequest($this->fbApp, 'token', 'POST', '/foo', ['file' => $myFile]); + $response = $this->fbClient->sendRequest($fbRequest); + + $headersSent = $response->getRequest()->getHeaders(); + + $this->assertContains('multipart/form-data; boundary=', $headersSent['Content-Type']); + } + + /** + * @group integration + */ + public function testCanCreateATestUserAndGetTheProfileAndThenDeleteTheTestUser() + { + $this->initializeTestApp(); + + // Create a test user + $testUserPath = '/' . FacebookTestCredentials::$appId . '/accounts/test-users'; + $params = [ + 'installed' => true, + 'name' => 'Foo Phpunit User', + 'locale' => 'en_US', + 'permissions' => implode(',', ['read_stream', 'user_photos']), + ]; + + $request = new FacebookRequest( + static::$testFacebookApp, + static::$testFacebookApp->getAccessToken(), + 'POST', + $testUserPath, + $params + ); + $response = static::$testFacebookClient->sendRequest($request)->getGraphNode(); + + $testUserId = $response->getField('id'); + $testUserAccessToken = $response->getField('access_token'); + + // Get the test user's profile + $request = new FacebookRequest( + static::$testFacebookApp, + $testUserAccessToken, + 'GET', + '/me' + ); + $graphNode = static::$testFacebookClient->sendRequest($request)->getGraphNode(); + + $this->assertInstanceOf('Facebook\GraphNodes\GraphNode', $graphNode); + $this->assertNotNull($graphNode->getField('id')); + $this->assertEquals('Foo Phpunit User', $graphNode->getField('name')); + + // Delete test user + $request = new FacebookRequest( + static::$testFacebookApp, + static::$testFacebookApp->getAccessToken(), + 'DELETE', + '/' . $testUserId + ); + $graphNode = static::$testFacebookClient->sendRequest($request)->getGraphNode(); + + $this->assertTrue($graphNode->getField('success')); + } + + public function initializeTestApp() + { + if (!file_exists(__DIR__ . '/FacebookTestCredentials.php')) { + throw new FacebookSDKException( + 'You must create a FacebookTestCredentials.php file from FacebookTestCredentials.php.dist' + ); + } + + if (!strlen(FacebookTestCredentials::$appId) || + !strlen(FacebookTestCredentials::$appSecret) + ) { + throw new FacebookSDKException( + 'You must fill out FacebookTestCredentials.php' + ); + } + static::$testFacebookApp = new FacebookApp( + FacebookTestCredentials::$appId, + FacebookTestCredentials::$appSecret + ); + + // Use default client + $client = null; + + // Uncomment to enable curl implementation. + //$client = new FacebookCurlHttpClient(); + + // Uncomment to enable stream wrapper implementation. + //$client = new FacebookStreamHttpClient(); + + // Uncomment to enable Guzzle implementation. + //$client = new FacebookGuzzleHttpClient(); + + static::$testFacebookClient = new FacebookClient($client); + } +} diff --git a/lib/facebook-graph-sdk/tests/FacebookRequestTest.php b/lib/facebook-graph-sdk/tests/FacebookRequestTest.php new file mode 100755 index 0000000..fdea644 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/FacebookRequestTest.php @@ -0,0 +1,207 @@ +assertInstanceOf('Facebook\FacebookRequest', $request); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testAMissingAccessTokenWillThrow() + { + $app = new FacebookApp('123', 'foo_secret'); + $request = new FacebookRequest($app); + + $request->validateAccessToken(); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testAMissingMethodWillThrow() + { + $app = new FacebookApp('123', 'foo_secret'); + $request = new FacebookRequest($app); + + $request->validateMethod(); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testAnInvalidMethodWillThrow() + { + $app = new FacebookApp('123', 'foo_secret'); + $request = new FacebookRequest($app, 'foo_token', 'FOO'); + + $request->validateMethod(); + } + + public function testGetHeadersWillAutoAppendETag() + { + $app = new FacebookApp('123', 'foo_secret'); + $request = new FacebookRequest($app, null, 'GET', '/foo', [], 'fooETag'); + + $headers = $request->getHeaders(); + + $expectedHeaders = FacebookRequest::getDefaultHeaders(); + $expectedHeaders['If-None-Match'] = 'fooETag'; + + $this->assertEquals($expectedHeaders, $headers); + } + + public function testGetParamsWillAutoAppendAccessTokenAndAppSecretProof() + { + $app = new FacebookApp('123', 'foo_secret'); + $request = new FacebookRequest($app, 'foo_token', 'POST', '/foo', ['foo' => 'bar']); + + $params = $request->getParams(); + + $this->assertEquals([ + 'foo' => 'bar', + 'access_token' => 'foo_token', + 'appsecret_proof' => 'df4256903ba4e23636cc142117aa632133d75c642bd2a68955be1443bd14deb9', + ], $params); + } + + public function testAnAccessTokenCanBeSetFromTheParams() + { + $app = new FacebookApp('123', 'foo_secret'); + $request = new FacebookRequest($app, null, 'POST', '/me', ['access_token' => 'bar_token']); + + $accessToken = $request->getAccessToken(); + + $this->assertEquals('bar_token', $accessToken); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testAccessTokenConflictsWillThrow() + { + $app = new FacebookApp('123', 'foo_secret'); + new FacebookRequest($app, 'foo_token', 'POST', '/me', ['access_token' => 'bar_token']); + } + + public function testAProperUrlWillBeGenerated() + { + $app = new FacebookApp('123', 'foo_secret'); + $getRequest = new FacebookRequest($app, 'foo_token', 'GET', '/foo', ['foo' => 'bar']); + + $getUrl = $getRequest->getUrl(); + $expectedParams = 'foo=bar&access_token=foo_token&appsecret_proof=df4256903ba4e23636cc142117aa632133d75c642bd2a68955be1443bd14deb9'; + $expectedUrl = '/' . Facebook::DEFAULT_GRAPH_VERSION . '/foo?' . $expectedParams; + + $this->assertEquals($expectedUrl, $getUrl); + + $postRequest = new FacebookRequest($app, 'foo_token', 'POST', '/bar', ['foo' => 'bar']); + + $postUrl = $postRequest->getUrl(); + $expectedUrl = '/' . Facebook::DEFAULT_GRAPH_VERSION . '/bar'; + + $this->assertEquals($expectedUrl, $postUrl); + } + + public function testAuthenticationParamsAreStrippedAndReapplied() + { + $app = new FacebookApp('123', 'foo_secret'); + + $request = new FacebookRequest( + $app, + $accessToken = 'foo_token', + $method = 'GET', + $endpoint = '/foo', + $params = [ + 'access_token' => 'foo_token', + 'appsecret_proof' => 'bar_app_secret', + 'bar' => 'baz', + ] + ); + + $url = $request->getUrl(); + + $expectedParams = 'bar=baz&access_token=foo_token&appsecret_proof=df4256903ba4e23636cc142117aa632133d75c642bd2a68955be1443bd14deb9'; + $expectedUrl = '/' . Facebook::DEFAULT_GRAPH_VERSION . '/foo?' . $expectedParams; + $this->assertEquals($expectedUrl, $url); + + $params = $request->getParams(); + + $expectedParams = [ + 'access_token' => 'foo_token', + 'appsecret_proof' => 'df4256903ba4e23636cc142117aa632133d75c642bd2a68955be1443bd14deb9', + 'bar' => 'baz', + ]; + $this->assertEquals($expectedParams, $params); + } + + public function testAFileCanBeAddedToParams() + { + $myFile = new FacebookFile(__DIR__ . '/foo.txt'); + $params = [ + 'name' => 'Foo Bar', + 'source' => $myFile, + ]; + $app = new FacebookApp('123', 'foo_secret'); + $request = new FacebookRequest($app, 'foo_token', 'POST', '/foo/photos', $params); + + $actualParams = $request->getParams(); + + $this->assertTrue($request->containsFileUploads()); + $this->assertFalse($request->containsVideoUploads()); + $this->assertTrue(!isset($actualParams['source'])); + $this->assertEquals('Foo Bar', $actualParams['name']); + } + + public function testAVideoCanBeAddedToParams() + { + $myFile = new FacebookVideo(__DIR__ . '/foo.txt'); + $params = [ + 'name' => 'Foo Bar', + 'source' => $myFile, + ]; + $app = new FacebookApp('123', 'foo_secret'); + $request = new FacebookRequest($app, 'foo_token', 'POST', '/foo/videos', $params); + + $actualParams = $request->getParams(); + + $this->assertTrue($request->containsFileUploads()); + $this->assertTrue($request->containsVideoUploads()); + $this->assertTrue(!isset($actualParams['source'])); + $this->assertEquals('Foo Bar', $actualParams['name']); + } +} diff --git a/lib/facebook-graph-sdk/tests/FacebookResponseTest.php b/lib/facebook-graph-sdk/tests/FacebookResponseTest.php new file mode 100755 index 0000000..21f90ae --- /dev/null +++ b/lib/facebook-graph-sdk/tests/FacebookResponseTest.php @@ -0,0 +1,121 @@ +request = new FacebookRequest( + $app, + 'foo_token', + 'GET', + '/me/photos?keep=me', + ['foo' => 'bar'], + 'foo_eTag', + 'v1337' + ); + } + + public function testAnETagCanBeProperlyAccessed() + { + $response = new FacebookResponse($this->request, '', 200, ['ETag' => 'foo_tag']); + + $eTag = $response->getETag(); + + $this->assertEquals('foo_tag', $eTag); + } + + public function testAProperAppSecretProofCanBeGenerated() + { + $response = new FacebookResponse($this->request); + + $appSecretProof = $response->getAppSecretProof(); + + $this->assertEquals('df4256903ba4e23636cc142117aa632133d75c642bd2a68955be1443bd14deb9', $appSecretProof); + } + + public function testASuccessfulJsonResponseWillBeDecodedToAGraphNode() + { + $graphResponseJson = '{"id":"123","name":"Foo"}'; + $response = new FacebookResponse($this->request, $graphResponseJson, 200); + + $decodedResponse = $response->getDecodedBody(); + $graphNode = $response->getGraphNode(); + + $this->assertFalse($response->isError(), 'Did not expect Response to return an error.'); + $this->assertEquals([ + 'id' => '123', + 'name' => 'Foo', + ], $decodedResponse); + $this->assertInstanceOf('Facebook\GraphNodes\GraphNode', $graphNode); + } + + public function testASuccessfulJsonResponseWillBeDecodedToAGraphEdge() + { + $graphResponseJson = '{"data":[{"id":"123","name":"Foo"},{"id":"1337","name":"Bar"}]}'; + $response = new FacebookResponse($this->request, $graphResponseJson, 200); + + $graphEdge = $response->getGraphEdge(); + + $this->assertFalse($response->isError(), 'Did not expect Response to return an error.'); + $this->assertInstanceOf('Facebook\GraphNodes\GraphNode', $graphEdge[0]); + $this->assertInstanceOf('Facebook\GraphNodes\GraphNode', $graphEdge[1]); + } + + public function testASuccessfulUrlEncodedKeyValuePairResponseWillBeDecoded() + { + $graphResponseKeyValuePairs = 'id=123&name=Foo'; + $response = new FacebookResponse($this->request, $graphResponseKeyValuePairs, 200); + + $decodedResponse = $response->getDecodedBody(); + + $this->assertFalse($response->isError(), 'Did not expect Response to return an error.'); + $this->assertEquals([ + 'id' => '123', + 'name' => 'Foo', + ], $decodedResponse); + } + + public function testErrorStatusCanBeCheckedWhenAnErrorResponseIsReturned() + { + $graphResponse = '{"error":{"message":"Foo error.","type":"OAuthException","code":190,"error_subcode":463}}'; + $response = new FacebookResponse($this->request, $graphResponse, 401); + + $exception = $response->getThrownException(); + + $this->assertTrue($response->isError(), 'Expected Response to return an error.'); + $this->assertInstanceOf('Facebook\Exceptions\FacebookResponseException', $exception); + } +} diff --git a/lib/facebook-graph-sdk/tests/FacebookTest.php b/lib/facebook-graph-sdk/tests/FacebookTest.php new file mode 100644 index 0000000..3648665 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/FacebookTest.php @@ -0,0 +1,381 @@ + '1337', + 'app_secret' => 'foo_secret', + ]; + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testInstantiatingWithoutAppIdThrows() + { + // unset value so there is no fallback to test expected Exception + putenv(Facebook::APP_ID_ENV_NAME.'='); + $config = [ + 'app_secret' => 'foo_secret', + ]; + $fb = new Facebook($config); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testInstantiatingWithoutAppSecretThrows() + { + // unset value so there is no fallback to test expected Exception + putenv(Facebook::APP_SECRET_ENV_NAME.'='); + $config = [ + 'app_id' => 'foo_id', + ]; + $fb = new Facebook($config); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSettingAnInvalidHttpClientHandlerThrows() + { + $config = array_merge($this->config, [ + 'http_client_handler' => 'foo_handler', + ]); + $fb = new Facebook($config); + } + + public function testCurlHttpClientHandlerCanBeForced() + { + $config = array_merge($this->config, [ + 'http_client_handler' => 'curl' + ]); + $fb = new Facebook($config); + $this->assertInstanceOf( + 'Facebook\HttpClients\FacebookCurlHttpClient', + $fb->getClient()->getHttpClientHandler() + ); + } + + public function testStreamHttpClientHandlerCanBeForced() + { + $config = array_merge($this->config, [ + 'http_client_handler' => 'stream' + ]); + $fb = new Facebook($config); + $this->assertInstanceOf( + 'Facebook\HttpClients\FacebookStreamHttpClient', + $fb->getClient()->getHttpClientHandler() + ); + } + + public function testGuzzleHttpClientHandlerCanBeForced() + { + $config = array_merge($this->config, [ + 'http_client_handler' => 'guzzle' + ]); + $fb = new Facebook($config); + $this->assertInstanceOf( + 'Facebook\HttpClients\FacebookGuzzleHttpClient', + $fb->getClient()->getHttpClientHandler() + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSettingAnInvalidPersistentDataHandlerThrows() + { + $config = array_merge($this->config, [ + 'persistent_data_handler' => 'foo_handler', + ]); + $fb = new Facebook($config); + } + + public function testPersistentDataHandlerCanBeForced() + { + $config = array_merge($this->config, [ + 'persistent_data_handler' => 'memory' + ]); + $fb = new Facebook($config); + $this->assertInstanceOf( + 'Facebook\PersistentData\FacebookMemoryPersistentDataHandler', + $fb->getRedirectLoginHelper()->getPersistentDataHandler() + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSettingAnInvalidUrlHandlerThrows() + { + $config = array_merge($this->config, [ + 'url_detection_handler' => 'foo_handler', + ]); + $fb = new Facebook($config); + } + + public function testTheUrlHandlerWillDefaultToTheFacebookImplementation() + { + $fb = new Facebook($this->config); + $this->assertInstanceOf('Facebook\Url\FacebookUrlDetectionHandler', $fb->getUrlDetectionHandler()); + } + + public function testAnAccessTokenCanBeSetAsAString() + { + $fb = new Facebook($this->config); + $fb->setDefaultAccessToken('foo_token'); + $accessToken = $fb->getDefaultAccessToken(); + + $this->assertInstanceOf('Facebook\Authentication\AccessToken', $accessToken); + $this->assertEquals('foo_token', (string)$accessToken); + } + + public function testAnAccessTokenCanBeSetAsAnAccessTokenEntity() + { + $fb = new Facebook($this->config); + $fb->setDefaultAccessToken(new AccessToken('bar_token')); + $accessToken = $fb->getDefaultAccessToken(); + + $this->assertInstanceOf('Facebook\Authentication\AccessToken', $accessToken); + $this->assertEquals('bar_token', (string)$accessToken); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSettingAnInvalidPseudoRandomStringGeneratorThrows() + { + $config = array_merge($this->config, [ + 'pseudo_random_string_generator' => 'foo_generator', + ]); + new Facebook($config); + } + + public function testMcryptCsprgCanBeForced() + { + if (!function_exists('mcrypt_create_iv')) { + $this->markTestSkipped( + 'Mcrypt must be installed to test mcrypt_create_iv().' + ); + } + + $config = array_merge($this->config, [ + 'persistent_data_handler' => 'memory', // To keep session errors from happening + 'pseudo_random_string_generator' => 'mcrypt' + ]); + $fb = new Facebook($config); + $this->assertInstanceOf( + 'Facebook\PseudoRandomString\McryptPseudoRandomStringGenerator', + $fb->getRedirectLoginHelper()->getPseudoRandomStringGenerator() + ); + } + + public function testOpenSslCsprgCanBeForced() + { + if (!function_exists('openssl_random_pseudo_bytes')) { + $this->markTestSkipped( + 'The OpenSSL extension must be enabled to test openssl_random_pseudo_bytes().' + ); + } + + $config = array_merge($this->config, [ + 'persistent_data_handler' => 'memory', // To keep session errors from happening + 'pseudo_random_string_generator' => 'openssl' + ]); + $fb = new Facebook($config); + $this->assertInstanceOf( + 'Facebook\PseudoRandomString\OpenSslPseudoRandomStringGenerator', + $fb->getRedirectLoginHelper()->getPseudoRandomStringGenerator() + ); + } + + public function testUrandomCsprgCanBeForced() + { + if (ini_get('open_basedir')) { + $this->markTestSkipped( + 'Cannot test /dev/urandom generator due to open_basedir constraint.' + ); + } + + if (!is_readable('/dev/urandom')) { + $this->markTestSkipped( + '/dev/urandom not found or is not readable.' + ); + } + + $config = array_merge($this->config, [ + 'persistent_data_handler' => 'memory', // To keep session errors from happening + 'pseudo_random_string_generator' => 'urandom' + ]); + $fb = new Facebook($config); + $this->assertInstanceOf( + 'Facebook\PseudoRandomString\UrandomPseudoRandomStringGenerator', + $fb->getRedirectLoginHelper()->getPseudoRandomStringGenerator() + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testSettingAnAccessThatIsNotStringOrAccessTokenThrows() + { + $config = array_merge($this->config, [ + 'default_access_token' => 123, + ]); + $fb = new Facebook($config); + } + + public function testCreatingANewRequestWillDefaultToTheProperConfig() + { + $config = array_merge($this->config, [ + 'default_access_token' => 'foo_token', + 'enable_beta_mode' => true, + 'default_graph_version' => 'v1337', + ]); + $fb = new Facebook($config); + + $request = $fb->request('FOO_VERB', '/foo'); + $this->assertEquals('1337', $request->getApp()->getId()); + $this->assertEquals('foo_secret', $request->getApp()->getSecret()); + $this->assertEquals('foo_token', (string)$request->getAccessToken()); + $this->assertEquals('v1337', $request->getGraphVersion()); + $this->assertEquals( + FacebookClient::BASE_GRAPH_URL_BETA, + $fb->getClient()->getBaseGraphUrl() + ); + } + + public function testCanInjectCustomHandlers() + { + $config = array_merge($this->config, [ + 'http_client_handler' => new FooClientInterface(), + 'persistent_data_handler' => new FooPersistentDataInterface(), + 'url_detection_handler' => new FooUrlDetectionInterface(), + 'pseudo_random_string_generator' => new FooBarPseudoRandomStringGenerator(), + ]); + $fb = new Facebook($config); + + $this->assertInstanceOf( + 'Facebook\Tests\FooClientInterface', + $fb->getClient()->getHttpClientHandler() + ); + $this->assertInstanceOf( + 'Facebook\Tests\FooPersistentDataInterface', + $fb->getRedirectLoginHelper()->getPersistentDataHandler() + ); + $this->assertInstanceOf( + 'Facebook\Tests\FooUrlDetectionInterface', + $fb->getRedirectLoginHelper()->getUrlDetectionHandler() + ); + $this->assertInstanceOf( + 'Facebook\Tests\FooBarPseudoRandomStringGenerator', + $fb->getRedirectLoginHelper()->getPseudoRandomStringGenerator() + ); + } + + public function testPaginationReturnsProperResponse() + { + $config = array_merge($this->config, [ + 'http_client_handler' => new FooClientInterface(), + ]); + $fb = new Facebook($config); + + $request = new FacebookRequest($fb->getApp(), 'foo_token', 'GET'); + $graphEdge = new GraphEdge( + $request, + [], + [ + 'paging' => [ + 'cursors' => [ + 'after' => 'bar_after_cursor', + 'before' => 'bar_before_cursor', + ], + ] + ], + '/1337/photos', + '\Facebook\GraphNodes\GraphUser' + ); + + $nextPage = $fb->next($graphEdge); + $this->assertInstanceOf('Facebook\GraphNodes\GraphEdge', $nextPage); + $this->assertInstanceOf('Facebook\GraphNodes\GraphUser', $nextPage[0]); + $this->assertEquals('Foo', $nextPage[0]['name']); + + $lastResponse = $fb->getLastResponse(); + $this->assertInstanceOf('Facebook\FacebookResponse', $lastResponse); + $this->assertEquals(1337, $lastResponse->getHttpStatusCode()); + } +} diff --git a/lib/facebook-graph-sdk/tests/FacebookTestCredentials.php.dist b/lib/facebook-graph-sdk/tests/FacebookTestCredentials.php.dist new file mode 100644 index 0000000..e5b66f7 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/FacebookTestCredentials.php.dist @@ -0,0 +1,36 @@ +testFile = __DIR__ . '/../foo.txt'; + } + + public function testCanOpenAndReadAndCloseAFile() + { + $file = new FacebookFile($this->testFile); + $fileContents = $file->getContents(); + + $this->assertEquals('This is a text file used for testing. Let\'s dance.', $fileContents); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testTryingToOpenAFileThatDoesntExistsThrows() + { + new FacebookFile('does_not_exist.file'); + } +} diff --git a/lib/facebook-graph-sdk/tests/FileUpload/MimetypesTest.php b/lib/facebook-graph-sdk/tests/FileUpload/MimetypesTest.php new file mode 100644 index 0000000..c2092c1 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/FileUpload/MimetypesTest.php @@ -0,0 +1,55 @@ +assertEquals('text/x-php', Mimetypes::getInstance()->fromExtension('php')); + } + + public function testGetsFromFilename() + { + $this->assertEquals('text/x-php', Mimetypes::getInstance()->fromFilename(__FILE__)); + } + + public function testGetsFromCaseInsensitiveFilename() + { + $this->assertEquals('text/x-php', Mimetypes::getInstance()->fromFilename(strtoupper(__FILE__))); + } + + public function testReturnsNullWhenNoMatchFound() + { + $this->assertNull(Mimetypes::getInstance()->fromExtension('foobar')); + } +} diff --git a/lib/facebook-graph-sdk/tests/GraphNodes/AbstractGraphNode.php b/lib/facebook-graph-sdk/tests/GraphNodes/AbstractGraphNode.php new file mode 100644 index 0000000..1c4ce15 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/GraphNodes/AbstractGraphNode.php @@ -0,0 +1,51 @@ +responseMock = m::mock('\Facebook\FacebookResponse'); + } + + protected function makeFactoryWithData($data) + { + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($data); + + return new GraphNodeFactory($this->responseMock); + } +} diff --git a/lib/facebook-graph-sdk/tests/GraphNodes/CollectionTest.php b/lib/facebook-graph-sdk/tests/GraphNodes/CollectionTest.php new file mode 100755 index 0000000..af3eba8 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/GraphNodes/CollectionTest.php @@ -0,0 +1,125 @@ + 'bar']); + + $field = $graphNode->getField('foo'); + $this->assertEquals('bar', $field); + + // @todo v6: Remove this assertion + $property = $graphNode->getProperty('foo'); + $this->assertEquals('bar', $property); + } + + public function testAMissingPropertyWillReturnNull() + { + $graphNode = new Collection(['foo' => 'bar']); + $field = $graphNode->getField('baz'); + + $this->assertNull($field, 'Expected the property to return null.'); + } + + public function testAMissingPropertyWillReturnTheDefault() + { + $graphNode = new Collection(['foo' => 'bar']); + + $field = $graphNode->getField('baz', 'faz'); + $this->assertEquals('faz', $field); + + // @todo v6: Remove this assertion + $property = $graphNode->getProperty('baz', 'faz'); + $this->assertEquals('faz', $property); + } + + public function testTheKeysFromTheCollectionCanBeReturned() + { + $graphNode = new Collection([ + 'key1' => 'foo', + 'key2' => 'bar', + 'key3' => 'baz', + ]); + + $fieldNames = $graphNode->getFieldNames(); + $this->assertEquals(['key1', 'key2', 'key3'], $fieldNames); + + // @todo v6: Remove this assertion + $propertyNames = $graphNode->getPropertyNames(); + $this->assertEquals(['key1', 'key2', 'key3'], $propertyNames); + } + + public function testAnArrayCanBeInjectedViaTheConstructor() + { + $collection = new Collection(['foo', 'bar']); + $this->assertEquals(['foo', 'bar'], $collection->asArray()); + } + + public function testACollectionCanBeConvertedToProperJson() + { + $collection = new Collection(['foo', 'bar', 123]); + + $collectionAsString = $collection->asJson(); + + $this->assertEquals('["foo","bar",123]', $collectionAsString); + } + + public function testACollectionCanBeCounted() + { + $collection = new Collection(['foo', 'bar', 'baz']); + + $collectionCount = count($collection); + + $this->assertEquals(3, $collectionCount); + } + + public function testACollectionCanBeAccessedAsAnArray() + { + $collection = new Collection(['foo' => 'bar', 'faz' => 'baz']); + + $this->assertEquals('bar', $collection['foo']); + $this->assertEquals('baz', $collection['faz']); + } + + public function testACollectionCanBeIteratedOver() + { + $collection = new Collection(['foo' => 'bar', 'faz' => 'baz']); + + $this->assertInstanceOf('IteratorAggregate', $collection); + + $newArray = []; + + foreach ($collection as $k => $v) { + $newArray[$k] = $v; + } + + $this->assertEquals(['foo' => 'bar', 'faz' => 'baz'], $newArray); + } +} diff --git a/lib/facebook-graph-sdk/tests/GraphNodes/GraphAchievementTest.php b/lib/facebook-graph-sdk/tests/GraphNodes/GraphAchievementTest.php new file mode 100644 index 0000000..d5e276e --- /dev/null +++ b/lib/facebook-graph-sdk/tests/GraphNodes/GraphAchievementTest.php @@ -0,0 +1,117 @@ + '1337' + ]; + + $factory = $this->makeFactoryWithData($dataFromGraph); + $graphNode = $factory->makeGraphAchievement(); + + $id = $graphNode->getId(); + + $this->assertEquals($dataFromGraph['id'], $id); + } + + public function testTypeIsAlwaysString() + { + $dataFromGraph = [ + 'id' => '1337' + ]; + + $factory = $this->makeFactoryWithData($dataFromGraph); + $graphNode = $factory->makeGraphAchievement(); + + $type = $graphNode->getType(); + + $this->assertEquals('game.achievement', $type); + } + + public function testNoFeedStoryIsBoolean() + { + $dataFromGraph = [ + 'no_feed_story' => (rand(0, 1) == 1) + ]; + + $factory = $this->makeFactoryWithData($dataFromGraph); + $graphNode = $factory->makeGraphAchievement(); + + $isNoFeedStory = $graphNode->isNoFeedStory(); + + $this->assertTrue(is_bool($isNoFeedStory)); + } + + public function testDatesGetCastToDateTime() + { + $dataFromGraph = [ + 'publish_time' => '2014-07-15T03:54:34+0000' + ]; + + $factory = $this->makeFactoryWithData($dataFromGraph); + $graphNode = $factory->makeGraphAchievement(); + + $publishTime = $graphNode->getPublishTime(); + + $this->assertInstanceOf('DateTime', $publishTime); + } + + public function testFromGetsCastAsGraphUser() + { + $dataFromGraph = [ + 'from' => [ + 'id' => '1337', + 'name' => 'Foo McBar' + ] + ]; + + $factory = $this->makeFactoryWithData($dataFromGraph); + $graphNode = $factory->makeGraphAchievement(); + + $from = $graphNode->getFrom(); + + $this->assertInstanceOf('\Facebook\GraphNodes\GraphUser', $from); + } + + public function testApplicationGetsCastAsGraphApplication() + { + $dataFromGraph = [ + 'application' => [ + 'id' => '1337' + ] + ]; + + $factory = $this->makeFactoryWithData($dataFromGraph); + $graphNode = $factory->makeGraphAchievement(); + + $app = $graphNode->getApplication(); + + $this->assertInstanceOf('\Facebook\GraphNodes\GraphApplication', $app); + } +} diff --git a/lib/facebook-graph-sdk/tests/GraphNodes/GraphAlbumTest.php b/lib/facebook-graph-sdk/tests/GraphNodes/GraphAlbumTest.php new file mode 100644 index 0000000..f7a5521 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/GraphNodes/GraphAlbumTest.php @@ -0,0 +1,109 @@ +responseMock = m::mock('\\Facebook\\FacebookResponse'); + } + + public function testDatesGetCastToDateTime() + { + $dataFromGraph = [ + 'created_time' => '2014-07-15T03:54:34+0000', + 'updated_time' => '2014-07-12T01:24:09+0000', + 'id' => '123', + 'name' => 'Bar', + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphNode = $factory->makeGraphAlbum(); + + $createdTime = $graphNode->getCreatedTime(); + $updatedTime = $graphNode->getUpdatedTime(); + + $this->assertInstanceOf('DateTime', $createdTime); + $this->assertInstanceOf('DateTime', $updatedTime); + } + + public function testFromGetsCastAsGraphUser() + { + $dataFromGraph = [ + 'id' => '123', + 'from' => [ + 'id' => '1337', + 'name' => 'Foo McBar', + ], + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphNode = $factory->makeGraphAlbum(); + + $from = $graphNode->getFrom(); + + $this->assertInstanceOf('\\Facebook\\GraphNodes\\GraphUser', $from); + } + + public function testPlacePropertyWillGetCastAsGraphPageObject() + { + $dataFromGraph = [ + 'id' => '123', + 'name' => 'Foo Album', + 'place' => [ + 'id' => '1', + 'name' => 'For Bar Place', + ] + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphNode = $factory->makeGraphAlbum(); + + $place = $graphNode->getPlace(); + + $this->assertInstanceOf('\\Facebook\\GraphNodes\\GraphPage', $place); + } +} diff --git a/lib/facebook-graph-sdk/tests/GraphNodes/GraphEdgeTest.php b/lib/facebook-graph-sdk/tests/GraphNodes/GraphEdgeTest.php new file mode 100644 index 0000000..4e70f52 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/GraphNodes/GraphEdgeTest.php @@ -0,0 +1,120 @@ + 'https://graph.facebook.com/v7.12/998899/photos?pretty=0&limit=25&after=foo_after_cursor', + 'previous' => 'https://graph.facebook.com/v7.12/998899/photos?pretty=0&limit=25&before=foo_before_cursor', + ]; + protected $cursorPagination = [ + 'cursors' => [ + 'after' => 'bar_after_cursor', + 'before' => 'bar_before_cursor', + ], + ]; + + public function setUp() + { + $app = new FacebookApp('123', 'foo_app_secret'); + $this->request = new FacebookRequest( + $app, + 'foo_token', + 'GET', + '/me/photos?keep=me', + ['foo' => 'bar'], + 'foo_eTag', + 'v1337' + ); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testNonGetRequestsWillThrow() + { + $this->request->setMethod('POST'); + $graphEdge = new GraphEdge($this->request); + $graphEdge->validateForPagination(); + } + + public function testCanReturnGraphGeneratedPaginationEndpoints() + { + $graphEdge = new GraphEdge( + $this->request, + [], + ['paging' => $this->basePagination] + ); + $nextPage = $graphEdge->getPaginationUrl('next'); + $prevPage = $graphEdge->getPaginationUrl('previous'); + + $this->assertEquals('/998899/photos?pretty=0&limit=25&after=foo_after_cursor', $nextPage); + $this->assertEquals('/998899/photos?pretty=0&limit=25&before=foo_before_cursor', $prevPage); + } + + public function testCanGeneratePaginationEndpointsFromACursor() + { + $graphEdge = new GraphEdge( + $this->request, + [], + ['paging' => $this->cursorPagination], + '/1234567890/likes' + ); + $nextPage = $graphEdge->getPaginationUrl('next'); + $prevPage = $graphEdge->getPaginationUrl('previous'); + + $this->assertEquals('/1234567890/likes?access_token=foo_token&after=bar_after_cursor&appsecret_proof=857d5f035a894f16b4180f19966e055cdeab92d4d53017b13dccd6d43b6497af&foo=bar&keep=me', $nextPage); + $this->assertEquals('/1234567890/likes?access_token=foo_token&appsecret_proof=857d5f035a894f16b4180f19966e055cdeab92d4d53017b13dccd6d43b6497af&before=bar_before_cursor&foo=bar&keep=me', $prevPage); + } + + public function testCanInstantiateNewPaginationRequest() + { + $graphEdge = new GraphEdge( + $this->request, + [], + ['paging' => $this->cursorPagination], + '/1234567890/likes' + ); + $nextPage = $graphEdge->getNextPageRequest(); + $prevPage = $graphEdge->getPreviousPageRequest(); + + $this->assertInstanceOf('Facebook\FacebookRequest', $nextPage); + $this->assertInstanceOf('Facebook\FacebookRequest', $prevPage); + $this->assertNotSame($this->request, $nextPage); + $this->assertNotSame($this->request, $prevPage); + $this->assertEquals('/v1337/1234567890/likes?access_token=foo_token&after=bar_after_cursor&appsecret_proof=857d5f035a894f16b4180f19966e055cdeab92d4d53017b13dccd6d43b6497af&foo=bar&keep=me', $nextPage->getUrl()); + $this->assertEquals('/v1337/1234567890/likes?access_token=foo_token&appsecret_proof=857d5f035a894f16b4180f19966e055cdeab92d4d53017b13dccd6d43b6497af&before=bar_before_cursor&foo=bar&keep=me', $prevPage->getUrl()); + } +} diff --git a/lib/facebook-graph-sdk/tests/GraphNodes/GraphEventTest.php b/lib/facebook-graph-sdk/tests/GraphNodes/GraphEventTest.php new file mode 100644 index 0000000..98ccd85 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/GraphNodes/GraphEventTest.php @@ -0,0 +1,109 @@ +responseMock = m::mock('\Facebook\FacebookResponse'); + } + + public function testCoverGetsCastAsGraphCoverPhoto() + { + $dataFromGraph = [ + 'cover' => ['id' => '1337'] + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphObject = $factory->makeGraphEvent(); + + $cover = $graphObject->getCover(); + $this->assertInstanceOf('\Facebook\GraphNodes\GraphCoverPhoto', $cover); + } + + public function testPlaceGetsCastAsGraphPage() + { + $dataFromGraph = [ + 'place' => ['id' => '1337'] + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphObject = $factory->makeGraphEvent(); + + $place = $graphObject->getPlace(); + $this->assertInstanceOf('\Facebook\GraphNodes\GraphPage', $place); + } + + public function testPictureGetsCastAsGraphPicture() + { + $dataFromGraph = [ + 'picture' => ['id' => '1337'] + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphObject = $factory->makeGraphEvent(); + + $picture = $graphObject->getPicture(); + $this->assertInstanceOf('\Facebook\GraphNodes\GraphPicture', $picture); + } + + public function testParentGroupGetsCastAsGraphGroup() + { + $dataFromGraph = [ + 'parent_group' => ['id' => '1337'] + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphObject = $factory->makeGraphEvent(); + + $parentGroup = $graphObject->getParentGroup(); + $this->assertInstanceOf('\Facebook\GraphNodes\GraphGroup', $parentGroup); + } +} diff --git a/lib/facebook-graph-sdk/tests/GraphNodes/GraphGroupTest.php b/lib/facebook-graph-sdk/tests/GraphNodes/GraphGroupTest.php new file mode 100644 index 0000000..21893b5 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/GraphNodes/GraphGroupTest.php @@ -0,0 +1,75 @@ +responseMock = m::mock('\Facebook\FacebookResponse'); + } + + public function testCoverGetsCastAsGraphCoverPhoto() + { + $dataFromGraph = [ + 'cover' => ['id' => '1337'] + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphNode = $factory->makeGraphGroup(); + + $cover = $graphNode->getCover(); + $this->assertInstanceOf('\Facebook\GraphNodes\GraphCoverPhoto', $cover); + } + + public function testVenueGetsCastAsGraphLocation() + { + $dataFromGraph = [ + 'venue' => ['id' => '1337'] + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphNode = $factory->makeGraphGroup(); + + $venue = $graphNode->getVenue(); + $this->assertInstanceOf('\Facebook\GraphNodes\GraphLocation', $venue); + } +} diff --git a/lib/facebook-graph-sdk/tests/GraphNodes/GraphNodeFactoryTest.php b/lib/facebook-graph-sdk/tests/GraphNodes/GraphNodeFactoryTest.php new file mode 100644 index 0000000..7d2f023 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/GraphNodes/GraphNodeFactoryTest.php @@ -0,0 +1,437 @@ + '\Facebook\Tests\GraphNodes\MyFooSubClassGraphNode', + ]; +} + +class GraphNodeFactoryTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Facebook\FacebookRequest + */ + protected $request; + + public function setUp() + { + $app = new FacebookApp('123', 'foo_app_secret'); + $this->request = new FacebookRequest( + $app, + 'foo_token', + 'GET', + '/me/photos?keep=me', + ['foo' => 'bar'], + 'foo_eTag', + 'v1337' + ); + } + + public function testAValidGraphNodeResponseWillNotThrow() + { + $data = '{"id":"123","name":"foo"}'; + $res = new FacebookResponse($this->request, $data); + + $factory = new GraphNodeFactory($res); + $factory->validateResponseCastableAsGraphNode(); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testANonGraphNodeResponseWillThrow() + { + $data = '{"data":[{"id":"123","name":"foo"},{"id":"1337","name":"bar"}]}'; + $res = new FacebookResponse($this->request, $data); + + $factory = new GraphNodeFactory($res); + $factory->validateResponseCastableAsGraphNode(); + } + + public function testAValidGraphEdgeResponseWillNotThrow() + { + $data = '{"data":[{"id":"123","name":"foo"},{"id":"1337","name":"bar"}]}'; + $res = new FacebookResponse($this->request, $data); + + $factory = new GraphNodeFactory($res); + $factory->validateResponseCastableAsGraphEdge(); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testANonGraphEdgeResponseWillThrow() + { + $data = '{"id":"123","name":"foo"}'; + $res = new FacebookResponse($this->request, $data); + + $factory = new GraphNodeFactory($res); + $factory->validateResponseCastableAsGraphEdge(); + } + + public function testOnlyNumericArraysAreCastableAsAGraphEdge() + { + $shouldPassOne = GraphNodeFactory::isCastableAsGraphEdge([]); + $shouldPassTwo = GraphNodeFactory::isCastableAsGraphEdge(['foo', 'bar']); + $shouldFail = GraphNodeFactory::isCastableAsGraphEdge(['faz' => 'baz']); + + $this->assertTrue($shouldPassOne, 'Expected the given array to be castable as a GraphEdge.'); + $this->assertTrue($shouldPassTwo, 'Expected the given array to be castable as a GraphEdge.'); + $this->assertFalse($shouldFail, 'Expected the given array to not be castable as a GraphEdge.'); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testInvalidSubClassesWillThrow() + { + GraphNodeFactory::validateSubclass('FooSubClass'); + } + + public function testValidSubClassesWillNotThrow() + { + GraphNodeFactory::validateSubclass('\Facebook\GraphNodes\GraphNode'); + GraphNodeFactory::validateSubclass('\Facebook\GraphNodes\GraphAlbum'); + GraphNodeFactory::validateSubclass('\Facebook\Tests\GraphNodes\MyFooGraphNode'); + } + + public function testCastingAsASubClassObjectWillInstantiateTheSubClass() + { + $data = '{"id":"123","name":"foo"}'; + $res = new FacebookResponse($this->request, $data); + + $factory = new GraphNodeFactory($res); + $mySubClassObject = $factory->makeGraphNode('\Facebook\Tests\GraphNodes\MyFooGraphNode'); + + $this->assertInstanceOf('\Facebook\Tests\GraphNodes\MyFooGraphNode', $mySubClassObject); + } + + public function testASubClassMappingWillAutomaticallyInstantiateSubClass() + { + $data = '{"id":"123","name":"Foo Name","foo_object":{"id":"1337","name":"Should be sub classed!"}}'; + $res = new FacebookResponse($this->request, $data); + + $factory = new GraphNodeFactory($res); + $mySubClassObject = $factory->makeGraphNode('\Facebook\Tests\GraphNodes\MyFooGraphNode'); + $fooObject = $mySubClassObject->getField('foo_object'); + + $this->assertInstanceOf('\Facebook\Tests\GraphNodes\MyFooGraphNode', $mySubClassObject); + $this->assertInstanceOf('\Facebook\Tests\GraphNodes\MyFooSubClassGraphNode', $fooObject); + } + + public function testAnUnknownGraphNodeWillBeCastAsAGenericGraphNode() + { + $data = json_encode([ + 'id' => '123', + 'name' => 'Foo Name', + 'unknown_object' => [ + 'id' => '1337', + 'name' => 'Should be generic!', + ], + ]); + $res = new FacebookResponse($this->request, $data); + + $factory = new GraphNodeFactory($res); + + $mySubClassObject = $factory->makeGraphNode('\Facebook\Tests\GraphNodes\MyFooGraphNode'); + $unknownObject = $mySubClassObject->getField('unknown_object'); + + $this->assertInstanceOf('\Facebook\Tests\GraphNodes\MyFooGraphNode', $mySubClassObject); + $this->assertInstanceOf('\Facebook\GraphNodes\GraphNode', $unknownObject); + $this->assertNotInstanceOf('\Facebook\Tests\GraphNodes\MyFooGraphNode', $unknownObject); + } + + public function testAListFromGraphWillBeCastAsAGraphEdge() + { + $data = json_encode([ + 'data' => [ + [ + 'id' => '123', + 'name' => 'Foo McBar', + 'link' => 'http://facebook/foo', + ], + [ + 'id' => '1337', + 'name' => 'Bar McBaz', + 'link' => 'http://facebook/bar', + ], + ], + 'paging' => [ + 'next' => 'http://facebook/next', + 'previous' => 'http://facebook/prev', + ], + ]); + $res = new FacebookResponse($this->request, $data); + + $factory = new GraphNodeFactory($res); + $graphEdge = $factory->makeGraphEdge(); + $graphData = $graphEdge->asArray(); + + $this->assertInstanceOf('\Facebook\GraphNodes\GraphEdge', $graphEdge); + $this->assertEquals([ + 'id' => '123', + 'name' => 'Foo McBar', + 'link' => 'http://facebook/foo', + ], $graphData[0]); + $this->assertEquals([ + 'id' => '1337', + 'name' => 'Bar McBaz', + 'link' => 'http://facebook/bar', + ], $graphData[1]); + } + + public function testAGraphNodeWillBeCastAsAGraphNode() + { + $data = json_encode([ + 'id' => '123', + 'name' => 'Foo McBar', + 'link' => 'http://facebook/foo', + ]); + $res = new FacebookResponse($this->request, $data); + + $factory = new GraphNodeFactory($res); + $graphNode = $factory->makeGraphNode(); + $graphData = $graphNode->asArray(); + + $this->assertInstanceOf('\Facebook\GraphNodes\GraphNode', $graphNode); + $this->assertEquals([ + 'id' => '123', + 'name' => 'Foo McBar', + 'link' => 'http://facebook/foo', + ], $graphData); + } + + public function testAGraphNodeWithARootDataKeyWillBeCastAsAGraphNode() + { + $data = json_encode([ + 'data' => [ + 'id' => '123', + 'name' => 'Foo McBar', + 'link' => 'http://facebook/foo', + ], + ]); + + $res = new FacebookResponse($this->request, $data); + + $factory = new GraphNodeFactory($res); + $graphNode = $factory->makeGraphNode(); + $graphData = $graphNode->asArray(); + + $this->assertInstanceOf('\Facebook\GraphNodes\GraphNode', $graphNode); + $this->assertEquals([ + 'id' => '123', + 'name' => 'Foo McBar', + 'link' => 'http://facebook/foo', + ], $graphData); + } + + public function testAGraphEdgeWillBeCastRecursively() + { + $someUser = [ + 'id' => '123', + 'name' => 'Foo McBar', + ]; + $likesCollection = [ + 'data' => [ + [ + 'id' => '1', + 'name' => 'Sammy Kaye Powers', + 'is_sexy' => true, + ], + [ + 'id' => '2', + 'name' => 'Yassine Guedidi', + 'is_sexy' => true, + ], + [ + 'id' => '3', + 'name' => 'Fosco Marotto', + 'is_sexy' => true, + ], + [ + 'id' => '4', + 'name' => 'Foo McUgly', + 'is_sexy' => false, + ], + ], + 'paging' => [ + 'next' => 'http://facebook/next_likes', + 'previous' => 'http://facebook/prev_likes', + ], + ]; + $commentsCollection = [ + 'data' => [ + [ + 'id' => '42_1', + 'from' => $someUser, + 'message' => 'Foo comment.', + 'created_time' => '2014-07-15T03:54:34+0000', + 'likes' => $likesCollection, + ], + [ + 'id' => '42_2', + 'from' => $someUser, + 'message' => 'Bar comment.', + 'created_time' => '2014-07-15T04:11:24+0000', + 'likes' => $likesCollection, + ], + ], + 'paging' => [ + 'next' => 'http://facebook/next_comments', + 'previous' => 'http://facebook/prev_comments', + ], + ]; + $dataFromGraph = [ + 'data' => [ + [ + 'id' => '1337_1', + 'from' => $someUser, + 'story' => 'Some great foo story.', + 'likes' => $likesCollection, + 'comments' => $commentsCollection, + ], + [ + 'id' => '1337_2', + 'from' => $someUser, + 'to' => [ + 'data' => [$someUser], + ], + 'message' => 'Some great bar message.', + 'likes' => $likesCollection, + 'comments' => $commentsCollection, + ], + ], + 'paging' => [ + 'next' => 'http://facebook/next', + 'previous' => 'http://facebook/prev', + ], + ]; + $data = json_encode($dataFromGraph); + $res = new FacebookResponse($this->request, $data); + + $factory = new GraphNodeFactory($res); + $graphNode = $factory->makeGraphEdge(); + $this->assertInstanceOf('\Facebook\GraphNodes\GraphEdge', $graphNode); + + // Story + $storyObject = $graphNode[0]; + $this->assertInstanceOf('\Facebook\GraphNodes\GraphNode', $storyObject['from']); + $this->assertInstanceOf('\Facebook\GraphNodes\GraphEdge', $storyObject['likes']); + $this->assertInstanceOf('\Facebook\GraphNodes\GraphEdge', $storyObject['comments']); + + // Story Comments + $storyComments = $storyObject['comments']; + $firstStoryComment = $storyComments[0]; + $this->assertInstanceOf('\Facebook\GraphNodes\GraphNode', $firstStoryComment['from']); + + // Message + $messageObject = $graphNode[1]; + $this->assertInstanceOf('\Facebook\GraphNodes\GraphEdge', $messageObject['to']); + $toUsers = $messageObject['to']; + $this->assertInstanceOf('\Facebook\GraphNodes\GraphNode', $toUsers[0]); + } + + public function testAGraphEdgeWillGenerateTheProperParentGraphEdges() + { + $likesList = [ + 'data' => [ + [ + 'id' => '1', + 'name' => 'Sammy Kaye Powers', + ], + ], + 'paging' => [ + 'cursors' => [ + 'after' => 'like_after_cursor', + 'before' => 'like_before_cursor', + ], + ], + ]; + + $photosList = [ + 'data' => [ + [ + 'id' => '777', + 'name' => 'Foo Photo', + 'likes' => $likesList, + ], + ], + 'paging' => [ + 'cursors' => [ + 'after' => 'photo_after_cursor', + 'before' => 'photo_before_cursor', + ], + ], + ]; + + $data = json_encode([ + 'data' => [ + [ + 'id' => '111', + 'name' => 'Foo McBar', + 'likes' => $likesList, + 'photos' => $photosList, + ], + [ + 'id' => '222', + 'name' => 'Bar McBaz', + 'likes' => $likesList, + 'photos' => $photosList, + ], + ], + 'paging' => [ + 'next' => 'http://facebook/next', + 'previous' => 'http://facebook/prev', + ], + ]); + $res = new FacebookResponse($this->request, $data); + + $factory = new GraphNodeFactory($res); + $graphEdge = $factory->makeGraphEdge(); + $topGraphEdge = $graphEdge->getParentGraphEdge(); + $childGraphEdgeOne = $graphEdge[0]['likes']->getParentGraphEdge(); + $childGraphEdgeTwo = $graphEdge[1]['likes']->getParentGraphEdge(); + $childGraphEdgeThree = $graphEdge[1]['photos']->getParentGraphEdge(); + $childGraphEdgeFour = $graphEdge[1]['photos'][0]['likes']->getParentGraphEdge(); + + $this->assertNull($topGraphEdge); + $this->assertEquals('/111/likes', $childGraphEdgeOne); + $this->assertEquals('/222/likes', $childGraphEdgeTwo); + $this->assertEquals('/222/photos', $childGraphEdgeThree); + $this->assertEquals('/777/likes', $childGraphEdgeFour); + } +} diff --git a/lib/facebook-graph-sdk/tests/GraphNodes/GraphNodeTest.php b/lib/facebook-graph-sdk/tests/GraphNodes/GraphNodeTest.php new file mode 100644 index 0000000..50f58ae --- /dev/null +++ b/lib/facebook-graph-sdk/tests/GraphNodes/GraphNodeTest.php @@ -0,0 +1,138 @@ +asArray(); + + $this->assertEquals([], $backingData); + } + + public function testAGraphNodeCanInstantiateWithData() + { + $graphNode = new GraphNode(['foo' => 'bar']); + $backingData = $graphNode->asArray(); + + $this->assertEquals(['foo' => 'bar'], $backingData); + } + + public function testDatesThatShouldBeCastAsDateTimeObjectsAreDetected() + { + $graphNode = new GraphNode(); + + // Should pass + $shouldPass = $graphNode->isIso8601DateString('1985-10-26T01:21:00+0000'); + $this->assertTrue($shouldPass, 'Expected the valid ISO 8601 formatted date from Back To The Future to pass.'); + + $shouldPass = $graphNode->isIso8601DateString('1999-12-31'); + $this->assertTrue($shouldPass, 'Expected the valid ISO 8601 formatted date to party like it\'s 1999.'); + + $shouldPass = $graphNode->isIso8601DateString('2009-05-19T14:39Z'); + $this->assertTrue($shouldPass, 'Expected the valid ISO 8601 formatted date to pass.'); + + $shouldPass = $graphNode->isIso8601DateString('2014-W36'); + $this->assertTrue($shouldPass, 'Expected the valid ISO 8601 formatted date to pass.'); + + // Should fail + $shouldFail = $graphNode->isIso8601DateString('2009-05-19T14a39r'); + $this->assertFalse($shouldFail, 'Expected the invalid ISO 8601 format to fail.'); + + $shouldFail = $graphNode->isIso8601DateString('foo_time'); + $this->assertFalse($shouldFail, 'Expected the invalid ISO 8601 format to fail.'); + } + + public function testATimeStampCanBeConvertedToADateTimeObject() + { + $someTimeStampFromGraph = 1405547020; + $graphNode = new GraphNode(); + $dateTime = $graphNode->castToDateTime($someTimeStampFromGraph); + $prettyDate = $dateTime->format(\DateTime::RFC1036); + $timeStamp = $dateTime->getTimestamp(); + + $this->assertInstanceOf('DateTime', $dateTime); + $this->assertEquals('Wed, 16 Jul 14 23:43:40 +0200', $prettyDate); + $this->assertEquals(1405547020, $timeStamp); + } + + public function testAGraphDateStringCanBeConvertedToADateTimeObject() + { + $someDateStringFromGraph = '2014-07-15T03:44:53+0000'; + $graphNode = new GraphNode(); + $dateTime = $graphNode->castToDateTime($someDateStringFromGraph); + $prettyDate = $dateTime->format(\DateTime::RFC1036); + $timeStamp = $dateTime->getTimestamp(); + + $this->assertInstanceOf('DateTime', $dateTime); + $this->assertEquals('Tue, 15 Jul 14 03:44:53 +0000', $prettyDate); + $this->assertEquals(1405395893, $timeStamp); + } + + public function testUncastingAGraphNodeWillUncastTheDateTimeObject() + { + $collectionOne = new GraphNode(['foo', 'bar']); + $collectionTwo = new GraphNode([ + 'id' => '123', + 'date' => new \DateTime('2014-07-15T03:44:53+0000'), + 'some_collection' => $collectionOne, + ]); + + $uncastArray = $collectionTwo->uncastItems(); + + $this->assertEquals([ + 'id' => '123', + 'date' => '2014-07-15T03:44:53+0000', + 'some_collection' => ['foo', 'bar'], + ], $uncastArray); + } + + public function testGettingGraphNodeAsAnArrayWillNotUncastTheDateTimeObject() + { + $collection = new GraphNode([ + 'id' => '123', + 'date' => new \DateTime('2014-07-15T03:44:53+0000'), + ]); + + $collectionAsArray = $collection->asArray(); + + $this->assertInstanceOf('DateTime', $collectionAsArray['date']); + } + + public function testReturningACollectionAsJasonWillSafelyRepresentDateTimes() + { + $collection = new GraphNode([ + 'id' => '123', + 'date' => new \DateTime('2014-07-15T03:44:53+0000'), + ]); + + $collectionAsString = $collection->asJson(); + + $this->assertEquals('{"id":"123","date":"2014-07-15T03:44:53+0000"}', $collectionAsString); + } +} diff --git a/lib/facebook-graph-sdk/tests/GraphNodes/GraphObjectFactoryTest.php b/lib/facebook-graph-sdk/tests/GraphNodes/GraphObjectFactoryTest.php new file mode 100644 index 0000000..764503a --- /dev/null +++ b/lib/facebook-graph-sdk/tests/GraphNodes/GraphObjectFactoryTest.php @@ -0,0 +1,114 @@ +request = new FacebookRequest( + $app, + 'foo_token', + 'GET', + '/me/photos?keep=me', + ['foo' => 'bar'], + 'foo_eTag', + 'v1337' + ); + } + + public function testAGraphNodeWillBeCastAsAGraphNode() + { + $data = json_encode([ + 'id' => '123', + 'name' => 'Foo McBar', + 'link' => 'http://facebook/foo', + ]); + $res = new FacebookResponse($this->request, $data); + + $factory = new GraphObjectFactory($res); + $graphObject = $factory->makeGraphObject(); + $graphData = $graphObject->asArray(); + + $this->assertInstanceOf('\Facebook\GraphNodes\GraphObject', $graphObject); + $this->assertEquals([ + 'id' => '123', + 'name' => 'Foo McBar', + 'link' => 'http://facebook/foo', + ], $graphData); + } + + public function testAListFromGraphWillBeCastAsAGraphEdge() + { + $data = json_encode([ + 'data' => [ + [ + 'id' => '123', + 'name' => 'Foo McBar', + 'link' => 'http://facebook/foo', + ], + [ + 'id' => '1337', + 'name' => 'Bar McBaz', + 'link' => 'http://facebook/bar', + ], + ], + 'paging' => [ + 'next' => 'http://facebook/next', + 'previous' => 'http://facebook/prev', + ], + ]); + $res = new FacebookResponse($this->request, $data); + + $factory = new GraphObjectFactory($res); + $graphList = $factory->makeGraphList(); + $graphData = $graphList->asArray(); + + $this->assertInstanceOf('\Facebook\GraphNodes\GraphList', $graphList); + $this->assertEquals([ + 'id' => '123', + 'name' => 'Foo McBar', + 'link' => 'http://facebook/foo', + ], $graphData[0]); + $this->assertEquals([ + 'id' => '1337', + 'name' => 'Bar McBaz', + 'link' => 'http://facebook/bar', + ], $graphData[1]); + } +} diff --git a/lib/facebook-graph-sdk/tests/GraphNodes/GraphPageTest.php b/lib/facebook-graph-sdk/tests/GraphNodes/GraphPageTest.php new file mode 100644 index 0000000..a3a88e9 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/GraphNodes/GraphPageTest.php @@ -0,0 +1,95 @@ +responseMock = m::mock('\\Facebook\\FacebookResponse'); + } + + public function testPagePropertiesReturnGraphPageObjects() + { + $dataFromGraph = [ + 'id' => '123', + 'name' => 'Foo Page', + 'best_page' => [ + 'id' => '1', + 'name' => 'Bar Page', + ], + 'global_brand_parent_page' => [ + 'id' => '2', + 'name' => 'Faz Page', + ], + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphNode = $factory->makeGraphPage(); + + $bestPage = $graphNode->getBestPage(); + $globalBrandParentPage = $graphNode->getGlobalBrandParentPage(); + + $this->assertInstanceOf('\\Facebook\\GraphNodes\\GraphPage', $bestPage); + $this->assertInstanceOf('\\Facebook\\GraphNodes\\GraphPage', $globalBrandParentPage); + } + + public function testLocationPropertyWillGetCastAsGraphLocationObject() + { + $dataFromGraph = [ + 'id' => '123', + 'name' => 'Foo Page', + 'location' => [ + 'city' => 'Washington', + 'country' => 'United States', + 'latitude' => 38.881634205431, + 'longitude' => -77.029121075722, + 'state' => 'DC', + ], + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphNode = $factory->makeGraphPage(); + + $location = $graphNode->getLocation(); + + $this->assertInstanceOf('\\Facebook\\GraphNodes\\GraphLocation', $location); + } +} diff --git a/lib/facebook-graph-sdk/tests/GraphNodes/GraphSessionInfoTest.php b/lib/facebook-graph-sdk/tests/GraphNodes/GraphSessionInfoTest.php new file mode 100644 index 0000000..2960c28 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/GraphNodes/GraphSessionInfoTest.php @@ -0,0 +1,62 @@ +responseMock = m::mock('\\Facebook\\FacebookResponse'); + } + + public function testDatesGetCastToDateTime() + { + $dataFromGraph = [ + 'expires_at' => 123, + 'issued_at' => 1337, + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + + $graphNode = $factory->makeGraphSessionInfo(); + + $expires = $graphNode->getExpiresAt(); + $issuedAt = $graphNode->getIssuedAt(); + + $this->assertInstanceOf('DateTime', $expires); + $this->assertInstanceOf('DateTime', $issuedAt); + } +} diff --git a/lib/facebook-graph-sdk/tests/GraphNodes/GraphUserTest.php b/lib/facebook-graph-sdk/tests/GraphNodes/GraphUserTest.php new file mode 100644 index 0000000..ca75573 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/GraphNodes/GraphUserTest.php @@ -0,0 +1,140 @@ +responseMock = m::mock('\\Facebook\\FacebookResponse'); + } + + public function testDatesGetCastToDateTime() + { + $dataFromGraph = [ + 'birthday' => '1984-01-01', + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphNode = $factory->makeGraphUser(); + + $birthday = $graphNode->getBirthday(); + + $this->assertInstanceOf('DateTime', $birthday); + } + + public function testPagePropertiesWillGetCastAsGraphPageObjects() + { + $dataFromGraph = [ + 'id' => '123', + 'name' => 'Foo User', + 'hometown' => [ + 'id' => '1', + 'name' => 'Foo Place', + ], + 'location' => [ + 'id' => '2', + 'name' => 'Bar Place', + ], + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphNode = $factory->makeGraphUser(); + + $hometown = $graphNode->getHometown(); + $location = $graphNode->getLocation(); + + $this->assertInstanceOf('\\Facebook\\GraphNodes\\GraphPage', $hometown); + $this->assertInstanceOf('\\Facebook\\GraphNodes\\GraphPage', $location); + } + + public function testUserPropertiesWillGetCastAsGraphUserObjects() + { + $dataFromGraph = [ + 'id' => '123', + 'name' => 'Foo User', + 'significant_other' => [ + 'id' => '1337', + 'name' => 'Bar User', + ], + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphNode = $factory->makeGraphUser(); + + $significantOther = $graphNode->getSignificantOther(); + + $this->assertInstanceOf('\\Facebook\\GraphNodes\\GraphUser', $significantOther); + } + + public function testPicturePropertiesWillGetCastAsGraphPictureObjects() + { + $dataFromGraph = [ + 'id' => '123', + 'name' => 'Foo User', + 'picture' => [ + 'is_silhouette' => true, + 'url' => 'http://foo.bar', + 'width' => 200, + 'height' => 200, + ], + ]; + + $this->responseMock + ->shouldReceive('getDecodedBody') + ->once() + ->andReturn($dataFromGraph); + $factory = new GraphNodeFactory($this->responseMock); + $graphNode = $factory->makeGraphUser(); + + $Picture = $graphNode->getPicture(); + + $this->assertInstanceOf('\\Facebook\\GraphNodes\\GraphPicture', $Picture); + $this->assertTrue($Picture->isSilhouette()); + $this->assertEquals(200, $Picture->getWidth()); + $this->assertEquals(200, $Picture->getHeight()); + $this->assertEquals('http://foo.bar', $Picture->getUrl()); + } +} diff --git a/lib/facebook-graph-sdk/tests/Helpers/FacebookCanvasHelperTest.php b/lib/facebook-graph-sdk/tests/Helpers/FacebookCanvasHelperTest.php new file mode 100644 index 0000000..294440e --- /dev/null +++ b/lib/facebook-graph-sdk/tests/Helpers/FacebookCanvasHelperTest.php @@ -0,0 +1,53 @@ +helper = new FacebookCanvasHelper($app, new FacebookClient()); + } + + public function testSignedRequestDataCanBeRetrievedFromPostData() + { + $_POST['signed_request'] = $this->rawSignedRequestAuthorized; + + $rawSignedRequest = $this->helper->getRawSignedRequest(); + + $this->assertEquals($this->rawSignedRequestAuthorized, $rawSignedRequest); + } +} diff --git a/lib/facebook-graph-sdk/tests/Helpers/FacebookJavaScriptHelperTest.php b/lib/facebook-graph-sdk/tests/Helpers/FacebookJavaScriptHelperTest.php new file mode 100644 index 0000000..3f9cb88 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/Helpers/FacebookJavaScriptHelperTest.php @@ -0,0 +1,45 @@ +rawSignedRequestAuthorized; + + $app = new FacebookApp('123', 'foo_app_secret'); + $helper = new FacebookJavaScriptHelper($app, new FacebookClient()); + + $rawSignedRequest = $helper->getRawSignedRequest(); + + $this->assertEquals($this->rawSignedRequestAuthorized, $rawSignedRequest); + } +} diff --git a/lib/facebook-graph-sdk/tests/Helpers/FacebookPageTabHelperTest.php b/lib/facebook-graph-sdk/tests/Helpers/FacebookPageTabHelperTest.php new file mode 100644 index 0000000..a4b06c1 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/Helpers/FacebookPageTabHelperTest.php @@ -0,0 +1,46 @@ +rawSignedRequestAuthorized; + + $app = new FacebookApp('123', 'foo_app_secret'); + $helper = new FacebookPageTabHelper($app, new FacebookClient()); + + $this->assertFalse($helper->isAdmin()); + $this->assertEquals('42', $helper->getPageId()); + $this->assertEquals('42', $helper->getPageData('id')); + $this->assertEquals('default', $helper->getPageData('foo', 'default')); + } +} diff --git a/lib/facebook-graph-sdk/tests/Helpers/FacebookRedirectLoginHelperTest.php b/lib/facebook-graph-sdk/tests/Helpers/FacebookRedirectLoginHelperTest.php new file mode 100644 index 0000000..faa4647 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/Helpers/FacebookRedirectLoginHelperTest.php @@ -0,0 +1,140 @@ +persistentDataHandler = new FacebookMemoryPersistentDataHandler(); + + $app = new FacebookApp('123', 'foo_app_secret'); + $oAuth2Client = new FooRedirectLoginOAuth2Client($app, new FacebookClient(), 'v1337'); + $this->redirectLoginHelper = new FacebookRedirectLoginHelper($oAuth2Client, $this->persistentDataHandler); + } + + public function testLoginURL() + { + $scope = ['foo', 'bar']; + $loginUrl = $this->redirectLoginHelper->getLoginUrl(self::REDIRECT_URL, $scope); + + $expectedUrl = 'https://www.facebook.com/v1337/dialog/oauth?'; + $this->assertTrue(strpos($loginUrl, $expectedUrl) === 0, 'Unexpected base login URL returned from getLoginUrl().'); + + $params = [ + 'client_id' => '123', + 'redirect_uri' => self::REDIRECT_URL, + 'state' => $this->persistentDataHandler->get('state'), + 'sdk' => 'php-sdk-' . Facebook::VERSION, + 'scope' => implode(',', $scope), + ]; + foreach ($params as $key => $value) { + $this->assertContains($key . '=' . urlencode($value), $loginUrl); + } + } + + public function testLogoutURL() + { + $logoutUrl = $this->redirectLoginHelper->getLogoutUrl('foo_token', self::REDIRECT_URL); + $expectedUrl = 'https://www.facebook.com/logout.php?'; + $this->assertTrue(strpos($logoutUrl, $expectedUrl) === 0, 'Unexpected base logout URL returned from getLogoutUrl().'); + + $params = [ + 'next' => self::REDIRECT_URL, + 'access_token' => 'foo_token', + ]; + foreach ($params as $key => $value) { + $this->assertTrue( + strpos($logoutUrl, $key . '=' . urlencode($value)) !== false + ); + } + } + + public function testAnAccessTokenCanBeObtainedFromRedirect() + { + $this->persistentDataHandler->set('state', 'foo_state'); + $_GET['state'] = 'foo_state'; + $_GET['code'] = 'foo_code'; + + $accessToken = $this->redirectLoginHelper->getAccessToken(self::REDIRECT_URL); + + $this->assertEquals('foo_token_from_code|foo_code|' . self::REDIRECT_URL, (string)$accessToken); + } + + public function testACustomCsprsgCanBeInjected() + { + $app = new FacebookApp('123', 'foo_app_secret'); + $accessTokenClient = new FooRedirectLoginOAuth2Client($app, new FacebookClient(), 'v1337'); + $fooPrsg = new FooPseudoRandomStringGenerator(); + $helper = new FacebookRedirectLoginHelper($accessTokenClient, $this->persistentDataHandler, null, $fooPrsg); + + $loginUrl = $helper->getLoginUrl(self::REDIRECT_URL); + + $this->assertContains('state=csprs123', $loginUrl); + } + + public function testThePseudoRandomStringGeneratorWillAutoDetectCsprsg() + { + $this->assertInstanceOf( + 'Facebook\PseudoRandomString\PseudoRandomStringGeneratorInterface', + $this->redirectLoginHelper->getPseudoRandomStringGenerator() + ); + } +} diff --git a/lib/facebook-graph-sdk/tests/Helpers/FacebookSignedRequestFromInputHelperTest.php b/lib/facebook-graph-sdk/tests/Helpers/FacebookSignedRequestFromInputHelperTest.php new file mode 100644 index 0000000..d9bd803 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/Helpers/FacebookSignedRequestFromInputHelperTest.php @@ -0,0 +1,113 @@ +getParams(); + $rawResponse = json_encode([ + 'access_token' => 'foo_access_token_from:' . $params['code'], + ]); + + return new FacebookResponse($request, $rawResponse, 200); + } +} + +class FacebookSignedRequestFromInputHelperTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var FooSignedRequestHelper + */ + protected $helper; + + public $rawSignedRequestAuthorizedWithAccessToken = 'vdZXlVEQ5NTRRTFvJ7Jeo_kP4SKnBDvbNP0fEYKS0Sg=.eyJvYXV0aF90b2tlbiI6ImZvb190b2tlbiIsImFsZ29yaXRobSI6IkhNQUMtU0hBMjU2IiwiaXNzdWVkX2F0IjoxNDAyNTUxMDMxLCJ1c2VyX2lkIjoiMTIzIn0='; + public $rawSignedRequestAuthorizedWithCode = 'oBtmZlsFguNQvGRETDYQQu1-PhwcArgbBBEK4urbpRA=.eyJjb2RlIjoiZm9vX2NvZGUiLCJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImlzc3VlZF9hdCI6MTQwNjMxMDc1MiwidXNlcl9pZCI6IjEyMyJ9'; + public $rawSignedRequestUnauthorized = 'KPlyhz-whtYAhHWr15N5TkbS_avz-2rUJFpFkfXKC88=.eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsImlzc3VlZF9hdCI6MTQwMjU1MTA4Nn0='; + + public function setUp() + { + $app = new FacebookApp('123', 'foo_app_secret'); + $this->helper = new FooSignedRequestHelper($app, new FooSignedRequestHelperFacebookClient()); + } + + public function testSignedRequestDataCanBeRetrievedFromPostData() + { + $_POST['signed_request'] = 'foo_signed_request'; + + $rawSignedRequest = $this->helper->getRawSignedRequestFromPost(); + + $this->assertEquals('foo_signed_request', $rawSignedRequest); + } + + public function testSignedRequestDataCanBeRetrievedFromCookieData() + { + $_COOKIE['fbsr_123'] = 'foo_signed_request'; + + $rawSignedRequest = $this->helper->getRawSignedRequestFromCookie(); + + $this->assertEquals('foo_signed_request', $rawSignedRequest); + } + + public function testAccessTokenWillBeNullWhenAUserHasNotYetAuthorizedTheApp() + { + $this->helper->instantiateSignedRequest($this->rawSignedRequestUnauthorized); + $accessToken = $this->helper->getAccessToken(); + + $this->assertNull($accessToken); + } + + public function testAnAccessTokenCanBeInstantiatedWhenRedirectReturnsAnAccessToken() + { + $this->helper->instantiateSignedRequest($this->rawSignedRequestAuthorizedWithAccessToken); + $accessToken = $this->helper->getAccessToken(); + + $this->assertInstanceOf('Facebook\Authentication\AccessToken', $accessToken); + $this->assertEquals('foo_token', $accessToken->getValue()); + } + + public function testAnAccessTokenCanBeInstantiatedWhenRedirectReturnsACode() + { + $this->helper->instantiateSignedRequest($this->rawSignedRequestAuthorizedWithCode); + $accessToken = $this->helper->getAccessToken(); + + $this->assertInstanceOf('Facebook\Authentication\AccessToken', $accessToken); + $this->assertEquals('foo_access_token_from:foo_code', $accessToken->getValue()); + } +} diff --git a/lib/facebook-graph-sdk/tests/Http/GraphRawResponseTest.php b/lib/facebook-graph-sdk/tests/Http/GraphRawResponseTest.php new file mode 100644 index 0000000..ecad26f --- /dev/null +++ b/lib/facebook-graph-sdk/tests/Http/GraphRawResponseTest.php @@ -0,0 +1,82 @@ + '"9d86b21aa74d74e574bbb35ba13524a52deb96e3"', + 'Content-Type' => 'text/javascript; charset=UTF-8', + 'X-FB-Rev' => '9244768', + 'Date' => 'Mon, 19 May 2014 18:37:17 GMT', + 'X-FB-Debug' => '02QQiffE7JG2rV6i/Agzd0gI2/OOQ2lk5UW0=', + 'Access-Control-Allow-Origin' => '*', + ]; + + public function testCanSetTheHeadersFromAnArray() + { + $myHeaders = [ + 'foo' => 'bar', + 'baz' => 'faz', + ]; + $response = new GraphRawResponse($myHeaders, ''); + $headers = $response->getHeaders(); + + $this->assertEquals($myHeaders, $headers); + } + + public function testCanSetTheHeadersFromAString() + { + $response = new GraphRawResponse($this->fakeRawHeader, ''); + $headers = $response->getHeaders(); + $httpResponseCode = $response->getHttpResponseCode(); + + $this->assertEquals($this->fakeHeadersAsArray, $headers); + $this->assertEquals(200, $httpResponseCode); + } + + public function testWillIgnoreProxyHeaders() + { + $response = new GraphRawResponse($this->fakeRawProxyHeader . $this->fakeRawHeader, ''); + $headers = $response->getHeaders(); + $httpResponseCode = $response->getHttpResponseCode(); + + $this->assertEquals($this->fakeHeadersAsArray, $headers); + $this->assertEquals(200, $httpResponseCode); + } +} diff --git a/lib/facebook-graph-sdk/tests/Http/RequestBodyMultipartTest.php b/lib/facebook-graph-sdk/tests/Http/RequestBodyMultipartTest.php new file mode 100644 index 0000000..267cc49 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/Http/RequestBodyMultipartTest.php @@ -0,0 +1,111 @@ + 'bar', + 'scawy_vawues' => '@FooBar is a real twitter handle.', + ], [], 'foo_boundary'); + $body = $message->getBody(); + + $expectedBody = "--foo_boundary\r\n"; + $expectedBody .= "Content-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n"; + $expectedBody .= "--foo_boundary\r\n"; + $expectedBody .= "Content-Disposition: form-data; name=\"scawy_vawues\"\r\n\r\n@FooBar is a real twitter handle.\r\n"; + $expectedBody .= "--foo_boundary--\r\n"; + + $this->assertEquals($expectedBody, $body); + } + + public function testCanProperlyEncodeFilesAndParams() + { + $file = new FacebookFile(__DIR__ . '/../foo.txt'); + $message = new RequestBodyMultipart([ + 'foo' => 'bar', + ], [ + 'foo_file' => $file, + ], 'foo_boundary'); + $body = $message->getBody(); + + $expectedBody = "--foo_boundary\r\n"; + $expectedBody .= "Content-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n"; + $expectedBody .= "--foo_boundary\r\n"; + $expectedBody .= "Content-Disposition: form-data; name=\"foo_file\"; filename=\"foo.txt\"\r\n"; + $expectedBody .= "Content-Type: text/plain\r\n\r\nThis is a text file used for testing. Let's dance.\r\n"; + $expectedBody .= "--foo_boundary--\r\n"; + + $this->assertEquals($expectedBody, $body); + } + + public function testSupportsMultidimensionalParams() + { + $message = new RequestBodyMultipart([ + 'foo' => 'bar', + 'faz' => [1,2,3], + 'targeting' => [ + 'countries' => 'US,GB', + 'age_min' => 13, + ], + 'call_to_action' => [ + 'type' => 'LEARN_MORE', + 'value' => [ + 'link' => 'http://example.com', + 'sponsorship' => [ + 'image' => 'http://example.com/bar.jpg', + ], + ], + ], + ], [], 'foo_boundary'); + $body = $message->getBody(); + + $expectedBody = "--foo_boundary\r\n"; + $expectedBody .= "Content-Disposition: form-data; name=\"foo\"\r\n\r\nbar\r\n"; + $expectedBody .= "--foo_boundary\r\n"; + $expectedBody .= "Content-Disposition: form-data; name=\"faz[0]\"\r\n\r\n1\r\n"; + $expectedBody .= "--foo_boundary\r\n"; + $expectedBody .= "Content-Disposition: form-data; name=\"faz[1]\"\r\n\r\n2\r\n"; + $expectedBody .= "--foo_boundary\r\n"; + $expectedBody .= "Content-Disposition: form-data; name=\"faz[2]\"\r\n\r\n3\r\n"; + $expectedBody .= "--foo_boundary\r\n"; + $expectedBody .= "Content-Disposition: form-data; name=\"targeting[countries]\"\r\n\r\nUS,GB\r\n"; + $expectedBody .= "--foo_boundary\r\n"; + $expectedBody .= "Content-Disposition: form-data; name=\"targeting[age_min]\"\r\n\r\n13\r\n"; + $expectedBody .= "--foo_boundary\r\n"; + $expectedBody .= "Content-Disposition: form-data; name=\"call_to_action[type]\"\r\n\r\nLEARN_MORE\r\n"; + $expectedBody .= "--foo_boundary\r\n"; + $expectedBody .= "Content-Disposition: form-data; name=\"call_to_action[value][link]\"\r\n\r\nhttp://example.com\r\n"; + $expectedBody .= "--foo_boundary\r\n"; + $expectedBody .= "Content-Disposition: form-data; name=\"call_to_action[value][sponsorship][image]\"\r\n\r\nhttp://example.com/bar.jpg\r\n"; + $expectedBody .= "--foo_boundary--\r\n"; + + $this->assertEquals($expectedBody, $body); + } +} diff --git a/lib/facebook-graph-sdk/tests/Http/RequestUrlEncodedTest.php b/lib/facebook-graph-sdk/tests/Http/RequestUrlEncodedTest.php new file mode 100644 index 0000000..3e22912 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/Http/RequestUrlEncodedTest.php @@ -0,0 +1,64 @@ + 'bar', + 'scawy_vawues' => '@FooBar is a real twitter handle.', + ]); + $body = $message->getBody(); + + $this->assertEquals('foo=bar&scawy_vawues=%40FooBar+is+a+real+twitter+handle.', $body); + } + + public function testSupportsMultidimensionalParams() + { + $message = new RequestBodyUrlEncoded([ + 'foo' => 'bar', + 'faz' => [1,2,3], + 'targeting' => [ + 'countries' => 'US,GB', + 'age_min' => 13, + ], + 'call_to_action' => [ + 'type' => 'LEARN_MORE', + 'value' => [ + 'link' => 'http://example.com', + 'sponsorship' => [ + 'image' => 'http://example.com/bar.jpg', + ], + ], + ], + ]); + $body = $message->getBody(); + + $this->assertEquals('foo=bar&faz%5B0%5D=1&faz%5B1%5D=2&faz%5B2%5D=3&targeting%5Bcountries%5D=US%2CGB&targeting%5Bage_min%5D=13&call_to_action%5Btype%5D=LEARN_MORE&call_to_action%5Bvalue%5D%5Blink%5D=http%3A%2F%2Fexample.com&call_to_action%5Bvalue%5D%5Bsponsorship%5D%5Bimage%5D=http%3A%2F%2Fexample.com%2Fbar.jpg', $body); + } +} diff --git a/lib/facebook-graph-sdk/tests/HttpClients/AbstractTestHttpClient.php b/lib/facebook-graph-sdk/tests/HttpClients/AbstractTestHttpClient.php new file mode 100644 index 0000000..269b235 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/HttpClients/AbstractTestHttpClient.php @@ -0,0 +1,60 @@ + '"9d86b21aa74d74e574bbb35ba13524a52deb96e3"', + 'Content-Type' => 'text/javascript; charset=UTF-8', + 'X-FB-Rev' => '9244768', + 'Pragma' => 'no-cache', + 'Expires' => 'Sat, 01 Jan 2000 00:00:00 GMT', + 'Connection' => 'close', + 'Date' => 'Mon, 19 May 2014 18:37:17 GMT', + 'X-FB-Debug' => '02QQiffE7JG2rV6i/Agzd0gI2/OOQ2lk5UW0=', + 'Content-Length' => '29', + 'Cache-Control' => 'private, no-cache, no-store, must-revalidate', + 'Access-Control-Allow-Origin' => '*', + ]; +} diff --git a/lib/facebook-graph-sdk/tests/HttpClients/FacebookCurlHttpClientTest.php b/lib/facebook-graph-sdk/tests/HttpClients/FacebookCurlHttpClientTest.php new file mode 100644 index 0000000..4cf31d3 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/HttpClients/FacebookCurlHttpClientTest.php @@ -0,0 +1,334 @@ +curlMock = m::mock('Facebook\HttpClients\FacebookCurl'); + $this->curlClient = new FacebookCurlHttpClient($this->curlMock); + } + + public function testCanOpenGetCurlConnection() + { + $this->curlMock + ->shouldReceive('init') + ->once() + ->andReturn(null); + $this->curlMock + ->shouldReceive('setoptArray') + ->with(m::on(function ($arg) { + + // array_diff() will sometimes trigger error on child-arrays + if (['X-Foo-Header: X-Bar'] !== $arg[CURLOPT_HTTPHEADER]) { + return false; + } + unset($arg[CURLOPT_HTTPHEADER]); + + $caInfo = array_diff($arg, [ + CURLOPT_CUSTOMREQUEST => 'GET', + CURLOPT_URL => 'http://foo.com', + CURLOPT_CONNECTTIMEOUT => 10, + CURLOPT_TIMEOUT => 123, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_SSL_VERIFYHOST => 2, + CURLOPT_SSL_VERIFYPEER => true, + ]); + + if (count($caInfo) !== 1) { + return false; + } + + if (1 !== preg_match('/.+\/certs\/DigiCertHighAssuranceEVRootCA\.pem$/', $caInfo[CURLOPT_CAINFO])) { + return false; + } + + return true; + })) + ->once() + ->andReturn(null); + + $this->curlClient->openConnection('http://foo.com', 'GET', 'foo_body', ['X-Foo-Header' => 'X-Bar'], 123); + } + + public function testCanOpenCurlConnectionWithPostBody() + { + $this->curlMock + ->shouldReceive('init') + ->once() + ->andReturn(null); + $this->curlMock + ->shouldReceive('setoptArray') + ->with(m::on(function ($arg) { + + // array_diff() will sometimes trigger error on child-arrays + if ([] !== $arg[CURLOPT_HTTPHEADER]) { + return false; + } + unset($arg[CURLOPT_HTTPHEADER]); + + $caInfo = array_diff($arg, [ + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_URL => 'http://bar.com', + CURLOPT_CONNECTTIMEOUT => 10, + CURLOPT_TIMEOUT => 60, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_SSL_VERIFYHOST => 2, + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_POSTFIELDS => 'baz=bar', + ]); + + if (count($caInfo) !== 1) { + return false; + } + + if (1 !== preg_match('/.+\/certs\/DigiCertHighAssuranceEVRootCA\.pem$/', $caInfo[CURLOPT_CAINFO])) { + return false; + } + + return true; + })) + ->once() + ->andReturn(null); + + $this->curlClient->openConnection('http://bar.com', 'POST', 'baz=bar', [], 60); + } + + public function testCanCloseConnection() + { + $this->curlMock + ->shouldReceive('close') + ->once() + ->andReturn(null); + + $this->curlClient->closeConnection(); + } + + public function testIsolatesTheHeaderAndBody() + { + $this->curlMock + ->shouldReceive('getinfo') + ->with(CURLINFO_HEADER_SIZE) + ->once() + ->andReturn(strlen($this->fakeRawHeader)); + $this->curlMock + ->shouldReceive('version') + ->once() + ->andReturn(['version_number' => self::CURL_VERSION_STABLE]); + $this->curlMock + ->shouldReceive('exec') + ->once() + ->andReturn($this->fakeRawHeader . $this->fakeRawBody); + + $this->curlClient->sendRequest(); + list($rawHeader, $rawBody) = $this->curlClient->extractResponseHeadersAndBody(); + + $this->assertEquals($rawHeader, trim($this->fakeRawHeader)); + $this->assertEquals($rawBody, $this->fakeRawBody); + } + + public function testProperlyHandlesProxyHeaders() + { + $rawHeader = $this->fakeRawProxyHeader . $this->fakeRawHeader; + $this->curlMock + ->shouldReceive('getinfo') + ->with(CURLINFO_HEADER_SIZE) + ->once() + ->andReturn(mb_strlen($rawHeader)); + $this->curlMock + ->shouldReceive('version') + ->once() + ->andReturn(['version_number' => self::CURL_VERSION_STABLE]); + $this->curlMock + ->shouldReceive('exec') + ->once() + ->andReturn($rawHeader . $this->fakeRawBody); + + $this->curlClient->sendRequest(); + list($rawHeaders, $rawBody) = $this->curlClient->extractResponseHeadersAndBody(); + + $this->assertEquals($rawHeaders, trim($rawHeader)); + $this->assertEquals($rawBody, $this->fakeRawBody); + } + + public function testProperlyHandlesProxyHeadersWithCurlBug() + { + $rawHeader = $this->fakeRawProxyHeader . $this->fakeRawHeader; + $this->curlMock + ->shouldReceive('getinfo') + ->with(CURLINFO_HEADER_SIZE) + ->once() + ->andReturn(mb_strlen($this->fakeRawHeader)); // Mimic bug that doesn't count proxy header + $this->curlMock + ->shouldReceive('version') + ->once() + ->andReturn(['version_number' => self::CURL_VERSION_BUGGY]); + $this->curlMock + ->shouldReceive('exec') + ->once() + ->andReturn($rawHeader . $this->fakeRawBody); + + $this->curlClient->sendRequest(); + list($rawHeaders, $rawBody) = $this->curlClient->extractResponseHeadersAndBody(); + + $this->assertEquals($rawHeaders, trim($rawHeader)); + $this->assertEquals($rawBody, $this->fakeRawBody); + } + + public function testProperlyHandlesProxyHeadersWithCurlBug2() + { + $rawHeader = $this->fakeRawProxyHeader2 . $this->fakeRawHeader; + $this->curlMock + ->shouldReceive('getinfo') + ->with(CURLINFO_HEADER_SIZE) + ->once() + ->andReturn(mb_strlen($this->fakeRawHeader)); // Mimic bug that doesn't count proxy header + $this->curlMock + ->shouldReceive('version') + ->once() + ->andReturn(['version_number' => self::CURL_VERSION_BUGGY]); + $this->curlMock + ->shouldReceive('exec') + ->once() + ->andReturn($rawHeader . $this->fakeRawBody); + + $this->curlClient->sendRequest(); + list($rawHeaders, $rawBody) = $this->curlClient->extractResponseHeadersAndBody(); + + $this->assertEquals($rawHeaders, trim($rawHeader)); + $this->assertEquals($rawBody, $this->fakeRawBody); + } + + public function testProperlyHandlesRedirectHeaders() + { + $rawHeader = $this->fakeRawRedirectHeader . $this->fakeRawHeader; + $this->curlMock + ->shouldReceive('getinfo') + ->with(CURLINFO_HEADER_SIZE) + ->once() + ->andReturn(mb_strlen($rawHeader)); + $this->curlMock + ->shouldReceive('version') + ->once() + ->andReturn(['version_number' => self::CURL_VERSION_STABLE]); + $this->curlMock + ->shouldReceive('exec') + ->once() + ->andReturn($rawHeader . $this->fakeRawBody); + + $this->curlClient->sendRequest(); + list($rawHeaders, $rawBody) = $this->curlClient->extractResponseHeadersAndBody(); + + $this->assertEquals($rawHeaders, trim($rawHeader)); + $this->assertEquals($rawBody, $this->fakeRawBody); + } + + public function testCanSendNormalRequest() + { + $this->curlMock + ->shouldReceive('init') + ->once() + ->andReturn(null); + $this->curlMock + ->shouldReceive('setoptArray') + ->once() + ->andReturn(null); + $this->curlMock + ->shouldReceive('exec') + ->once() + ->andReturn($this->fakeRawHeader . $this->fakeRawBody); + $this->curlMock + ->shouldReceive('errno') + ->once() + ->andReturn(null); + $this->curlMock + ->shouldReceive('getinfo') + ->with(CURLINFO_HEADER_SIZE) + ->once() + ->andReturn(mb_strlen($this->fakeRawHeader)); + $this->curlMock + ->shouldReceive('version') + ->once() + ->andReturn(['version_number' => self::CURL_VERSION_STABLE]); + $this->curlMock + ->shouldReceive('close') + ->once() + ->andReturn(null); + + $response = $this->curlClient->send('http://foo.com/', 'GET', '', [], 60); + + $this->assertInstanceOf('Facebook\Http\GraphRawResponse', $response); + $this->assertEquals($this->fakeRawBody, $response->getBody()); + $this->assertEquals($this->fakeHeadersAsArray, $response->getHeaders()); + $this->assertEquals(200, $response->getHttpResponseCode()); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testThrowsExceptionOnClientError() + { + $this->curlMock + ->shouldReceive('init') + ->once() + ->andReturn(null); + $this->curlMock + ->shouldReceive('setoptArray') + ->once() + ->andReturn(null); + $this->curlMock + ->shouldReceive('exec') + ->once() + ->andReturn(false); + $this->curlMock + ->shouldReceive('errno') + ->once() + ->andReturn(123); + $this->curlMock + ->shouldReceive('error') + ->once() + ->andReturn('Foo error'); + + $this->curlClient->send('http://foo.com/', 'GET', '', [], 60); + } +} diff --git a/lib/facebook-graph-sdk/tests/HttpClients/FacebookGuzzleHttpClientTest.php b/lib/facebook-graph-sdk/tests/HttpClients/FacebookGuzzleHttpClientTest.php new file mode 100644 index 0000000..12eb36a --- /dev/null +++ b/lib/facebook-graph-sdk/tests/HttpClients/FacebookGuzzleHttpClientTest.php @@ -0,0 +1,143 @@ +guzzleMock = m::mock('GuzzleHttp\Client'); + $this->guzzleClient = new FacebookGuzzleHttpClient($this->guzzleMock); + } + + public function testCanSendNormalRequest() + { + $request = new Request('GET', 'http://foo.com'); + + $body = Stream::factory($this->fakeRawBody); + $response = new Response(200, $this->fakeHeadersAsArray, $body); + + $this->guzzleMock + ->shouldReceive('createRequest') + ->once() + ->with('GET', 'http://foo.com/', m::on(function ($arg) { + + // array_diff_assoc() will sometimes trigger error on child-arrays + if (['X-foo' => 'bar'] !== $arg['headers']) { + return false; + } + unset($arg['headers']); + + $caInfo = array_diff_assoc($arg, [ + 'body' => 'foo_body', + 'timeout' => 123, + 'connect_timeout' => 10, + ]); + + if (count($caInfo) !== 1) { + return false; + } + + if (1 !== preg_match('/.+\/certs\/DigiCertHighAssuranceEVRootCA\.pem$/', $caInfo['verify'])) { + return false; + } + + return true; + })) + ->andReturn($request); + $this->guzzleMock + ->shouldReceive('send') + ->once() + ->with($request) + ->andReturn($response); + + $response = $this->guzzleClient->send('http://foo.com/', 'GET', 'foo_body', ['X-foo' => 'bar'], 123); + + $this->assertInstanceOf('Facebook\Http\GraphRawResponse', $response); + $this->assertEquals($this->fakeRawBody, $response->getBody()); + $this->assertEquals($this->fakeHeadersAsArray, $response->getHeaders()); + $this->assertEquals(200, $response->getHttpResponseCode()); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testThrowsExceptionOnClientError() + { + $request = new Request('GET', 'http://foo.com'); + + $this->guzzleMock + ->shouldReceive('createRequest') + ->once() + ->with('GET', 'http://foo.com/', m::on(function ($arg) { + + // array_diff_assoc() will sometimes trigger error on child-arrays + if ([] !== $arg['headers']) { + return false; + } + unset($arg['headers']); + + $caInfo = array_diff_assoc($arg, [ + 'body' => 'foo_body', + 'timeout' => 60, + 'connect_timeout' => 10, + ]); + + if (count($caInfo) !== 1) { + return false; + } + + if (1 !== preg_match('/.+\/certs\/DigiCertHighAssuranceEVRootCA\.pem$/', $caInfo['verify'])) { + return false; + } + + return true; + })) + ->andReturn($request); + $this->guzzleMock + ->shouldReceive('send') + ->once() + ->with($request) + ->andThrow(new RequestException('Foo', $request)); + + $this->guzzleClient->send('http://foo.com/', 'GET', 'foo_body', [], 60); + } +} diff --git a/lib/facebook-graph-sdk/tests/HttpClients/FacebookStreamHttpClientTest.php b/lib/facebook-graph-sdk/tests/HttpClients/FacebookStreamHttpClientTest.php new file mode 100644 index 0000000..9102b08 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/HttpClients/FacebookStreamHttpClientTest.php @@ -0,0 +1,134 @@ +streamMock = m::mock('Facebook\HttpClients\FacebookStream'); + $this->streamClient = new FacebookStreamHttpClient($this->streamMock); + } + + public function testCanCompileHeader() + { + $headers = [ + 'X-foo' => 'bar', + 'X-bar' => 'faz', + ]; + $header = $this->streamClient->compileHeader($headers); + $this->assertEquals("X-foo: bar\r\nX-bar: faz", $header); + } + + public function testCanSendNormalRequest() + { + $this->streamMock + ->shouldReceive('streamContextCreate') + ->once() + ->with(m::on(function ($arg) { + if (!isset($arg['http']) || !isset($arg['ssl'])) { + return false; + } + + if ($arg['http'] !== [ + 'method' => 'GET', + 'header' => 'X-foo: bar', + 'content' => 'foo_body', + 'timeout' => 123, + 'ignore_errors' => true, + ] + ) { + return false; + } + + $caInfo = array_diff_assoc($arg['ssl'], [ + 'verify_peer' => true, + 'verify_peer_name' => true, + 'allow_self_signed' => true, + ]); + + if (count($caInfo) !== 1) { + return false; + } + + if (1 !== preg_match('/.+\/certs\/DigiCertHighAssuranceEVRootCA\.pem$/', $caInfo['cafile'])) { + return false; + } + + return true; + })) + ->andReturn(null); + $this->streamMock + ->shouldReceive('getResponseHeaders') + ->once() + ->andReturn(explode("\n", trim($this->fakeRawHeader))); + $this->streamMock + ->shouldReceive('fileGetContents') + ->once() + ->with('http://foo.com/') + ->andReturn($this->fakeRawBody); + + $response = $this->streamClient->send('http://foo.com/', 'GET', 'foo_body', ['X-foo' => 'bar'], 123); + + $this->assertInstanceOf('Facebook\Http\GraphRawResponse', $response); + $this->assertEquals($this->fakeRawBody, $response->getBody()); + $this->assertEquals($this->fakeHeadersAsArray, $response->getHeaders()); + $this->assertEquals(200, $response->getHttpResponseCode()); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testThrowsExceptionOnClientError() + { + $this->streamMock + ->shouldReceive('streamContextCreate') + ->once() + ->andReturn(null); + $this->streamMock + ->shouldReceive('getResponseHeaders') + ->once() + ->andReturn(null); + $this->streamMock + ->shouldReceive('fileGetContents') + ->once() + ->with('http://foo.com/') + ->andReturn(false); + + $this->streamClient->send('http://foo.com/', 'GET', 'foo_body', [], 60); + } +} diff --git a/lib/facebook-graph-sdk/tests/PersistentData/FacebookMemoryPersistentDataHandlerTest.php b/lib/facebook-graph-sdk/tests/PersistentData/FacebookMemoryPersistentDataHandlerTest.php new file mode 100644 index 0000000..2b09d29 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/PersistentData/FacebookMemoryPersistentDataHandlerTest.php @@ -0,0 +1,46 @@ +set('foo', 'bar'); + $value = $handler->get('foo'); + + $this->assertEquals('bar', $value); + } + + public function testGettingAValueThatDoesntExistWillReturnNull() + { + $handler = new FacebookMemoryPersistentDataHandler(); + $value = $handler->get('does_not_exist'); + + $this->assertNull($value); + } +} diff --git a/lib/facebook-graph-sdk/tests/PersistentData/FacebookSessionPersistentDataHandlerTest.php b/lib/facebook-graph-sdk/tests/PersistentData/FacebookSessionPersistentDataHandlerTest.php new file mode 100644 index 0000000..e21d366 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/PersistentData/FacebookSessionPersistentDataHandlerTest.php @@ -0,0 +1,62 @@ +set('foo', 'bar'); + + $this->assertEquals('bar', $_SESSION['FBRLH_foo']); + } + + public function testCanGetAValue() + { + $_SESSION['FBRLH_faz'] = 'baz'; + $handler = new FacebookSessionPersistentDataHandler($enableSessionCheck = false); + $value = $handler->get('faz'); + + $this->assertEquals('baz', $value); + } + + public function testGettingAValueThatDoesntExistWillReturnNull() + { + $handler = new FacebookSessionPersistentDataHandler($enableSessionCheck = false); + $value = $handler->get('does_not_exist'); + + $this->assertNull($value); + } +} diff --git a/lib/facebook-graph-sdk/tests/PseudoRandomString/McryptPseudoRandomStringGeneratorTest.php b/lib/facebook-graph-sdk/tests/PseudoRandomString/McryptPseudoRandomStringGeneratorTest.php new file mode 100644 index 0000000..a45a3cf --- /dev/null +++ b/lib/facebook-graph-sdk/tests/PseudoRandomString/McryptPseudoRandomStringGeneratorTest.php @@ -0,0 +1,44 @@ +markTestSkipped( + 'Mcrypt must be installed to test mcrypt_create_iv().' + ); + } + + $prsg = new McryptPseudoRandomStringGenerator(); + $randomString = $prsg->getPseudoRandomString(10); + + $this->assertEquals(1, preg_match('/^([0-9a-f]+)$/', $randomString)); + $this->assertEquals(10, mb_strlen($randomString)); + } +} diff --git a/lib/facebook-graph-sdk/tests/PseudoRandomString/OpenSslPseudoRandomStringGeneratorTest.php b/lib/facebook-graph-sdk/tests/PseudoRandomString/OpenSslPseudoRandomStringGeneratorTest.php new file mode 100644 index 0000000..c740d0b --- /dev/null +++ b/lib/facebook-graph-sdk/tests/PseudoRandomString/OpenSslPseudoRandomStringGeneratorTest.php @@ -0,0 +1,44 @@ +markTestSkipped( + 'The OpenSSL extension must be enabled to test openssl_random_pseudo_bytes().' + ); + } + + $prsg = new OpenSslPseudoRandomStringGenerator(); + $randomString = $prsg->getPseudoRandomString(10); + + $this->assertEquals(1, preg_match('/^([0-9a-f]+)$/', $randomString)); + $this->assertEquals(10, mb_strlen($randomString)); + } +} diff --git a/lib/facebook-graph-sdk/tests/PseudoRandomString/PseudoRandomStringGeneratorTraitTest.php b/lib/facebook-graph-sdk/tests/PseudoRandomString/PseudoRandomStringGeneratorTraitTest.php new file mode 100644 index 0000000..ea3a1f8 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/PseudoRandomString/PseudoRandomStringGeneratorTraitTest.php @@ -0,0 +1,52 @@ +validateLength('foo_len'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testALengthThatIsNotAtLeastOneCharacterWillThrow() + { + $prsg = new MyFooBarPseudoRandomStringGenerator(); + $prsg->validateLength(0); + } +} diff --git a/lib/facebook-graph-sdk/tests/PseudoRandomString/UrandomPseudoRandomStringGeneratorTest.php b/lib/facebook-graph-sdk/tests/PseudoRandomString/UrandomPseudoRandomStringGeneratorTest.php new file mode 100644 index 0000000..9e12a58 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/PseudoRandomString/UrandomPseudoRandomStringGeneratorTest.php @@ -0,0 +1,50 @@ +markTestSkipped( + 'Cannot test /dev/urandom generator due to open_basedir constraint.' + ); + } + + if (!is_readable('/dev/urandom')) { + $this->markTestSkipped( + '/dev/urandom not found or is not readable.' + ); + } + + $prsg = new UrandomPseudoRandomStringGenerator(); + $randomString = $prsg->getPseudoRandomString(10); + + $this->assertEquals(1, preg_match('/^([0-9a-f]+)$/', $randomString)); + $this->assertEquals(10, mb_strlen($randomString)); + } +} diff --git a/lib/facebook-graph-sdk/tests/SignedRequestTest.php b/lib/facebook-graph-sdk/tests/SignedRequestTest.php new file mode 100644 index 0000000..247600f --- /dev/null +++ b/lib/facebook-graph-sdk/tests/SignedRequestTest.php @@ -0,0 +1,139 @@ + 'foo_token', + 'algorithm' => 'HMAC-SHA256', + 'issued_at' => 321, + 'code' => 'foo_code', + 'state' => 'foo_state', + 'user_id' => 123, + 'foo' => 'bar', + ]; + + public function setUp() + { + $this->app = new FacebookApp('123', 'foo_app_secret'); + } + + public function testAValidSignedRequestCanBeCreated() + { + $sr = new SignedRequest($this->app); + $rawSignedRequest = $sr->make($this->payloadData); + + $srTwo = new SignedRequest($this->app, $rawSignedRequest); + $payload = $srTwo->getPayload(); + + $expectedRawSignedRequest = $this->rawSignature . '.' . $this->rawPayload; + $this->assertEquals($expectedRawSignedRequest, $rawSignedRequest); + $this->assertEquals($this->payloadData, $payload); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testInvalidSignedRequestsWillFailFormattingValidation() + { + new SignedRequest($this->app, 'invalid_signed_request'); + } + + public function testBase64EncodingIsUrlSafe() + { + $sr = new SignedRequest($this->app); + $encodedData = $sr->base64UrlEncode('aijkoprstADIJKLOPQTUVX1256!)]-:;"<>?.|~'); + + $this->assertEquals('YWlqa29wcnN0QURJSktMT1BRVFVWWDEyNTYhKV0tOjsiPD4_Lnx-', $encodedData); + } + + public function testAUrlSafeBase64EncodedStringCanBeDecoded() + { + $sr = new SignedRequest($this->app); + $decodedData = $sr->base64UrlDecode('YWlqa29wcnN0QURJSktMT1BRVFVWWDEyNTYhKV0tOjsiPD4/Lnx+'); + + $this->assertEquals('aijkoprstADIJKLOPQTUVX1256!)]-:;"<>?.|~', $decodedData); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testAnImproperlyEncodedSignatureWillThrowAnException() + { + new SignedRequest($this->app, 'foo_sig.' . $this->rawPayload); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testAnImproperlyEncodedPayloadWillThrowAnException() + { + new SignedRequest($this->app, $this->rawSignature . '.foo_payload'); + } + + /** + * @expectedException \Facebook\Exceptions\FacebookSDKException + */ + public function testNonApprovedAlgorithmsWillThrowAnException() + { + $signedRequestData = $this->payloadData; + $signedRequestData['algorithm'] = 'FOO-ALGORITHM'; + + $sr = new SignedRequest($this->app); + $rawSignedRequest = $sr->make($signedRequestData); + + new SignedRequest($this->app, $rawSignedRequest); + } + + public function testAsRawSignedRequestCanBeValidatedAndDecoded() + { + $rawSignedRequest = $this->rawSignature . '.' . $this->rawPayload; + $sr = new SignedRequest($this->app, $rawSignedRequest); + + $this->assertEquals($this->payloadData, $sr->getPayload()); + } + + public function testARawSignedRequestCanBeValidatedAndDecoded() + { + $rawSignedRequest = $this->rawSignature . '.' . $this->rawPayload; + $sr = new SignedRequest($this->app, $rawSignedRequest); + + $this->assertEquals($sr->getPayload(), $this->payloadData); + $this->assertEquals($sr->getRawSignedRequest(), $rawSignedRequest); + $this->assertEquals(123, $sr->getUserId()); + $this->assertTrue($sr->hasOAuthData()); + } +} diff --git a/lib/facebook-graph-sdk/tests/Url/FacebookUrlDetectionHandlerTest.php b/lib/facebook-graph-sdk/tests/Url/FacebookUrlDetectionHandlerTest.php new file mode 100644 index 0000000..c3127ef --- /dev/null +++ b/lib/facebook-graph-sdk/tests/Url/FacebookUrlDetectionHandlerTest.php @@ -0,0 +1,134 @@ + 'foo.bar', + 'SERVER_PORT' => '80', + 'REQUEST_URI' => '/baz?foo=123', + ]; + + $urlHandler = new FacebookUrlDetectionHandler(); + $currentUri = $urlHandler->getCurrentUrl(); + + $this->assertEquals('http://foo.bar/baz?foo=123', $currentUri); + } + + public function testProperlyGeneratesSecureUrlFromCommonScenario() + { + $_SERVER = [ + 'HTTP_HOST' => 'foo.bar', + 'SERVER_PORT' => '443', + 'REQUEST_URI' => '/baz?foo=123', + ]; + + $urlHandler = new FacebookUrlDetectionHandler(); + $currentUri = $urlHandler->getCurrentUrl(); + + $this->assertEquals('https://foo.bar/baz?foo=123', $currentUri); + } + + public function testProperlyGeneratesUrlFromProxy() + { + $_SERVER = [ + 'HTTP_X_FORWARDED_PORT' => '80', + 'HTTP_X_FORWARDED_PROTO' => 'http', + 'HTTP_HOST' => 'foo.bar', + 'SERVER_PORT' => '80', + 'REQUEST_URI' => '/baz?foo=123', + ]; + + $urlHandler = new FacebookUrlDetectionHandler(); + $currentUri = $urlHandler->getCurrentUrl(); + + $this->assertEquals('http://foo.bar/baz?foo=123', $currentUri); + } + + public function testProperlyGeneratesSecureUrlFromProxy() + { + $_SERVER = [ + 'HTTP_X_FORWARDED_PORT' => '443', + 'HTTP_X_FORWARDED_PROTO' => 'https', + 'HTTP_HOST' => 'foo.bar', + 'SERVER_PORT' => '80', + 'REQUEST_URI' => '/baz?foo=123', + ]; + + $urlHandler = new FacebookUrlDetectionHandler(); + $currentUri = $urlHandler->getCurrentUrl(); + + $this->assertEquals('https://foo.bar/baz?foo=123', $currentUri); + } + + public function testProperlyGeneratesUrlWithCustomPort() + { + $_SERVER = [ + 'HTTP_HOST' => 'foo.bar', + 'SERVER_PORT' => '1337', + 'REQUEST_URI' => '/foo.php', + ]; + + $urlHandler = new FacebookUrlDetectionHandler(); + $currentUri = $urlHandler->getCurrentUrl(); + + $this->assertEquals('http://foo.bar:1337/foo.php', $currentUri); + } + + public function testProperlyGeneratesSecureUrlWithCustomPort() + { + $_SERVER = [ + 'HTTP_HOST' => 'foo.bar', + 'SERVER_PORT' => '1337', + 'REQUEST_URI' => '/foo.php', + 'HTTPS' => 'On', + ]; + + $urlHandler = new FacebookUrlDetectionHandler(); + $currentUri = $urlHandler->getCurrentUrl(); + + $this->assertEquals('https://foo.bar:1337/foo.php', $currentUri); + } + + public function testProperlyGeneratesUrlWithCustomPortFromProxy() + { + $_SERVER = [ + 'HTTP_X_FORWARDED_PORT' => '8888', + 'HTTP_X_FORWARDED_PROTO' => 'http', + 'HTTP_HOST' => 'foo.bar', + 'SERVER_PORT' => '80', + 'REQUEST_URI' => '/foo.php', + ]; + + $urlHandler = new FacebookUrlDetectionHandler(); + $currentUri = $urlHandler->getCurrentUrl(); + + $this->assertEquals('http://foo.bar:8888/foo.php', $currentUri); + } +} diff --git a/lib/facebook-graph-sdk/tests/Url/FacebookUrlManipulatorTest.php b/lib/facebook-graph-sdk/tests/Url/FacebookUrlManipulatorTest.php new file mode 100644 index 0000000..c58e2b3 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/Url/FacebookUrlManipulatorTest.php @@ -0,0 +1,217 @@ +assertEquals($expectedCleanUrl, $currentUri); + } + + public function provideUris() + { + return [ + [ + 'http://localhost/something?state=0000&foo=bar&code=abcd', + 'http://localhost/something?foo=bar', + ], + [ + 'https://localhost/something?state=0000&foo=bar&code=abcd', + 'https://localhost/something?foo=bar', + ], + [ + 'http://localhost/something?state=0000&foo=bar&error=abcd&error_reason=abcd&error_description=abcd&error_code=1', + 'http://localhost/something?foo=bar', + ], + [ + 'https://localhost/something?state=0000&foo=bar&error=abcd&error_reason=abcd&error_description=abcd&error_code=1', + 'https://localhost/something?foo=bar', + ], + [ + 'http://localhost/something?state=0000&foo=bar&error=abcd', + 'http://localhost/something?foo=bar', + ], + [ + 'https://localhost/something?state=0000&foo=bar&error=abcd', + 'https://localhost/something?foo=bar', + ], + [ + 'https://localhost:1337/something?state=0000&foo=bar&error=abcd', + 'https://localhost:1337/something?foo=bar', + ], + [ + 'https://localhost:1337/something?state=0000&code=foo', + 'https://localhost:1337/something', + ], + [ + 'https://localhost/something/?state=0000&code=foo&foo=bar', + 'https://localhost/something/?foo=bar', + ], + [ + 'https://localhost/something/?state=0000&code=foo', + 'https://localhost/something/', + ], + ]; + } + + public function testGracefullyHandlesUrlAppending() + { + $params = []; + $url = 'https://www.foo.com/'; + $processed_url = FacebookUrlManipulator::appendParamsToUrl($url, $params); + $this->assertEquals('https://www.foo.com/', $processed_url); + + $params = [ + 'access_token' => 'foo', + ]; + $url = 'https://www.foo.com/'; + $processed_url = FacebookUrlManipulator::appendParamsToUrl($url, $params); + $this->assertEquals('https://www.foo.com/?access_token=foo', $processed_url); + + $params = [ + 'access_token' => 'foo', + 'bar' => 'baz', + ]; + $url = 'https://www.foo.com/?foo=bar'; + $processed_url = FacebookUrlManipulator::appendParamsToUrl($url, $params); + $this->assertEquals('https://www.foo.com/?access_token=foo&bar=baz&foo=bar', $processed_url); + + $params = [ + 'access_token' => 'foo', + ]; + $url = 'https://www.foo.com/?foo=bar&access_token=bar'; + $processed_url = FacebookUrlManipulator::appendParamsToUrl($url, $params); + $this->assertEquals('https://www.foo.com/?access_token=bar&foo=bar', $processed_url); + } + + public function testSlashesAreProperlyPrepended() + { + $slashTestOne = FacebookUrlManipulator::forceSlashPrefix('foo'); + $slashTestTwo = FacebookUrlManipulator::forceSlashPrefix('/foo'); + $slashTestThree = FacebookUrlManipulator::forceSlashPrefix('foo/bar'); + $slashTestFour = FacebookUrlManipulator::forceSlashPrefix('/foo/bar'); + $slashTestFive = FacebookUrlManipulator::forceSlashPrefix(null); + $slashTestSix = FacebookUrlManipulator::forceSlashPrefix(''); + + $this->assertEquals('/foo', $slashTestOne); + $this->assertEquals('/foo', $slashTestTwo); + $this->assertEquals('/foo/bar', $slashTestThree); + $this->assertEquals('/foo/bar', $slashTestFour); + $this->assertEquals(null, $slashTestFive); + $this->assertEquals('', $slashTestSix); + } + + public function testParamsCanBeReturnedAsArray() + { + $paramsOne = FacebookUrlManipulator::getParamsAsArray('/foo'); + $paramsTwo = FacebookUrlManipulator::getParamsAsArray('/foo?one=1&two=2'); + $paramsThree = FacebookUrlManipulator::getParamsAsArray('https://www.foo.com'); + $paramsFour = FacebookUrlManipulator::getParamsAsArray('https://www.foo.com/?'); + $paramsFive = FacebookUrlManipulator::getParamsAsArray('https://www.foo.com/?foo=bar'); + + $this->assertEquals([], $paramsOne); + $this->assertEquals(['one' => '1', 'two' => '2'], $paramsTwo); + $this->assertEquals([], $paramsThree); + $this->assertEquals([], $paramsFour); + $this->assertEquals(['foo' => 'bar'], $paramsFive); + } + + /** + * @dataProvider provideMergableEndpoints + */ + public function testParamsCanBeMergedOntoUrlProperly($urlOne, $urlTwo, $expected) + { + $result = FacebookUrlManipulator::mergeUrlParams($urlOne, $urlTwo); + + $this->assertEquals($result, $expected); + } + + public function provideMergableEndpoints() + { + return [ + [ + 'https://www.foo.com/?foo=ignore_foo&dance=fun', + '/me?foo=keep_foo', + '/me?dance=fun&foo=keep_foo', + ], + [ + 'https://www.bar.com?', + 'https://foo.com?foo=bar', + 'https://foo.com?foo=bar', + ], + [ + 'you', + 'me', + 'me', + ], + [ + '/1234?swing=fun', + '/1337?bar=baz&west=coast', + '/1337?bar=baz&swing=fun&west=coast', + ], + ]; + } + + public function testGraphUrlsCanBeTrimmed() + { + $fullGraphUrl = 'https://graph.facebook.com/'; + $baseGraphUrl = FacebookUrlManipulator::baseGraphUrlEndpoint($fullGraphUrl); + $this->assertEquals('/', $baseGraphUrl); + + $fullGraphUrl = 'https://graph.facebook.com/v1.0/'; + $baseGraphUrl = FacebookUrlManipulator::baseGraphUrlEndpoint($fullGraphUrl); + $this->assertEquals('/', $baseGraphUrl); + + $fullGraphUrl = 'https://graph.facebook.com/me'; + $baseGraphUrl = FacebookUrlManipulator::baseGraphUrlEndpoint($fullGraphUrl); + $this->assertEquals('/me', $baseGraphUrl); + + $fullGraphUrl = 'https://graph.beta.facebook.com/me'; + $baseGraphUrl = FacebookUrlManipulator::baseGraphUrlEndpoint($fullGraphUrl); + $this->assertEquals('/me', $baseGraphUrl); + + $fullGraphUrl = 'https://whatever-they-want.facebook.com/v2.1/me'; + $baseGraphUrl = FacebookUrlManipulator::baseGraphUrlEndpoint($fullGraphUrl); + $this->assertEquals('/me', $baseGraphUrl); + + $fullGraphUrl = 'https://graph.facebook.com/v5.301/1233?foo=bar'; + $baseGraphUrl = FacebookUrlManipulator::baseGraphUrlEndpoint($fullGraphUrl); + $this->assertEquals('/1233?foo=bar', $baseGraphUrl); + } +} diff --git a/lib/facebook-graph-sdk/tests/bootstrap.php b/lib/facebook-graph-sdk/tests/bootstrap.php new file mode 100644 index 0000000..4b04836 --- /dev/null +++ b/lib/facebook-graph-sdk/tests/bootstrap.php @@ -0,0 +1,33 @@ +