├── art └── pinterest-php.jpg ├── tests └── Pinterest │ ├── fixtures │ └── cat.jpg │ ├── responses │ ├── post_v1_me_following_users_a22f421c007ca367591446448669e24c.json │ ├── post_v1_me_following_boards_acb09c511e44c8e8ee18a6582be246f4.json │ ├── delete_v1_boards_314196580192887528_d41d8cd98f00b204e9800998ecf8427e.json │ ├── delete_v1_me_following_users_engagord41d8cd98f00b204e9800998ecf8427e.json │ ├── delete_v1_me_following_boards_engagor_engagor-in-the-newsd41d8cd98f00b204e9800998ecf8427e.json │ ├── get_v1_me_following_boards_f540825c41d09d337b65d1bf46a19fe8.json │ ├── get_v1_me_following_users_e7f00147b20fff1c562ea111852e576e.json │ ├── delete_v1_pins_314196511500433394_d41d8cd98f00b204e9800998ecf8427e.json │ ├── delete_v1_pins_314196511500433395_d41d8cd98f00b204e9800998ecf8427e.json │ ├── delete_v1_pins_314196511500433396_d41d8cd98f00b204e9800998ecf8427e.json │ ├── delete_v1_pins_314196511500433397_d41d8cd98f00b204e9800998ecf8427e.json │ ├── get_v1_me_e7f00147b20fff1c562ea111852e576e.json │ ├── get_v1_users_hanso1_e7f00147b20fff1c562ea111852e576e.json │ ├── get_v1_users_314196648911734959_e7f00147b20fff1c562ea111852e576e.json │ ├── post_v1_boards_70b08b1fbb08f54340a2b9e381d51dc3.json │ ├── patch_v1_boards_314196580192887528_7a9e411987c7b78dd18291e11169f9e4.json │ ├── get_v1_boards_314196580192658592_f540825c41d09d337b65d1bf46a19fe8.json │ ├── get_v1_pins_314196511498502249_0b8461cd5574fc97b8ab9b836a092dfe.json │ ├── post_v1_pins_26f04d82b6b8e731738b394a6c510356.json │ ├── post_v1_pins_5bbd4d62e330b15253bc4e74c5e3bcfc.json │ ├── post_v1_pins_ed6785fd1638af7c6826c483ffe742fc.json │ ├── post_v1_pins_57ba63e262aae3bfa688a1a214df79bc.json │ ├── get_v1_pins_314196511500433397_0b8461cd5574fc97b8ab9b836a092dfe.json │ ├── patch_v1_pins_314196511498502249_339d203ec596d5016eca8415d6cb8b1d.json │ ├── get_v1_me_following_interests_bab7fcd2edf55c2f193319138437ca52.json │ ├── patch_v1_pins_314196511500433397_339d203ec596d5016eca8415d6cb8b1d.json │ ├── get_v1_me_boards_f540825c41d09d337b65d1bf46a19fe8.json │ ├── get_v1_me_pins_0b8461cd5574fc97b8ab9b836a092dfe.json │ ├── get_v1_boards_314196580192658592_pins_0b8461cd5574fc97b8ab9b836a092dfe.json │ └── get_v1_me_followers_e7f00147b20fff1c562ea111852e576e.json │ ├── bootstrap.php │ ├── ImageTest.php │ ├── AuthenticationTest.php │ ├── TestCase.php │ ├── PagedListTest.php │ ├── MockClient.php │ └── ApiTest.php ├── .env-example ├── .gitignore ├── src └── Pinterest │ ├── Api │ └── Exceptions │ │ ├── TooManyScopesGiven.php │ │ ├── AtLeastOneScopeNeeded.php │ │ ├── InvalidScopeException.php │ │ └── TokenMissing.php │ ├── Http │ ├── Exceptions │ │ ├── MalformedJson.php │ │ └── RateLimitedReached.php │ ├── ClientInterface.php │ ├── Request.php │ ├── BuzzClient.php │ └── Response.php │ ├── Objects │ ├── BaseObject.php │ ├── Stats.php │ ├── Board.php │ ├── User.php │ ├── Pin.php │ └── PagedList.php │ ├── App │ └── Scope.php │ ├── Image.php │ ├── Mapper.php │ ├── Authentication.php │ └── Api.php ├── .php_cs ├── phpunit.xml.dist ├── composer.json ├── .travis.yml ├── LICENSE.md ├── CONTRIBUTING.md └── README.md /art/pinterest-php.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hansott/pinterest-php/HEAD/art/pinterest-php.jpg -------------------------------------------------------------------------------- /tests/Pinterest/fixtures/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hansott/pinterest-php/HEAD/tests/Pinterest/fixtures/cat.jpg -------------------------------------------------------------------------------- /.env-example: -------------------------------------------------------------------------------- 1 | CLIENT_ID="your-client-id" 2 | CLIENT_SECRET="your-client-secret" 3 | ACCESS_TOKEN="your-access-token" 4 | BOARD_ID="your-test-board-id" 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | .php_cs.cache 4 | .DS_Store 5 | Thumbs.db 6 | pinterest.sublime-workspace 7 | .env 8 | test.php 9 | 10 | # PHPStorm 11 | .idea 12 | -------------------------------------------------------------------------------- /src/Pinterest/Api/Exceptions/TooManyScopesGiven.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Api\Exceptions; 15 | 16 | use Exception; 17 | 18 | final class TooManyScopesGiven extends Exception 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /src/Pinterest/Api/Exceptions/AtLeastOneScopeNeeded.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Api\Exceptions; 15 | 16 | use Exception; 17 | 18 | final class AtLeastOneScopeNeeded extends Exception 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /src/Pinterest/Api/Exceptions/InvalidScopeException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Api\Exceptions; 15 | 16 | use Exception; 17 | 18 | final class InvalidScopeException extends Exception 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /src/Pinterest/Http/Exceptions/MalformedJson.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Http\Exceptions; 15 | 16 | use Exception; 17 | 18 | /** 19 | * This exception is thrown when the response is no valid json. 20 | * 21 | * @author Hans Ott 22 | */ 23 | class MalformedJson extends Exception 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /src/Pinterest/Api/Exceptions/TokenMissing.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Api\Exceptions; 15 | 16 | use Exception; 17 | 18 | /** 19 | * This exception is thrown when a request is being made without an access token. 20 | * 21 | * @author Hans Ott 22 | */ 23 | class TokenMissing extends Exception 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /src/Pinterest/Objects/BaseObject.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Objects; 15 | 16 | /** 17 | * The base object. 18 | * 19 | * @author Hans Ott 20 | */ 21 | interface BaseObject 22 | { 23 | /** 24 | * Get the required fields for the object. 25 | * 26 | * @return array 27 | */ 28 | public static function fields(); 29 | } 30 | -------------------------------------------------------------------------------- /src/Pinterest/App/Scope.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\App; 15 | 16 | /** 17 | * The possible application scopes. 18 | * 19 | * @author Hans Ott 20 | */ 21 | class Scope 22 | { 23 | const READ_PUBLIC = 'read_public'; 24 | const WRITE_PUBLIC = 'write_public'; 25 | const READ_RELATIONSHIPS = 'read_relationships'; 26 | const WRITE_RELATIONSHIPS = 'write_relationships'; 27 | } 28 | -------------------------------------------------------------------------------- /tests/Pinterest/responses/post_v1_me_following_users_a22f421c007ca367591446448669e24c.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": null 4 | }, 5 | "statusCode": 200, 6 | "headers": { 7 | "Access-Control-Allow-Origin": "*", 8 | "Age": "0", 9 | "Cache-Control": "private", 10 | "Content-Type": "application\/json", 11 | "Pinterest-Version": "4f9f34e", 12 | "X-Content-Type-Options": "nosniff", 13 | "X-Pinterest-RID": "495114853653", 14 | "X-Ratelimit-Limit": "10", 15 | "X-Ratelimit-Remaining": "9", 16 | "Content-Length": "14", 17 | "Date": "Sun, 22 Apr 2018 13:44:25 GMT", 18 | "Connection": "keep-alive", 19 | "Pinterest-Generated-By:": "" 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/post_v1_me_following_boards_acb09c511e44c8e8ee18a6582be246f4.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": null 4 | }, 5 | "statusCode": 200, 6 | "headers": { 7 | "Access-Control-Allow-Origin": "*", 8 | "Age": "0", 9 | "Cache-Control": "private", 10 | "Content-Type": "application\/json", 11 | "Pinterest-Version": "4f9f34e", 12 | "X-Content-Type-Options": "nosniff", 13 | "X-Pinterest-RID": "903072032337", 14 | "X-Ratelimit-Limit": "10", 15 | "X-Ratelimit-Remaining": "7", 16 | "Content-Length": "14", 17 | "Date": "Sun, 22 Apr 2018 13:44:26 GMT", 18 | "Connection": "keep-alive", 19 | "Pinterest-Generated-By:": "" 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/delete_v1_boards_314196580192887528_d41d8cd98f00b204e9800998ecf8427e.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": null 4 | }, 5 | "statusCode": 200, 6 | "headers": { 7 | "Access-Control-Allow-Origin": "*", 8 | "Age": "0", 9 | "Cache-Control": "private", 10 | "Content-Type": "application\/json", 11 | "Pinterest-Version": "4f9f34e", 12 | "X-Content-Type-Options": "nosniff", 13 | "X-Pinterest-RID": "844314424322", 14 | "X-Ratelimit-Limit": "10", 15 | "X-Ratelimit-Remaining": "3", 16 | "Content-Length": "14", 17 | "Date": "Sun, 22 Apr 2018 15:06:50 GMT", 18 | "Connection": "keep-alive", 19 | "Pinterest-Generated-By:": "" 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/delete_v1_me_following_users_engagord41d8cd98f00b204e9800998ecf8427e.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": null 4 | }, 5 | "statusCode": 200, 6 | "headers": { 7 | "Access-Control-Allow-Origin": "*", 8 | "Age": "0", 9 | "Cache-Control": "private", 10 | "Content-Type": "application\/json", 11 | "Pinterest-Version": "4f9f34e", 12 | "X-Content-Type-Options": "nosniff", 13 | "X-Pinterest-RID": "341096373106", 14 | "X-Ratelimit-Limit": "10", 15 | "X-Ratelimit-Remaining": "8", 16 | "Content-Length": "14", 17 | "Date": "Sun, 22 Apr 2018 13:44:26 GMT", 18 | "Connection": "keep-alive", 19 | "Pinterest-Generated-By:": "" 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/delete_v1_me_following_boards_engagor_engagor-in-the-newsd41d8cd98f00b204e9800998ecf8427e.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": null 4 | }, 5 | "statusCode": 200, 6 | "headers": { 7 | "Access-Control-Allow-Origin": "*", 8 | "Age": "0", 9 | "Cache-Control": "private", 10 | "Content-Type": "application\/json", 11 | "Pinterest-Version": "4f9f34e", 12 | "X-Content-Type-Options": "nosniff", 13 | "X-Pinterest-RID": "137632360192", 14 | "X-Ratelimit-Limit": "10", 15 | "X-Ratelimit-Remaining": "6", 16 | "Content-Length": "14", 17 | "Date": "Sun, 22 Apr 2018 13:44:26 GMT", 18 | "Connection": "keep-alive", 19 | "Pinterest-Generated-By:": "" 20 | } 21 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/get_v1_me_following_boards_f540825c41d09d337b65d1bf46a19fe8.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": {}, 4 | "page": { 5 | "cursor": null, 6 | "next": null 7 | } 8 | }, 9 | "statusCode": 200, 10 | "headers": { 11 | "Access-Control-Allow-Origin": "*", 12 | "Age": "0", 13 | "Cache-Control": "private", 14 | "Content-Type": "application\/json", 15 | "Pinterest-Version": "4f9f34e", 16 | "X-Content-Type-Options": "nosniff", 17 | "X-Pinterest-RID": "372006537790", 18 | "X-Ratelimit-Limit": "10", 19 | "X-Ratelimit-Remaining": "2", 20 | "Content-Length": "52", 21 | "Date": "Sun, 22 Apr 2018 12:04:23 GMT", 22 | "Connection": "keep-alive", 23 | "Pinterest-Generated-By:": "" 24 | } 25 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/get_v1_me_following_users_e7f00147b20fff1c562ea111852e576e.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": {}, 4 | "page": { 5 | "cursor": null, 6 | "next": null 7 | } 8 | }, 9 | "statusCode": 200, 10 | "headers": { 11 | "Access-Control-Allow-Origin": "*", 12 | "Age": "0", 13 | "Cache-Control": "private", 14 | "Content-Type": "application\/json", 15 | "Pinterest-Version": "4f9f34e", 16 | "X-Content-Type-Options": "nosniff", 17 | "X-Pinterest-RID": "881231782141", 18 | "X-Ratelimit-Limit": "10", 19 | "X-Ratelimit-Remaining": "1", 20 | "Content-Length": "52", 21 | "Date": "Sun, 22 Apr 2018 12:04:23 GMT", 22 | "Connection": "keep-alive", 23 | "Pinterest-Generated-By:": "" 24 | } 25 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/delete_v1_pins_314196511500433394_d41d8cd98f00b204e9800998ecf8427e.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": null 4 | }, 5 | "statusCode": 200, 6 | "headers": { 7 | "Access-Control-Allow-Origin": "*", 8 | "Cache-Control": "private", 9 | "Content-Type": "application\/json", 10 | "pinterest-generated-by": "coreapp-ngapi-prod-0a0102cb", 11 | "pinterest-version": "df0f6f7", 12 | "x-content-type-options": "nosniff", 13 | "x-envoy-upstream-service-time": "301", 14 | "x-pinterest-rid": "050258780322", 15 | "X-RateLimit-Limit": "10", 16 | "X-RateLimit-Remaining": "6", 17 | "Content-Length": "14", 18 | "Date": "Mon, 17 Sep 2018 11:21:02 GMT", 19 | "Connection": "keep-alive", 20 | "Set-Cookie": "_ir=0; Max-Age=1800; HttpOnly; Secure", 21 | "Age": "0" 22 | } 23 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/delete_v1_pins_314196511500433395_d41d8cd98f00b204e9800998ecf8427e.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": null 4 | }, 5 | "statusCode": 200, 6 | "headers": { 7 | "Access-Control-Allow-Origin": "*", 8 | "Cache-Control": "private", 9 | "Content-Type": "application\/json", 10 | "pinterest-generated-by": "coreapp-ngapi-prod-0a010e2c", 11 | "pinterest-version": "df0f6f7", 12 | "x-content-type-options": "nosniff", 13 | "x-envoy-upstream-service-time": "123", 14 | "x-pinterest-rid": "952434611764", 15 | "X-RateLimit-Limit": "10", 16 | "X-RateLimit-Remaining": "7", 17 | "Content-Length": "14", 18 | "Date": "Mon, 17 Sep 2018 11:21:02 GMT", 19 | "Connection": "keep-alive", 20 | "Set-Cookie": "_ir=0; Max-Age=1800; HttpOnly; Secure", 21 | "Age": "0" 22 | } 23 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/delete_v1_pins_314196511500433396_d41d8cd98f00b204e9800998ecf8427e.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": null 4 | }, 5 | "statusCode": 200, 6 | "headers": { 7 | "Access-Control-Allow-Origin": "*", 8 | "Cache-Control": "private", 9 | "Content-Type": "application\/json", 10 | "pinterest-generated-by": "coreapp-ngapi-prod-0a01ca6f", 11 | "pinterest-version": "df0f6f7", 12 | "x-content-type-options": "nosniff", 13 | "x-envoy-upstream-service-time": "138", 14 | "x-pinterest-rid": "554672669154", 15 | "X-RateLimit-Limit": "10", 16 | "X-RateLimit-Remaining": "7", 17 | "Content-Length": "14", 18 | "Date": "Mon, 17 Sep 2018 11:21:02 GMT", 19 | "Connection": "keep-alive", 20 | "Set-Cookie": "_ir=0; Max-Age=1800; HttpOnly; Secure", 21 | "Age": "0" 22 | } 23 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/delete_v1_pins_314196511500433397_d41d8cd98f00b204e9800998ecf8427e.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": null 4 | }, 5 | "statusCode": 200, 6 | "headers": { 7 | "Access-Control-Allow-Origin": "*", 8 | "Cache-Control": "private", 9 | "Content-Type": "application\/json", 10 | "pinterest-generated-by": "coreapp-ngapi-prod-0a0104ef", 11 | "pinterest-version": "df0f6f7", 12 | "x-content-type-options": "nosniff", 13 | "x-envoy-upstream-service-time": "278", 14 | "x-pinterest-rid": "158784266831", 15 | "X-RateLimit-Limit": "10", 16 | "X-RateLimit-Remaining": "8", 17 | "Content-Length": "14", 18 | "Date": "Mon, 17 Sep 2018 11:19:31 GMT", 19 | "Connection": "keep-alive", 20 | "Set-Cookie": "_ir=0; Max-Age=1800; HttpOnly; Secure", 21 | "Age": "0" 22 | } 23 | } -------------------------------------------------------------------------------- /src/Pinterest/Http/ClientInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Http; 15 | 16 | /** 17 | * All http clients need to implement this. 18 | * 19 | * Implement this interface to create your own http client. 20 | * When you need extra logging for example. 21 | * 22 | * @author Hans Ott 23 | */ 24 | interface ClientInterface 25 | { 26 | /** 27 | * Executes a http request. 28 | * 29 | * @param Request $request The http request. 30 | * 31 | * @return Response The http response. 32 | */ 33 | public function execute(Request $request); 34 | } 35 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | 7 | 8 | This source file is subject to the MIT license that is bundled 9 | with this source code in the file LICENSE.md. 10 | 11 | Source: https://github.com/hansott/pinterest-php 12 | EOF; 13 | 14 | Symfony\CS\Fixer\Contrib\HeaderCommentFixer::setHeader($header); 15 | 16 | return Symfony\CS\Config\Config::create() 17 | // use default SYMFONY_LEVEL and extra fixers: 18 | ->fixers(array( 19 | 'header_comment', 20 | 'long_array_syntax', 21 | 'ordered_use', 22 | 'php_unit_construct', 23 | 'php_unit_strict', 24 | 'strict', 25 | 'strict_param', 26 | )) 27 | ->finder( 28 | Symfony\CS\Finder\DefaultFinder::create() 29 | ->exclude('tests/Pinterest/fixtures') 30 | ->exclude('tests/Pinterest/responses') 31 | ->in(__DIR__) 32 | ) 33 | ; 34 | -------------------------------------------------------------------------------- /src/Pinterest/Http/Exceptions/RateLimitedReached.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Http\Exceptions; 15 | 16 | use Pinterest\Http\Response; 17 | 18 | /** 19 | * This exception will be thrown when the rate limit is reached. 20 | * 21 | * @author Toon Daelman 22 | */ 23 | final class RateLimitedReached extends \Exception 24 | { 25 | private $response; 26 | 27 | public function __construct(Response $response) 28 | { 29 | $this->response = $response; 30 | 31 | parent::__construct('Rate limit reached.'); 32 | } 33 | 34 | public function getResponse() 35 | { 36 | return $this->response; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./tests 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ./vendor 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hansott/pinterest-php", 3 | "description": "PHP client for the official Pinterest API", 4 | "keywords": [ 5 | "pinterest", 6 | "oauth", 7 | "api", 8 | "repin" 9 | ], 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Hans Ott", 14 | "email": "hans@iott.consulting" 15 | }, 16 | { 17 | "name": "Toon Daelman", 18 | "email": "spinnewebber_toon@hotmail.com", 19 | "homepage": "http://turanct.wordpress.com" 20 | } 21 | ], 22 | "require": { 23 | "kriswallsmith/buzz": "v0.15", 24 | "netresearch/jsonmapper": "^1.0" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "4.8.12", 28 | "vlucas/phpdotenv": "dev-master", 29 | "symfony/yaml": "2.7.5" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Pinterest\\": "src/Pinterest/" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Pinterest\\Tests\\": "tests/Pinterest/" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.6 4 | - 7.0 5 | - 7.1 6 | - 7.2 7 | 8 | env: 9 | global: 10 | - setup=basic 11 | 12 | cache: 13 | directories: 14 | - vendor 15 | - $HOME/.composer/cache 16 | 17 | sudo: false 18 | 19 | install: 20 | - if [[ $setup = 'basic' ]]; then travis_retry composer install --no-interaction --prefer-source; fi 21 | - if [[ $setup = 'stable' ]]; then travis_retry composer update --prefer-source --no-interaction --prefer-stable; fi 22 | - if [[ $setup = 'lowest' ]]; then travis_retry composer update --prefer-source --no-interaction --prefer-lowest --prefer-stable; fi 23 | 24 | script: 25 | - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover 26 | 27 | after_script: 28 | - if [ "$TRAVIS_PHP_VERSION" != "7.0" ] && [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi 29 | - if [ "$TRAVIS_PHP_VERSION" != "7.0" ] && [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi 30 | 31 | notifications: 32 | email: false 33 | 34 | branches: 35 | only: 36 | - master 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Hans Ott 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/Pinterest/Objects/Stats.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Objects; 15 | 16 | /** 17 | * This class holds all general statistics. 18 | * 19 | * @author Hans Ott 20 | */ 21 | final class Stats 22 | { 23 | /** 24 | * The amount of saves. 25 | * 26 | * @var int 27 | */ 28 | public $saves; 29 | 30 | /** 31 | * The amount of comments. 32 | * 33 | * @var int 34 | */ 35 | public $comments; 36 | 37 | /** 38 | * The amount of following. 39 | * 40 | * @var int 41 | */ 42 | public $following; 43 | 44 | /** 45 | * The amount of followers. 46 | * 47 | * @var int 48 | */ 49 | public $followers; 50 | 51 | /** 52 | * The amount of pins. 53 | * 54 | * @var int 55 | */ 56 | public $pins; 57 | 58 | /** 59 | * The amount of collaborators. 60 | * 61 | * @var int 62 | */ 63 | public $collaborators; 64 | } 65 | -------------------------------------------------------------------------------- /tests/Pinterest/bootstrap.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | if (!defined('JSON_PRETTY_PRINT')) { 15 | define('JSON_PRETTY_PRINT', 128); 16 | } 17 | 18 | require __DIR__.'/../../vendor/autoload.php'; 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Load environments variables 23 | |-------------------------------------------------------------------------- 24 | | 25 | | To keep our credentials a secret, 26 | | we'll use dotenv to store them in a .env file. 27 | | 28 | */ 29 | 30 | try { 31 | $dotenv = new Dotenv\Dotenv(__DIR__.'/../../'); 32 | $dotenv->load(); 33 | } catch (InvalidArgumentException $e) { 34 | // It's okay to fail here. Because env variables are set with Travis. 35 | } 36 | 37 | /* 38 | |-------------------------------------------------------------------------- 39 | | Set The Default Timezone 40 | |-------------------------------------------------------------------------- 41 | | 42 | | Here we will set the default timezone for PHP. PHP is notoriously mean 43 | | if the timezone is not explicitly set. 44 | | 45 | */ 46 | 47 | date_default_timezone_set('UTC'); 48 | -------------------------------------------------------------------------------- /tests/Pinterest/responses/get_v1_me_e7f00147b20fff1c562ea111852e576e.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "username": "hanso1", 5 | "bio": "", 6 | "first_name": "H", 7 | "last_name": "O", 8 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 9 | "created_at": "2014-08-31T12:45:22", 10 | "image": { 11 | "60x60": { 12 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/d5\/d3\/89\/d5d3893094ce251893d83aeccb4ee889.jpg", 13 | "width": 60, 14 | "height": 60 15 | } 16 | }, 17 | "counts": { 18 | "pins": 5, 19 | "following": 0, 20 | "followers": 23, 21 | "boards": 2 22 | }, 23 | "id": "314196648911734959" 24 | } 25 | }, 26 | "statusCode": 200, 27 | "headers": { 28 | "Access-Control-Allow-Origin": "*", 29 | "Age": "0", 30 | "Cache-Control": "private", 31 | "Content-Type": "application\/json", 32 | "Pinterest-Version": "4f9f34e", 33 | "X-Content-Type-Options": "nosniff", 34 | "X-Pinterest-RID": "318398462302", 35 | "X-Ratelimit-Limit": "10", 36 | "X-Ratelimit-Remaining": "4", 37 | "Content-Length": "393", 38 | "Date": "Sun, 22 Apr 2018 12:04:22 GMT", 39 | "Connection": "keep-alive", 40 | "Pinterest-Generated-By:": "" 41 | } 42 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/get_v1_users_hanso1_e7f00147b20fff1c562ea111852e576e.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "username": "hanso1", 5 | "bio": "", 6 | "first_name": "H", 7 | "last_name": "O", 8 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 9 | "created_at": "2014-08-31T12:45:22", 10 | "image": { 11 | "60x60": { 12 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/d5\/d3\/89\/d5d3893094ce251893d83aeccb4ee889.jpg", 13 | "width": 60, 14 | "height": 60 15 | } 16 | }, 17 | "counts": { 18 | "pins": 5, 19 | "following": 0, 20 | "followers": 23, 21 | "boards": 2 22 | }, 23 | "id": "314196648911734959" 24 | } 25 | }, 26 | "statusCode": 200, 27 | "headers": { 28 | "Access-Control-Allow-Origin": "*", 29 | "Age": "0", 30 | "Cache-Control": "private", 31 | "Content-Type": "application\/json", 32 | "Pinterest-Version": "4f9f34e", 33 | "X-Content-Type-Options": "nosniff", 34 | "X-Pinterest-RID": "116840468365", 35 | "X-Ratelimit-Limit": "10", 36 | "X-Ratelimit-Remaining": "9", 37 | "Content-Length": "393", 38 | "Date": "Sun, 22 Apr 2018 12:04:21 GMT", 39 | "Connection": "keep-alive", 40 | "Pinterest-Generated-By:": "" 41 | } 42 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/get_v1_users_314196648911734959_e7f00147b20fff1c562ea111852e576e.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "username": "hanso1", 5 | "bio": "", 6 | "first_name": "H", 7 | "last_name": "O", 8 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 9 | "created_at": "2014-08-31T12:45:22", 10 | "image": { 11 | "60x60": { 12 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/d5\/d3\/89\/d5d3893094ce251893d83aeccb4ee889.jpg", 13 | "width": 60, 14 | "height": 60 15 | } 16 | }, 17 | "counts": { 18 | "pins": 5, 19 | "following": 0, 20 | "followers": 23, 21 | "boards": 2 22 | }, 23 | "id": "314196648911734959" 24 | } 25 | }, 26 | "statusCode": 200, 27 | "headers": { 28 | "Access-Control-Allow-Origin": "*", 29 | "Age": "0", 30 | "Cache-Control": "private", 31 | "Content-Type": "application\/json", 32 | "Pinterest-Version": "4f9f34e", 33 | "X-Content-Type-Options": "nosniff", 34 | "X-Pinterest-RID": "993193506514", 35 | "X-Ratelimit-Limit": "10", 36 | "X-Ratelimit-Remaining": "8", 37 | "Content-Length": "393", 38 | "Date": "Sun, 22 Apr 2018 12:04:21 GMT", 39 | "Connection": "keep-alive", 40 | "Pinterest-Generated-By:": "" 41 | } 42 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | We accept contributions via Pull Requests on [Github](https://github.com/hansott/pinterest-php/pulls). 6 | 7 | 8 | ## Pull Requests 9 | 10 | - **[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 install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). 11 | 12 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 13 | 14 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 15 | 16 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 17 | 18 | - **Create feature branches** - Don't ask us to pull from your master branch. 19 | 20 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 21 | 22 | - **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](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 23 | 24 | 25 | ## Running Tests 26 | 27 | Don't forget to rename the .env-example to .env and set your own credentials. 28 | 29 | ``` bash 30 | $ vendor/bin/phpunit 31 | ``` 32 | 33 | **Happy coding**! 34 | -------------------------------------------------------------------------------- /tests/Pinterest/responses/post_v1_boards_70b08b1fbb08f54340a2b9e381d51dc3.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "description": "A simple description", 5 | "creator": { 6 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 7 | "first_name": "H", 8 | "last_name": "O", 9 | "id": "314196648911734959" 10 | }, 11 | "url": "https:\/\/www.pinterest.com\/hanso1\/unit-test\/", 12 | "created_at": "2018-04-22T15:06:49", 13 | "image": { 14 | "60x60": { 15 | "url": null, 16 | "width": 60, 17 | "height": 60 18 | } 19 | }, 20 | "counts": { 21 | "pins": 0, 22 | "collaborators": 0, 23 | "followers": 23 24 | }, 25 | "id": "314196580192887528", 26 | "name": "Unit test" 27 | } 28 | }, 29 | "statusCode": 201, 30 | "headers": { 31 | "Access-Control-Allow-Origin": "*", 32 | "Age": "0", 33 | "Cache-Control": "private", 34 | "Content-Type": "application\/json", 35 | "Pinterest-Version": "4f9f34e", 36 | "X-Content-Type-Options": "nosniff", 37 | "X-Pinterest-RID": "450230308379", 38 | "X-Ratelimit-Limit": "10", 39 | "X-Ratelimit-Remaining": "5", 40 | "Content-Length": "433", 41 | "Date": "Sun, 22 Apr 2018 15:06:50 GMT", 42 | "Connection": "keep-alive", 43 | "Pinterest-Generated-By:": "" 44 | } 45 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/patch_v1_boards_314196580192887528_7a9e411987c7b78dd18291e11169f9e4.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "description": "A simple description", 5 | "creator": { 6 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 7 | "first_name": "H", 8 | "last_name": "O", 9 | "id": "314196648911734959" 10 | }, 11 | "url": "https:\/\/www.pinterest.com\/hanso1\/unit-test-update\/", 12 | "created_at": "2018-04-22T15:06:49", 13 | "image": { 14 | "60x60": { 15 | "url": null, 16 | "width": 60, 17 | "height": 60 18 | } 19 | }, 20 | "counts": { 21 | "pins": 0, 22 | "collaborators": 0, 23 | "followers": 23 24 | }, 25 | "id": "314196580192887528", 26 | "name": "Unit test update" 27 | } 28 | }, 29 | "statusCode": 200, 30 | "headers": { 31 | "Access-Control-Allow-Origin": "*", 32 | "Age": "0", 33 | "Cache-Control": "private", 34 | "Content-Type": "application\/json", 35 | "Pinterest-Version": "4f9f34e", 36 | "X-Content-Type-Options": "nosniff", 37 | "X-Pinterest-RID": "679188046730", 38 | "X-Ratelimit-Limit": "10", 39 | "X-Ratelimit-Remaining": "4", 40 | "Content-Length": "447", 41 | "Date": "Sun, 22 Apr 2018 15:06:50 GMT", 42 | "Connection": "keep-alive", 43 | "Pinterest-Generated-By:": "" 44 | } 45 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/get_v1_boards_314196580192658592_f540825c41d09d337b65d1bf46a19fe8.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "description": "", 5 | "creator": { 6 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 7 | "first_name": "H", 8 | "last_name": "O", 9 | "id": "314196648911734959" 10 | }, 11 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 12 | "created_at": "2016-03-13T14:59:11", 13 | "image": { 14 | "60x60": { 15 | "url": "https:\/\/i.pinimg.com\/60x60\/71\/f5\/64\/71f56468c6aaef1da0e4580d4e687914.jpg", 16 | "width": 60, 17 | "height": 60 18 | } 19 | }, 20 | "counts": { 21 | "pins": 5, 22 | "collaborators": 0, 23 | "followers": 23 24 | }, 25 | "id": "314196580192658592", 26 | "name": "Test" 27 | } 28 | }, 29 | "statusCode": 200, 30 | "headers": { 31 | "Access-Control-Allow-Origin": "*", 32 | "Age": "0", 33 | "Cache-Control": "private", 34 | "Content-Type": "application\/json", 35 | "Pinterest-Version": "4f9f34e", 36 | "X-Content-Type-Options": "nosniff", 37 | "X-Pinterest-RID": "196806875836", 38 | "X-Ratelimit-Limit": "10", 39 | "X-Ratelimit-Remaining": "7", 40 | "Content-Length": "473", 41 | "Date": "Sun, 22 Apr 2018 12:04:21 GMT", 42 | "Connection": "keep-alive", 43 | "Pinterest-Generated-By:": "" 44 | } 45 | } -------------------------------------------------------------------------------- /tests/Pinterest/ImageTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Tests; 15 | 16 | use Pinterest\Image; 17 | use PHPUnit_Framework_TestCase; 18 | 19 | class ImageTest extends PHPUnit_Framework_TestCase 20 | { 21 | public function test_it_can_be_instantiated_with_an_url() 22 | { 23 | $url = 'http://example.com/example.png'; 24 | $image = Image::url($url); 25 | 26 | $this->assertTrue($image->isUrl()); 27 | $this->assertSame($url, $image->getData()); 28 | 29 | $this->assertFalse($image->isBase64()); 30 | $this->assertFalse($image->isFile()); 31 | } 32 | 33 | public function test_it_can_be_instantiated_with_a_base64_string() 34 | { 35 | $string = 'base-64-string-test'; 36 | $image = Image::base64($string); 37 | 38 | $this->assertTrue($image->isBase64()); 39 | $this->assertSame($string, $image->getData()); 40 | 41 | $this->assertFalse($image->isUrl()); 42 | $this->assertFalse($image->isFile()); 43 | } 44 | 45 | public function test_it_can_be_instantiated_with_a_file_path() 46 | { 47 | $path = 'file-path-test'; 48 | $image = Image::file($path); 49 | 50 | $this->assertTrue($image->isFile()); 51 | $this->assertSame($path, $image->getData()); 52 | 53 | $this->assertFalse($image->isBase64()); 54 | $this->assertFalse($image->isUrl()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Pinterest/Objects/Board.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Objects; 15 | 16 | /** 17 | * This class represents a board. 18 | * 19 | * @author Hans Ott 20 | */ 21 | final class Board implements BaseObject 22 | { 23 | /** 24 | * The required fields. 25 | * 26 | * @return array The required fields. 27 | */ 28 | public static function fields() 29 | { 30 | return array( 31 | 'id', 32 | 'name', 33 | 'url', 34 | 'description', 35 | 'created_at', 36 | 'counts', 37 | 'image', 38 | ); 39 | } 40 | 41 | /** 42 | * The boards's id. 43 | * 44 | * @var string 45 | * @required 46 | */ 47 | public $id; 48 | 49 | /** 50 | * The name of the board. 51 | * 52 | * @var string 53 | */ 54 | public $name; 55 | 56 | /** 57 | * The url to the object on pinterest. 58 | * 59 | * @var string 60 | */ 61 | public $url; 62 | 63 | /** 64 | * The description of the board by the creator. 65 | * 66 | * @var string 67 | */ 68 | public $description; 69 | 70 | /** 71 | * ISO 8601 Timestamp of creation date. 72 | * 73 | * @var \DateTime 74 | */ 75 | public $created_at; 76 | 77 | /** 78 | * The stats/counts of the Board (pins, collaborators and followers). 79 | * 80 | * @var Stats 81 | */ 82 | public $counts; 83 | 84 | /** 85 | * Information about the media type, including whether it's an "image" or "video".. 86 | * 87 | * @var array 88 | */ 89 | public $image; 90 | } 91 | -------------------------------------------------------------------------------- /src/Pinterest/Image.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest; 15 | 16 | use InvalidArgumentException; 17 | 18 | /** 19 | * This class represents an image. 20 | * 21 | * @author Toon Daelman 22 | */ 23 | final class Image 24 | { 25 | const TYPE_URL = 'url'; 26 | const TYPE_BASE64 = 'base64'; 27 | const TYPE_FILE = 'file'; 28 | 29 | private $type; 30 | private $data; 31 | 32 | private function __construct($type, $data) 33 | { 34 | $allowedTypes = array(self::TYPE_URL, self::TYPE_BASE64, self::TYPE_FILE); 35 | if (!in_array($type, $allowedTypes, true)) { 36 | throw new InvalidArgumentException('Type '.$type.' is not allowed.'); 37 | } 38 | 39 | $this->type = $type; 40 | $this->data = $data; 41 | } 42 | 43 | public static function url($url) 44 | { 45 | return new static(static::TYPE_URL, $url); 46 | } 47 | 48 | public static function base64($base64) 49 | { 50 | return new static(static::TYPE_BASE64, $base64); 51 | } 52 | 53 | public static function file($file) 54 | { 55 | return new static(static::TYPE_FILE, $file); 56 | } 57 | 58 | public function isUrl() 59 | { 60 | return $this->type === static::TYPE_URL; 61 | } 62 | 63 | public function isBase64() 64 | { 65 | return $this->type === static::TYPE_BASE64; 66 | } 67 | 68 | public function isFile() 69 | { 70 | return $this->type === static::TYPE_FILE; 71 | } 72 | 73 | public function getData() 74 | { 75 | if ($this->isFile()) { 76 | return $this->data; 77 | } 78 | 79 | return $this->data; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/Pinterest/responses/get_v1_pins_314196511498502249_0b8461cd5574fc97b8ab9b836a092dfe.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "attribution": null, 5 | "creator": { 6 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 7 | "first_name": "H", 8 | "last_name": "O", 9 | "id": "314196648911734959" 10 | }, 11 | "url": "https:\/\/www.pinterest.com\/pin\/314196511498502249\/", 12 | "media": { 13 | "type": "image" 14 | }, 15 | "created_at": "2018-04-22T15:06:48", 16 | "note": "A note!", 17 | "color": null, 18 | "link": "", 19 | "board": { 20 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 21 | "id": "314196580192658592", 22 | "name": "Test" 23 | }, 24 | "image": { 25 | "original": { 26 | "url": "https:\/\/i.pinimg.com\/originals\/e4\/84\/e3\/e484e3fa1959913947378735eddd969f.png", 27 | "width": 350, 28 | "height": 150 29 | } 30 | }, 31 | "counts": { 32 | "saves": 0, 33 | "comments": 0 34 | }, 35 | "id": "314196511498502249", 36 | "metadata": {} 37 | } 38 | }, 39 | "statusCode": 200, 40 | "headers": { 41 | "Access-Control-Allow-Origin": "*", 42 | "Age": "0", 43 | "Cache-Control": "private", 44 | "Content-Type": "application\/json", 45 | "Pinterest-Version": "4f9f34e", 46 | "X-Content-Type-Options": "nosniff", 47 | "X-Pinterest-RID": "396832822911", 48 | "X-Ratelimit-Limit": "10", 49 | "X-Ratelimit-Remaining": "8", 50 | "Content-Length": "652", 51 | "Date": "Sun, 22 Apr 2018 15:06:49 GMT", 52 | "Connection": "keep-alive", 53 | "Pinterest-Generated-By:": "" 54 | } 55 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/post_v1_pins_26f04d82b6b8e731738b394a6c510356.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "attribution": null, 5 | "creator": { 6 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 7 | "first_name": "H", 8 | "last_name": "O", 9 | "id": "314196648911734959" 10 | }, 11 | "url": "https:\/\/www.pinterest.com\/pin\/314196511500433397\/", 12 | "media": { 13 | "type": "image" 14 | }, 15 | "created_at": "2018-09-17T11:19:30", 16 | "note": "A note!", 17 | "color": null, 18 | "link": "", 19 | "board": { 20 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 21 | "id": "314196580192658592", 22 | "name": "Test" 23 | }, 24 | "image": { 25 | "original": { 26 | "url": "https:\/\/i.pinimg.com\/originals\/18\/27\/4a\/18274ad303abf53cbb225f05cc79d44b.png", 27 | "width": 350, 28 | "height": 150 29 | } 30 | }, 31 | "counts": { 32 | "saves": 0, 33 | "comments": 0 34 | }, 35 | "id": "314196511500433397", 36 | "metadata": {} 37 | } 38 | }, 39 | "statusCode": 201, 40 | "headers": { 41 | "Access-Control-Allow-Origin": "*", 42 | "Cache-Control": "private", 43 | "Content-Type": "application\/json", 44 | "pinterest-generated-by": "coreapp-ngapi-prod-0a010643", 45 | "pinterest-version": "df0f6f7", 46 | "x-content-type-options": "nosniff", 47 | "x-envoy-upstream-service-time": "214", 48 | "x-pinterest-rid": "836459307470", 49 | "X-RateLimit-Limit": "10", 50 | "X-RateLimit-Remaining": "8", 51 | "Content-Length": "652", 52 | "Date": "Mon, 17 Sep 2018 11:19:30 GMT", 53 | "Connection": "keep-alive", 54 | "Set-Cookie": "_ir=0; Max-Age=1800; HttpOnly; Secure", 55 | "Age": "0" 56 | } 57 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/post_v1_pins_5bbd4d62e330b15253bc4e74c5e3bcfc.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "attribution": null, 5 | "creator": { 6 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 7 | "first_name": "H", 8 | "last_name": "O", 9 | "id": "314196648911734959" 10 | }, 11 | "url": "https:\/\/www.pinterest.com\/pin\/314196511500433394\/", 12 | "media": { 13 | "type": "image" 14 | }, 15 | "created_at": "2018-09-17T11:19:26", 16 | "note": "Test pin url", 17 | "color": null, 18 | "link": "", 19 | "board": { 20 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 21 | "id": "314196580192658592", 22 | "name": "Test" 23 | }, 24 | "image": { 25 | "original": { 26 | "url": "https:\/\/i.pinimg.com\/originals\/18\/27\/4a\/18274ad303abf53cbb225f05cc79d44b.png", 27 | "width": 350, 28 | "height": 150 29 | } 30 | }, 31 | "counts": { 32 | "saves": 0, 33 | "comments": 0 34 | }, 35 | "id": "314196511500433394", 36 | "metadata": {} 37 | } 38 | }, 39 | "statusCode": 201, 40 | "headers": { 41 | "Access-Control-Allow-Origin": "*", 42 | "Cache-Control": "private", 43 | "Content-Type": "application\/json", 44 | "pinterest-generated-by": "coreapp-ngapi-prod-0a018f3e", 45 | "pinterest-version": "df0f6f7", 46 | "x-content-type-options": "nosniff", 47 | "x-envoy-upstream-service-time": "244", 48 | "x-pinterest-rid": "012730907582", 49 | "X-RateLimit-Limit": "10", 50 | "X-RateLimit-Remaining": "7", 51 | "Content-Length": "657", 52 | "Date": "Mon, 17 Sep 2018 11:19:26 GMT", 53 | "Connection": "keep-alive", 54 | "Set-Cookie": "_ir=0; Max-Age=1800; HttpOnly; Secure", 55 | "Age": "0" 56 | } 57 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/post_v1_pins_ed6785fd1638af7c6826c483ffe742fc.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "attribution": null, 5 | "creator": { 6 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 7 | "first_name": "H", 8 | "last_name": "O", 9 | "id": "314196648911734959" 10 | }, 11 | "url": "https:\/\/www.pinterest.com\/pin\/314196511500433395\/", 12 | "media": { 13 | "type": "image" 14 | }, 15 | "created_at": "2018-09-17T11:19:28", 16 | "note": "Test pin file", 17 | "color": null, 18 | "link": "", 19 | "board": { 20 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 21 | "id": "314196580192658592", 22 | "name": "Test" 23 | }, 24 | "image": { 25 | "original": { 26 | "url": "https:\/\/i.pinimg.com\/originals\/88\/36\/f7\/8836f7aa08bb8c73d6251a45b24d67d7.jpg", 27 | "width": 632, 28 | "height": 475 29 | } 30 | }, 31 | "counts": { 32 | "saves": 0, 33 | "comments": 0 34 | }, 35 | "id": "314196511500433395", 36 | "metadata": {} 37 | } 38 | }, 39 | "statusCode": 201, 40 | "headers": { 41 | "Access-Control-Allow-Origin": "*", 42 | "Cache-Control": "private", 43 | "Content-Type": "application\/json", 44 | "pinterest-generated-by": "coreapp-ngapi-prod-0a018f91", 45 | "pinterest-version": "df0f6f7", 46 | "x-content-type-options": "nosniff", 47 | "x-envoy-upstream-service-time": "193", 48 | "x-pinterest-rid": "753222464028", 49 | "X-RateLimit-Limit": "10", 50 | "X-RateLimit-Remaining": "6", 51 | "Content-Length": "658", 52 | "Date": "Mon, 17 Sep 2018 11:19:28 GMT", 53 | "Connection": "keep-alive", 54 | "Set-Cookie": "_ir=0; Max-Age=1800; HttpOnly; Secure", 55 | "Age": "0" 56 | } 57 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/post_v1_pins_57ba63e262aae3bfa688a1a214df79bc.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "attribution": null, 5 | "creator": { 6 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 7 | "first_name": "H", 8 | "last_name": "O", 9 | "id": "314196648911734959" 10 | }, 11 | "url": "https:\/\/www.pinterest.com\/pin\/314196511500433396\/", 12 | "media": { 13 | "type": "image" 14 | }, 15 | "created_at": "2018-09-17T11:19:29", 16 | "note": "Test pin base64", 17 | "color": null, 18 | "link": "", 19 | "board": { 20 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 21 | "id": "314196580192658592", 22 | "name": "Test" 23 | }, 24 | "image": { 25 | "original": { 26 | "url": "https:\/\/i.pinimg.com\/originals\/88\/36\/f7\/8836f7aa08bb8c73d6251a45b24d67d7.jpg", 27 | "width": 632, 28 | "height": 475 29 | } 30 | }, 31 | "counts": { 32 | "saves": 0, 33 | "comments": 0 34 | }, 35 | "id": "314196511500433396", 36 | "metadata": {} 37 | } 38 | }, 39 | "statusCode": 201, 40 | "headers": { 41 | "Access-Control-Allow-Origin": "*", 42 | "Cache-Control": "private", 43 | "Content-Type": "application\/json", 44 | "pinterest-generated-by": "coreapp-ngapi-prod-0a01891f", 45 | "pinterest-version": "df0f6f7", 46 | "x-content-type-options": "nosniff", 47 | "x-envoy-upstream-service-time": "184", 48 | "x-pinterest-rid": "800735447484", 49 | "X-RateLimit-Limit": "10", 50 | "X-RateLimit-Remaining": "8", 51 | "Content-Length": "660", 52 | "Date": "Mon, 17 Sep 2018 11:19:29 GMT", 53 | "Connection": "keep-alive", 54 | "Set-Cookie": "_ir=0; Max-Age=1800; HttpOnly; Secure", 55 | "Age": "0" 56 | } 57 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/get_v1_pins_314196511500433397_0b8461cd5574fc97b8ab9b836a092dfe.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "attribution": null, 5 | "creator": { 6 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 7 | "first_name": "H", 8 | "last_name": "O", 9 | "id": "314196648911734959" 10 | }, 11 | "url": "https:\/\/www.pinterest.com\/pin\/314196511500433397\/", 12 | "media": { 13 | "type": "image" 14 | }, 15 | "created_at": "2018-09-17T11:19:30", 16 | "note": "A note!", 17 | "color": null, 18 | "link": "", 19 | "board": { 20 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 21 | "id": "314196580192658592", 22 | "name": "Test" 23 | }, 24 | "image": { 25 | "original": { 26 | "url": "https:\/\/i.pinimg.com\/originals\/18\/27\/4a\/18274ad303abf53cbb225f05cc79d44b.png", 27 | "width": 350, 28 | "height": 150 29 | } 30 | }, 31 | "counts": { 32 | "saves": 0, 33 | "comments": 0 34 | }, 35 | "id": "314196511500433397", 36 | "metadata": {} 37 | } 38 | }, 39 | "statusCode": 200, 40 | "headers": { 41 | "Access-Control-Allow-Origin": "*", 42 | "Cache-Control": "private", 43 | "Content-Type": "application\/json", 44 | "pinterest-generated-by": "coreapp-ngapi-prod-0a01c2e2", 45 | "pinterest-version": "df0f6f7", 46 | "x-content-type-options": "nosniff", 47 | "x-envoy-upstream-service-time": "51", 48 | "x-pinterest-rid": "095431258012", 49 | "X-RateLimit-Limit": "10", 50 | "X-RateLimit-Remaining": "7", 51 | "Content-Length": "652", 52 | "Date": "Mon, 17 Sep 2018 11:19:30 GMT", 53 | "Connection": "keep-alive", 54 | "Set-Cookie": "_ir=0; Max-Age=1800; HttpOnly; Secure", 55 | "Age": "0" 56 | } 57 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/patch_v1_pins_314196511498502249_339d203ec596d5016eca8415d6cb8b1d.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "attribution": null, 5 | "creator": { 6 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 7 | "first_name": "H", 8 | "last_name": "O", 9 | "id": "314196648911734959" 10 | }, 11 | "url": "https:\/\/www.pinterest.com\/pin\/314196511498502249\/", 12 | "media": { 13 | "type": "image" 14 | }, 15 | "created_at": "2018-04-22T15:06:48", 16 | "note": "A new note", 17 | "color": null, 18 | "link": "https:\/\/www.pinterest.com\/r\/pin\/314196511498502249\/4862229257031532989\/7fe6c089b84b780440b115587db3dff272e151d9f22f61b3f2f06746a577269a", 19 | "board": { 20 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 21 | "id": "314196580192658592", 22 | "name": "Test" 23 | }, 24 | "image": { 25 | "original": { 26 | "url": "https:\/\/i.pinimg.com\/originals\/e4\/84\/e3\/e484e3fa1959913947378735eddd969f.png", 27 | "width": 350, 28 | "height": 150 29 | } 30 | }, 31 | "counts": { 32 | "saves": 0, 33 | "comments": 0 34 | }, 35 | "id": "314196511498502249", 36 | "metadata": {} 37 | } 38 | }, 39 | "statusCode": 200, 40 | "headers": { 41 | "Access-Control-Allow-Origin": "*", 42 | "Age": "0", 43 | "Cache-Control": "private", 44 | "Content-Type": "application\/json", 45 | "Pinterest-Version": "4f9f34e", 46 | "X-Content-Type-Options": "nosniff", 47 | "X-Pinterest-RID": "860395769311", 48 | "X-Ratelimit-Limit": "10", 49 | "X-Ratelimit-Remaining": "7", 50 | "Content-Length": "790", 51 | "Date": "Sun, 22 Apr 2018 15:06:49 GMT", 52 | "Connection": "keep-alive", 53 | "Pinterest-Generated-By:": "" 54 | } 55 | } -------------------------------------------------------------------------------- /src/Pinterest/Mapper.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest; 15 | 16 | use JsonMapper; 17 | use ArrayObject; 18 | use Pinterest\Http\Response; 19 | use Pinterest\Objects\PagedList; 20 | use Pinterest\Objects\BaseObject; 21 | 22 | /** 23 | * This class maps an object to a response. 24 | * 25 | * @author Hans Ott 26 | */ 27 | final class Mapper 28 | { 29 | protected $mapper; 30 | 31 | protected $class; 32 | 33 | public function __construct(BaseObject $class) 34 | { 35 | $this->class = $class; 36 | $this->mapper = new JsonMapper(); 37 | $this->mapper->bStrictNullTypes = false; // don't throw exception if any field is null 38 | } 39 | 40 | public function toSingle(Response $response) 41 | { 42 | $data = $response->body->data; 43 | 44 | return $this->mapper->map($data, $this->class); 45 | } 46 | 47 | /** 48 | * Converts an array object to array. 49 | * 50 | * @param \ArrayObject $object The array object to convert. 51 | * 52 | * @return array The converted array. 53 | */ 54 | private function convertToArray(ArrayObject $object) 55 | { 56 | $arr = array(); 57 | $iterator = $object->getIterator(); 58 | while ($iterator->valid()) { 59 | $arr[] = $iterator->current(); 60 | $iterator->next(); 61 | } 62 | 63 | return $arr; 64 | } 65 | 66 | public function toList(Response $response) 67 | { 68 | $data = $response->body->data; 69 | $nextUrl = isset($response->body->page->next) ? $response->body->page->next : null; 70 | 71 | $items = $this->mapper->mapArray( 72 | $data, 73 | new ArrayObject(), 74 | get_class($this->class) 75 | ); 76 | 77 | $items = $this->convertToArray($items); 78 | 79 | return new PagedList($items, $nextUrl); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/Pinterest/responses/get_v1_me_following_interests_bab7fcd2edf55c2f193319138437ca52.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "0": { 5 | "id": "949133422105", 6 | "name": "programming" 7 | }, 8 | "1": { 9 | "id": "898364082388", 10 | "name": "airplanes" 11 | }, 12 | "2": { 13 | "id": "930106275255", 14 | "name": "Funny pictures" 15 | }, 16 | "3": { 17 | "id": "896604368580", 18 | "name": "game of thrones" 19 | }, 20 | "4": { 21 | "id": "907002707958", 22 | "name": "illustrations" 23 | }, 24 | "5": { 25 | "id": "905860166503", 26 | "name": "photography" 27 | }, 28 | "6": { 29 | "id": "896755026367", 30 | "name": "Web design" 31 | }, 32 | "7": { 33 | "id": "920295496421", 34 | "name": "Industrial design" 35 | }, 36 | "8": { 37 | "id": "902065567321", 38 | "name": "design" 39 | }, 40 | "9": { 41 | "id": "918105274631", 42 | "name": "architecture" 43 | }, 44 | "10": { 45 | "id": "899319027242", 46 | "name": "suits" 47 | } 48 | }, 49 | "page": { 50 | "cursor": null, 51 | "next": null 52 | } 53 | }, 54 | "statusCode": 200, 55 | "headers": { 56 | "Access-Control-Allow-Origin": "*", 57 | "Age": "0", 58 | "Cache-Control": "private", 59 | "Content-Type": "application\/json", 60 | "Pinterest-Version": "4f9f34e", 61 | "X-Content-Type-Options": "nosniff", 62 | "X-Pinterest-RID": "704277507684", 63 | "X-Ratelimit-Limit": "10", 64 | "X-Ratelimit-Remaining": "0", 65 | "Content-Length": "569", 66 | "Date": "Sun, 22 Apr 2018 12:04:24 GMT", 67 | "Connection": "keep-alive", 68 | "Pinterest-Generated-By:": "" 69 | } 70 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/patch_v1_pins_314196511500433397_339d203ec596d5016eca8415d6cb8b1d.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "attribution": null, 5 | "creator": { 6 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 7 | "first_name": "H", 8 | "last_name": "O", 9 | "id": "314196648911734959" 10 | }, 11 | "url": "https:\/\/www.pinterest.com\/pin\/314196511500433397\/", 12 | "media": { 13 | "type": "image" 14 | }, 15 | "created_at": "2018-09-17T11:19:30", 16 | "note": "A new note", 17 | "color": null, 18 | "link": "https:\/\/www.pinterest.com\/r\/pin\/314196511500433397\/4862229257031532989\/1291f5dc54b992df93103bce36a88ce5ae5d08d54af734d38d6ac99dba4660c0", 19 | "board": { 20 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 21 | "id": "314196580192658592", 22 | "name": "Test" 23 | }, 24 | "image": { 25 | "original": { 26 | "url": "https:\/\/i.pinimg.com\/originals\/18\/27\/4a\/18274ad303abf53cbb225f05cc79d44b.png", 27 | "width": 350, 28 | "height": 150 29 | } 30 | }, 31 | "counts": { 32 | "saves": 0, 33 | "comments": 0 34 | }, 35 | "id": "314196511500433397", 36 | "metadata": {} 37 | } 38 | }, 39 | "statusCode": 200, 40 | "headers": { 41 | "Access-Control-Allow-Origin": "*", 42 | "Cache-Control": "private", 43 | "Content-Type": "application\/json", 44 | "pinterest-generated-by": "coreapp-ngapi-prod-0a01887a", 45 | "pinterest-version": "df0f6f7", 46 | "x-content-type-options": "nosniff", 47 | "x-envoy-upstream-service-time": "110", 48 | "x-pinterest-rid": "367486938789", 49 | "X-RateLimit-Limit": "10", 50 | "X-RateLimit-Remaining": "9", 51 | "Content-Length": "790", 52 | "Date": "Mon, 17 Sep 2018 11:19:30 GMT", 53 | "Connection": "keep-alive", 54 | "Set-Cookie": "_ir=0; Max-Age=1800; HttpOnly; Secure", 55 | "Age": "0" 56 | } 57 | } -------------------------------------------------------------------------------- /tests/Pinterest/AuthenticationTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Tests; 15 | 16 | use Pinterest\App\Scope; 17 | use Pinterest\Authentication; 18 | use Pinterest\Http\BuzzClient; 19 | 20 | class AuthenticationTest extends TestCase 21 | { 22 | public function getHttpClient() 23 | { 24 | return new BuzzClient(); 25 | } 26 | 27 | public function testConstructWithAccessToken() 28 | { 29 | $accessToken = 'access-token'; 30 | $auth = Authentication::withAccessToken($this->getHttpClient(), 'client-id', 'client-secret', $accessToken); 31 | $this->assertSame($accessToken, $auth->getAccessToken()); 32 | } 33 | 34 | public function testConstructOnlyAccessToken() 35 | { 36 | $accessToken = 'access-token'; 37 | $auth = Authentication::onlyAccessToken($this->getHttpClient(), $accessToken); 38 | $this->assertSame($accessToken, $auth->getAccessToken()); 39 | } 40 | 41 | public function testGetAuthenticationUrl() 42 | { 43 | $auth = new Authentication($this->getHttpClient(), 'client-id', 'client-secret'); 44 | $authUrl = $auth->getAuthenticationUrl('http://localhost', array(Scope::READ_PUBLIC), 'random'); 45 | $excepted = 'https://api.pinterest.com/oauth/?response_type=code&redirect_uri=http%3A%2F%2Flocalhost&client_id=client-id&scope=read_public&state=random'; 46 | $this->assertSame($excepted, $authUrl); 47 | 48 | $this->setExpectedException('Pinterest\Api\Exceptions\InvalidScopeException'); 49 | $auth->getAuthenticationUrl('http://localhost', array('not-valid'), 'random'); 50 | 51 | $this->setExpectedException('Pinterest\Api\Exceptions\TooManyScopesGiven'); 52 | $auth->getAuthenticationUrl('http://localhost', array(Scope::READ_PUBLIC, Scope::READ_PUBLIC, Scope::READ_PUBLIC, Scope::READ_PUBLIC, Scope::READ_PUBLIC), 'random'); 53 | 54 | $this->setExpectedException('Pinterest\Api\Exceptions\AtLeastOneScopeNeeded'); 55 | $auth->getAuthenticationUrl('http://localhost', array(), 'random'); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Pinterest/Objects/User.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Objects; 15 | 16 | /** 17 | * This class represents a user. 18 | * 19 | * @author Hans Ott 20 | */ 21 | final class User implements BaseObject 22 | { 23 | /** 24 | * The required fields. 25 | * 26 | * @return array The required fields. 27 | */ 28 | public static function fields() 29 | { 30 | return array( 31 | 'id', 32 | 'username', 33 | 'first_name', 34 | 'last_name', 35 | 'bio', 36 | 'created_at', 37 | 'counts', 38 | 'image', 39 | 'url', 40 | ); 41 | } 42 | 43 | /** 44 | * The user's id. 45 | * 46 | * @var string 47 | * @required 48 | */ 49 | public $id; 50 | 51 | /** 52 | * The user's Pinterest username. 53 | * 54 | * @var string 55 | */ 56 | public $username; 57 | 58 | /** 59 | * The user's first name. 60 | * 61 | * @var string 62 | */ 63 | public $first_name; 64 | 65 | /** 66 | * The user's last name. 67 | * 68 | * @var string 69 | */ 70 | public $last_name; 71 | 72 | /** 73 | * The user's bio. 74 | * 75 | * @var string 76 | */ 77 | public $bio; 78 | 79 | /** 80 | * Timestamp of creation date. 81 | * 82 | * @var \DateTime 83 | */ 84 | public $created_at; 85 | 86 | /** 87 | * The stats/counts of the User (follower Pins, likes, boards). 88 | * 89 | * @var Stats 90 | */ 91 | public $counts; 92 | 93 | /** 94 | * The images that represents the user. 95 | * 96 | * This is determined by the request. 97 | * 98 | * @var array 99 | */ 100 | public $image; 101 | 102 | /** 103 | * The url to the object on pinterest. 104 | * 105 | * @var string 106 | */ 107 | public $url; 108 | } 109 | -------------------------------------------------------------------------------- /tests/Pinterest/TestCase.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Tests; 15 | 16 | use PHPUnit_Framework_TestCase; 17 | use Pinterest\Http\Response; 18 | 19 | class TestCase extends PHPUnit_Framework_TestCase 20 | { 21 | protected function assertResponse(Response $response) 22 | { 23 | $this->assertInstanceOf('Pinterest\Http\Response', $response); 24 | $this->assertTrue($response->ok()); 25 | $this->assertNotEmpty($response->result()); 26 | } 27 | 28 | public function assertBoard(Response $response) 29 | { 30 | $this->assertResponse($response); 31 | $user = $response->result(); 32 | $this->assertInstanceOf('Pinterest\Objects\Board', $user); 33 | } 34 | 35 | public function assertPin(Response $response) 36 | { 37 | $this->assertResponse($response); 38 | $user = $response->result(); 39 | $this->assertInstanceOf('Pinterest\Objects\Pin', $user); 40 | } 41 | 42 | public function assertUser(Response $response) 43 | { 44 | $this->assertResponse($response); 45 | $user = $response->result(); 46 | $this->assertInstanceOf('Pinterest\Objects\User', $user); 47 | } 48 | 49 | public function assertMultipleBoards(Response $response) 50 | { 51 | $this->assertPagedList($response, 'Pinterest\Objects\Board'); 52 | } 53 | 54 | public function assertMultipleUsers(Response $response) 55 | { 56 | $this->assertPagedList($response, 'Pinterest\Objects\User'); 57 | } 58 | 59 | public function assertMultiplePins(Response $response) 60 | { 61 | $this->assertPagedList($response, 'Pinterest\Objects\Pin'); 62 | } 63 | 64 | private function assertPagedList(Response $response, $object) 65 | { 66 | $this->assertResponse($response); 67 | $pagedList = $response->result(); 68 | $this->assertInstanceOf('Pinterest\Objects\PagedList', $pagedList); 69 | $items = $pagedList->items(); 70 | $this->assertInternalType('array', $items); 71 | $this->assertContainsOnlyInstancesOf($object, $items); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/Pinterest/PagedListTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Tests; 15 | 16 | use stdClass; 17 | use Pinterest\Objects\Pin; 18 | use Pinterest\Objects\User; 19 | use Pinterest\Objects\Board; 20 | use InvalidArgumentException; 21 | use Pinterest\Authentication; 22 | use Pinterest\Objects\PagedList; 23 | 24 | class PagedListTest extends TestCase 25 | { 26 | public function validPinterestObjects() 27 | { 28 | return array( 29 | array(array(new User)), 30 | array(array(new Pin)), 31 | array(array(new Board)), 32 | ); 33 | } 34 | 35 | /** 36 | * @dataProvider validPinterestObjects 37 | */ 38 | public function test_it_accepts_pinterest_objects(array $objects) 39 | { 40 | new PagedList($objects); 41 | } 42 | 43 | public function invalidPinterestObjects() 44 | { 45 | return array( 46 | array(array(1, new User)), 47 | array(array('string')), 48 | array(array(new stdClass)) 49 | ); 50 | } 51 | 52 | /** 53 | * @dataProvider invalidPinterestObjects 54 | * @expectedException InvalidArgumentException 55 | */ 56 | public function test_it_does_not_accept_non_pinterest_objects(array $objects) 57 | { 58 | new PagedList($objects); 59 | } 60 | 61 | public function test_it_has_more_items() 62 | { 63 | new PagedList(array(new User), Authentication::BASE_URI.'/me'); 64 | 65 | $this->setExpectedException('InvalidArgumentException'); 66 | new PagedList(array(new User), 'next-uri'); 67 | } 68 | 69 | public function test_it_returns_the_next_uri() 70 | { 71 | $uri = Authentication::BASE_URI.'/v1/me'; 72 | $pagedList = new PagedList(array(new User), $uri); 73 | $this->assertTrue($pagedList->hasNext()); 74 | $this->assertSame($uri, $pagedList->getNextUrl()); 75 | 76 | $pagedList = new PagedList(array(new User)); 77 | $this->assertFalse($pagedList->hasNext()); 78 | } 79 | 80 | public function test_it_returns_the_items() 81 | { 82 | $items = array(new User, new User); 83 | $pagedList = new PagedList($items); 84 | $this->assertSame($items, $pagedList->items()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/Pinterest/responses/get_v1_me_boards_f540825c41d09d337b65d1bf46a19fe8.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "0": { 5 | "description": "", 6 | "creator": { 7 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 8 | "first_name": "H", 9 | "last_name": "O", 10 | "id": "314196648911734959" 11 | }, 12 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 13 | "created_at": "2016-03-13T14:59:11", 14 | "image": { 15 | "60x60": { 16 | "url": "https:\/\/i.pinimg.com\/60x60\/71\/f5\/64\/71f56468c6aaef1da0e4580d4e687914.jpg", 17 | "width": 60, 18 | "height": 60 19 | } 20 | }, 21 | "counts": { 22 | "pins": 5, 23 | "collaborators": 0, 24 | "followers": 23 25 | }, 26 | "id": "314196580192658592", 27 | "name": "Test" 28 | }, 29 | "1": { 30 | "description": "A simple description", 31 | "creator": { 32 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 33 | "first_name": "H", 34 | "last_name": "O", 35 | "id": "314196648911734959" 36 | }, 37 | "url": "https:\/\/www.pinterest.com\/hanso1\/my-board\/", 38 | "created_at": "2016-03-13T14:57:24", 39 | "image": { 40 | "60x60": { 41 | "url": null, 42 | "width": 60, 43 | "height": 60 44 | } 45 | }, 46 | "counts": { 47 | "pins": 0, 48 | "collaborators": 0, 49 | "followers": 23 50 | }, 51 | "id": "314196580192658590", 52 | "name": "My board!" 53 | } 54 | } 55 | }, 56 | "statusCode": 200, 57 | "headers": { 58 | "Access-Control-Allow-Origin": "*", 59 | "Age": "0", 60 | "Cache-Control": "private", 61 | "Content-Type": "application\/json", 62 | "Pinterest-Version": "4f9f34e", 63 | "X-Content-Type-Options": "nosniff", 64 | "X-Pinterest-RID": "872367876426", 65 | "X-Ratelimit-Limit": "10", 66 | "X-Ratelimit-Remaining": "6", 67 | "Content-Length": "899", 68 | "Date": "Sun, 22 Apr 2018 12:04:21 GMT", 69 | "Connection": "keep-alive", 70 | "Pinterest-Generated-By:": "" 71 | } 72 | } -------------------------------------------------------------------------------- /src/Pinterest/Objects/Pin.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Objects; 15 | 16 | /** 17 | * This class represents a pin. 18 | * 19 | * @author Hans Ott 20 | */ 21 | final class Pin implements BaseObject 22 | { 23 | /** 24 | * The required fields. 25 | * 26 | * @return array The required fields. 27 | */ 28 | public static function fields() 29 | { 30 | return array( 31 | 'id', 32 | 'link', 33 | 'url', 34 | 'board', 35 | 'created_at', 36 | 'note', 37 | 'color', 38 | 'counts', 39 | 'media', 40 | 'attribution', 41 | 'image', 42 | 'metadata', 43 | ); 44 | } 45 | 46 | /** 47 | * The Pin's id. 48 | * 49 | * @var string 50 | * @required 51 | */ 52 | public $id; 53 | 54 | /** 55 | * The URL of the web page where the Pin was created. 56 | * 57 | * @var string 58 | */ 59 | public $link; 60 | 61 | /** 62 | * The url to the object on pinterest. 63 | * 64 | * @var string 65 | */ 66 | public $url; 67 | 68 | /** 69 | * The board the Pin is in. 70 | * 71 | * @var Board 72 | */ 73 | public $board; 74 | 75 | /** 76 | * ISO 8601 Timestamp of creation date. 77 | * 78 | * @var \DateTime 79 | */ 80 | public $created_at; 81 | 82 | /** 83 | * The description of the Pin by the creator. 84 | * 85 | * @var string 86 | */ 87 | public $note; 88 | 89 | /** 90 | * The dominant color of the Pin image. 91 | * 92 | * @var string 93 | */ 94 | public $color; 95 | 96 | /** 97 | * The stats/counts of the Pin (saves and comments). 98 | * 99 | * @var Stats 100 | */ 101 | public $counts; 102 | 103 | /** 104 | * Information about the media type, including whether it's an "image" or "video". 105 | * 106 | * @var array 107 | */ 108 | public $media; 109 | 110 | /** 111 | * Attribution information. 112 | * 113 | * @var array 114 | */ 115 | public $attribution; 116 | 117 | /** 118 | * The images that represents the Pin. This is determined by the request. 119 | * 120 | * @var array 121 | */ 122 | public $image; 123 | 124 | /** 125 | * Extra information including Pin type (recipe, article, etc.) and related data (ingredients, author, etc). 126 | * 127 | * @var array 128 | */ 129 | public $metadata; 130 | } 131 | -------------------------------------------------------------------------------- /src/Pinterest/Objects/PagedList.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Objects; 15 | 16 | use InvalidArgumentException; 17 | use Pinterest\Authentication; 18 | 19 | /** 20 | * This class represents a paged list. 21 | * 22 | * @author Hans Ott 23 | */ 24 | final class PagedList 25 | { 26 | /** 27 | * The paged list items. 28 | * 29 | * @var array 30 | */ 31 | private $items; 32 | 33 | /** 34 | * The url for retrieving the next set of items. 35 | * 36 | * @var string 37 | */ 38 | private $nextUrl; 39 | 40 | /** 41 | * Creates a new paged list. 42 | * 43 | * @param array $items The paged list items. 44 | * @param string $nextUrl The url for retrieving the next set of items. 45 | */ 46 | public function __construct(array $items = array(), $nextUrl = null) 47 | { 48 | $this->guardThatTheseAreAllPinterestObjects($items); 49 | $this->assertValidUri($nextUrl); 50 | $this->items = $items; 51 | $this->nextUrl = $nextUrl; 52 | } 53 | 54 | /** 55 | * Returns the items. 56 | * 57 | * @return array The items. 58 | */ 59 | public function items() 60 | { 61 | return $this->items; 62 | } 63 | 64 | /** 65 | * Returns whether the paged list has more items. 66 | * 67 | * @return bool Whether there are more items. 68 | */ 69 | public function hasNext() 70 | { 71 | return !empty($this->nextUrl); 72 | } 73 | 74 | /** 75 | * Returns the url to get the next set of items. 76 | * 77 | * @return string The url to get the next set of items. 78 | */ 79 | public function getNextUrl() 80 | { 81 | return $this->nextUrl; 82 | } 83 | 84 | /** 85 | * Checks if all items are pinterest objects. 86 | * 87 | * @param array $items 88 | * 89 | * @throws InvalidArgumentException 90 | */ 91 | private function guardThatTheseAreAllPinterestObjects(array $items) 92 | { 93 | foreach ($items as $item) { 94 | if (!($item instanceof BaseObject)) { 95 | throw new InvalidArgumentException(sprintf( 96 | 'Expected "Pinterest\Objects\BaseObject" but instead got: "%s"', 97 | is_object($item) ? get_class($item) : gettype($item) 98 | )); 99 | } 100 | } 101 | } 102 | 103 | /** 104 | * Checks if the next uri is valid. 105 | * 106 | * @throws InvalidArgumentException 107 | * 108 | * @param $nextUri 109 | */ 110 | private function assertValidUri($nextUri) 111 | { 112 | if ($nextUri === null) { 113 | return; 114 | } 115 | 116 | if (strpos($nextUri, Authentication::BASE_URI) === false) { 117 | throw new InvalidArgumentException( 118 | 'Not a pinterest api uri' 119 | ); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Pinterest/Http/Request.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Http; 15 | 16 | /** 17 | * The request class. 18 | * 19 | * @author Hans Ott 20 | * @author Toon Daelman 21 | */ 22 | final class Request 23 | { 24 | /** 25 | * The http method. 26 | * 27 | * @var string 28 | */ 29 | private $method; 30 | 31 | /** 32 | * The endpoint to call. 33 | * 34 | * Relative url. 35 | * 36 | * @var string 37 | */ 38 | private $endpoint; 39 | 40 | /** 41 | * The parameters to pass. 42 | * 43 | * @var array 44 | */ 45 | private $params; 46 | 47 | /** 48 | * The headers to pass. 49 | * 50 | * @var array 51 | */ 52 | private $headers; 53 | 54 | /** 55 | * The constructor. 56 | * 57 | * @param string $method The http method. 58 | * @param string $endpoint The relative url to call. 59 | * @param array $params The parameters. 60 | * @param array $headers The headers. 61 | */ 62 | public function __construct($method, $endpoint, array $params = array(), array $headers = array()) 63 | { 64 | $this->method = strtoupper((string) $method); 65 | $this->endpoint = (string) $endpoint; 66 | $this->params = $params; 67 | $this->headers = $headers; 68 | } 69 | 70 | /** 71 | * Sets the fields. 72 | * 73 | * @param array $fields The fields to return. 74 | * 75 | * @return Request The current Request instance. 76 | */ 77 | public function setFields(array $fields) 78 | { 79 | $merge = array( 80 | 'fields' => implode(',', $fields), 81 | ); 82 | $this->params = array_replace($this->params, $merge); 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * Get the http (lowercase) method. 89 | * 90 | * @return string 91 | */ 92 | public function getMethod() 93 | { 94 | return $this->method; 95 | } 96 | 97 | /** 98 | * Get the http endpoint. 99 | * 100 | * @return string 101 | */ 102 | public function getEndpoint() 103 | { 104 | return $this->endpoint; 105 | } 106 | 107 | /** 108 | * Get the http parameters. 109 | * 110 | * @return array 111 | */ 112 | public function getParams() 113 | { 114 | return $this->params; 115 | } 116 | 117 | /** 118 | * Get the http headers. 119 | * 120 | * @return array 121 | */ 122 | public function getHeaders() 123 | { 124 | return $this->headers; 125 | } 126 | 127 | /** 128 | * Is this a POST request? 129 | * 130 | * @return bool 131 | */ 132 | public function isPost() 133 | { 134 | return $this->getMethod() === 'POST'; 135 | } 136 | 137 | /** 138 | * Is this a GET request? 139 | * 140 | * @return bool 141 | */ 142 | public function isGet() 143 | { 144 | return $this->getMethod() === 'GET'; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Pinterest/Http/BuzzClient.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Http; 15 | 16 | use Exception; 17 | use Buzz\Browser; 18 | use Pinterest\Image; 19 | use Buzz\Client\Curl; 20 | use Buzz\Message\Form\FormUpload; 21 | use Buzz\Message\Form\FormRequest; 22 | use Buzz\Exception\RequestException; 23 | use Buzz\Message\Response as BuzzResponse; 24 | 25 | /** 26 | * The implemented http client class (uses Buzz). 27 | * 28 | * @link https://github.com/kriswallsmith/Buzz 29 | * 30 | * @author Toon Daelman 31 | */ 32 | class BuzzClient implements ClientInterface 33 | { 34 | /** 35 | * Buzz browser. 36 | * 37 | * @var Browser 38 | */ 39 | private $client; 40 | 41 | /** 42 | * Creates a new buzz client. 43 | * 44 | * @param \Buzz\Client\ClientInterface|null $client 45 | */ 46 | public function __construct(\Buzz\Client\ClientInterface $client = null) 47 | { 48 | if ($client instanceof ClientInterface) { 49 | $this->client = $client; 50 | } else { 51 | // Backwards compatible 52 | $curl = new Curl(); 53 | $this->client = new Browser($curl); 54 | } 55 | } 56 | 57 | /** 58 | * Converts a buzz response to a pinterest response. 59 | * 60 | * @param Request $request The request. 61 | * @param BuzzResponse $buzzResponse The buzz response. 62 | * 63 | * @return Response The response. 64 | */ 65 | private static function convertResponse(Request $request, BuzzResponse $buzzResponse) 66 | { 67 | $statusCode = $buzzResponse->getStatusCode(); 68 | $rawBody = (string) $buzzResponse->getContent(); 69 | 70 | $rawHeaders = $buzzResponse->getHeaders(); 71 | $headers = array(); 72 | foreach ($rawHeaders as $header) { 73 | if (stristr($header, 'HTTP/1.')) { 74 | continue; 75 | } 76 | 77 | $parts = explode(': ', $header); 78 | 79 | if (count($parts) !== 2) { 80 | $headers[$parts[0]] = ''; 81 | continue; 82 | } 83 | 84 | list ($key, $value) = $parts; 85 | $headers[$key] = $value; 86 | } 87 | 88 | return new Response($request, $statusCode, $rawBody, $headers); 89 | } 90 | 91 | /** 92 | * Executes a http request. 93 | * 94 | * @param Request $request The http request. 95 | * 96 | * @return Response The http response. 97 | */ 98 | public function execute(Request $request) 99 | { 100 | $method = $request->getMethod(); 101 | $endpoint = $request->getEndpoint(); 102 | $params = $request->getParams(); 103 | $headers = $request->getHeaders(); 104 | 105 | try { 106 | if ($method === 'GET') { 107 | $buzzResponse = $this->client->call( 108 | $endpoint.'?'.http_build_query($params), 109 | $method, 110 | $headers, 111 | array() 112 | ); 113 | } else { 114 | $buzzRequest = new FormRequest(); 115 | $buzzRequest->fromUrl($endpoint); 116 | $buzzRequest->setMethod($method); 117 | $buzzRequest->setHeaders($headers); 118 | foreach ($params as $key => $value) { 119 | if ($value instanceof Image) { 120 | $value = new FormUpload($value->getData()); 121 | } 122 | 123 | $buzzRequest->setField($key, $value); 124 | } 125 | 126 | $buzzResponse = new BuzzResponse(); 127 | $this->client->send($buzzRequest, $buzzResponse); 128 | } 129 | } catch (RequestException $e) { 130 | throw new Exception($e->getMessage()); 131 | } 132 | 133 | return static::convertResponse($request, $buzzResponse); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Pinterest/Http/Response.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Http; 15 | 16 | use Pinterest\Http\Exceptions\MalformedJson; 17 | 18 | /** 19 | * The response class. 20 | * 21 | * @author Hans Ott 22 | * @author Toon Daelman 23 | */ 24 | final class Response 25 | { 26 | /** 27 | * The parsed response body. 28 | * 29 | * @var mixed 30 | */ 31 | public $body; 32 | 33 | /** 34 | * The response status code. 35 | * 36 | * @var int 37 | */ 38 | private $statusCode; 39 | 40 | /** 41 | * The raw response body. 42 | * 43 | * @var string 44 | */ 45 | private $rawBody; 46 | 47 | /** 48 | * The HTTP headers. 49 | * 50 | * @var array 51 | */ 52 | private $headers; 53 | 54 | /** 55 | * The processed result. 56 | * 57 | * @var mixed 58 | */ 59 | private $result; 60 | 61 | /** 62 | * The request object. 63 | * 64 | * @var Request 65 | */ 66 | private $request; 67 | 68 | /** 69 | * The constructor. 70 | * 71 | * @param Request $request The request object. 72 | * @param int $statusCode The status code. 73 | * @param string $rawBody The raw response body. 74 | * @param array $headers A key => value representation of response headers. 75 | * 76 | * @throws MalformedJson 77 | */ 78 | public function __construct(Request $request, $statusCode, $rawBody, array $headers) 79 | { 80 | $this->request = $request; 81 | $this->statusCode = (int) $statusCode; 82 | $this->rawBody = (string) $rawBody; 83 | $this->body = $this->statusCode >= 200 && $this->statusCode < 300 ? $this->parseJson($this->rawBody) : null; 84 | $this->headers = $headers; 85 | } 86 | 87 | /** 88 | * Checks if the response is okay. 89 | * 90 | * @return bool Whether the response is okay. 91 | */ 92 | public function ok() 93 | { 94 | return ( 95 | !isset($this->body->error) 96 | && $this->statusCode >= 200 97 | && $this->statusCode < 300 98 | ); 99 | } 100 | 101 | /** 102 | * Get the HTTP status code. 103 | * 104 | * @return int 105 | */ 106 | public function getStatusCode() 107 | { 108 | return $this->statusCode; 109 | } 110 | 111 | /** 112 | * Get the error message. 113 | * 114 | * @return null|string 115 | */ 116 | public function getError() 117 | { 118 | return !empty($this->body->message) ? (string) $this->body->message : null; 119 | } 120 | 121 | /** 122 | * Checks if the response is rate-limited. 123 | * 124 | * @return bool Whether the response is rate-limited. 125 | */ 126 | public function rateLimited() 127 | { 128 | return $this->statusCode === 429; 129 | } 130 | 131 | /** 132 | * Parse the response json. 133 | * 134 | * @param string $rawBody The raw response body. 135 | * @param bool $toArray Return as array? 136 | * 137 | * @throws MalformedJson 138 | * 139 | * @return mixed The parsed json. 140 | */ 141 | private function parseJson($rawBody, $toArray = false) 142 | { 143 | $json = json_decode($rawBody, $toArray); 144 | if (json_last_error() === JSON_ERROR_NONE) { 145 | return $json; 146 | } else { 147 | throw new MalformedJson(); 148 | } 149 | } 150 | 151 | /** 152 | * Get the processed result. 153 | * 154 | * @return mixed The processed result. 155 | */ 156 | public function result() 157 | { 158 | return $this->result; 159 | } 160 | 161 | /** 162 | * Set the processed result. 163 | * 164 | * @param mixed $result The result. 165 | * 166 | * @return $this 167 | */ 168 | public function setResult($result) 169 | { 170 | $this->result = $result; 171 | 172 | return $this; 173 | } 174 | 175 | /** 176 | * Get the request object. 177 | * 178 | * @return Request 179 | */ 180 | public function getRequest() 181 | { 182 | return $this->request; 183 | } 184 | 185 | /** 186 | * Get the HTTP Headers. 187 | * 188 | * @return array 189 | */ 190 | public function getHeaders() 191 | { 192 | return $this->headers; 193 | } 194 | 195 | /** 196 | * Get a specific HTTP header. 197 | * 198 | * @param string $header 199 | * 200 | * @return string|null The header value. 201 | */ 202 | public function getHeader($header) 203 | { 204 | $header = strtolower($header); 205 | foreach ($this->headers as $name => $value) { 206 | if (strtolower($name) === $header) { 207 | return $value; 208 | } 209 | } 210 | 211 | return null; 212 | } 213 | 214 | /** 215 | * Get the rate-limit. 216 | * 217 | * @return int|null The rate-limit. 218 | */ 219 | public function getRateLimit() 220 | { 221 | $limit = $this->getHeader('X-RateLimit-Limit'); 222 | 223 | return is_numeric($limit) ? (int) $limit : null; 224 | } 225 | 226 | /** 227 | * Get the remaining requests before reaching the rate-limit. 228 | * 229 | * @return int|null The remaining requests. 230 | */ 231 | public function getRemainingRequests() 232 | { 233 | $remaining = $this->getHeader('X-RateLimit-Remaining'); 234 | 235 | return is_numeric($remaining) ? (int) $remaining : null; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /tests/Pinterest/MockClient.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Tests; 15 | 16 | use stdClass; 17 | use Pinterest\Image; 18 | use Pinterest\Http\Request; 19 | use Pinterest\Http\Response; 20 | use Pinterest\Http\ClientInterface; 21 | 22 | /** 23 | * This http client mocks responses. 24 | * 25 | * The responses are stored as json. 26 | * If no response is found for a request, 27 | * the file will be automatically created. 28 | * 29 | * @author Hans Ott 30 | */ 31 | final class MockClient implements ClientInterface 32 | { 33 | /** 34 | * The http client to fallback. 35 | * 36 | * @var ClientInterface 37 | */ 38 | private $http; 39 | 40 | /** 41 | * The caching directory. 42 | * 43 | * @var string 44 | */ 45 | protected $cacheDir; 46 | 47 | /** 48 | * Creates a new mocking client. 49 | * 50 | * @param ClientInterface $http The fallback client to use. 51 | * @param string $cacheDir 52 | */ 53 | public function __construct(ClientInterface $http, $cacheDir) 54 | { 55 | $this->http = $http; 56 | $this->cacheDir = $cacheDir; 57 | } 58 | 59 | /** 60 | * Execute an Http request. 61 | * 62 | * @param Request $request The Http Request 63 | * 64 | * @return Response The Http Response 65 | */ 66 | public function execute(Request $request) 67 | { 68 | return $this->makeResponse($request); 69 | } 70 | 71 | /** 72 | * Get the path of a url. 73 | * 74 | * @param $url 75 | * 76 | * @return string 77 | */ 78 | private static function getPath($url) 79 | { 80 | return parse_url($url, PHP_URL_PATH); 81 | } 82 | 83 | /** 84 | * Convert parameters to a string. 85 | * 86 | * Note: It hides certain fields (like image data). 87 | * 88 | * @param $params 89 | * 90 | * @return string 91 | */ 92 | private static function paramsToString(array $params) 93 | { 94 | $hiddenParams = array( 95 | 'image_url', 96 | 'image_base64', 97 | 'image', 98 | ); 99 | 100 | foreach ($hiddenParams as $param) { 101 | if (isset($params[$param])) { 102 | $value = $params[$param]; 103 | if ($value instanceof Image) { 104 | $value = $value->getData(); 105 | } 106 | $params[$param] = $value; 107 | } 108 | } 109 | 110 | return md5(implode('_', $params)); 111 | } 112 | 113 | /** 114 | * Get the caching file for a request. 115 | * 116 | * @param Request $request The request. 117 | * 118 | * @return string The path to the caching file. 119 | */ 120 | private function getFilePath(Request $request) 121 | { 122 | $endpoint = $request->getEndpoint(); 123 | $path = static::getPath($endpoint); 124 | $method = strtolower($request->getMethod()); 125 | $params = static::paramsToString($request->getParams()); 126 | $file = $method.$path.$params; 127 | $chars = array('/', ':', '.', ',', ' '); 128 | $file = static::str_replace($chars, '_', $file); 129 | 130 | return sprintf('%s/%s.json', $this->cacheDir, $file); 131 | } 132 | 133 | /** 134 | * Replace a set of characters with another char in a string. 135 | * 136 | * @param array $chars The chars to replace. 137 | * @param string $replace The replacement char. 138 | * @param string $subject The subject. 139 | * 140 | * @return string The string with replaced chars. 141 | */ 142 | private static function str_replace(array $chars, $replace, $subject) 143 | { 144 | foreach ($chars as $char) { 145 | $subject = str_replace($char, $replace, $subject); 146 | } 147 | 148 | return $subject; 149 | } 150 | 151 | /** 152 | * Encode a response in json format. 153 | * 154 | * @param Response $response The response. 155 | * 156 | * @return string The json encoded response. 157 | */ 158 | private static function encode(Response $response) 159 | { 160 | $resp = new stdClass(); 161 | $resp->body = $response->body; 162 | $resp->statusCode = $response->getStatusCode(); 163 | $resp->headers = $response->getHeaders(); 164 | 165 | return json_encode($resp, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT); 166 | } 167 | 168 | /** 169 | * Decode a json encoded response. 170 | * 171 | * @param Request $request The request. 172 | * @param string $json The json encoded response. 173 | * 174 | * @return Response The response object. 175 | */ 176 | private static function decode(Request $request, $json) 177 | { 178 | $response = json_decode($json); 179 | $body = (object) $response->body; 180 | $body = json_encode($body); 181 | $code = (int) $response->statusCode; 182 | $headers = (array) $response->headers; 183 | 184 | return new Response($request, $code, $body, $headers); 185 | } 186 | 187 | /** 188 | * Writes the response to a json file. 189 | * 190 | * @param Request $request The request. 191 | * @param Response $response The response. 192 | */ 193 | private function writeToFile(Request $request, Response $response) 194 | { 195 | $file = $this->getFilePath($request); 196 | $contents = static::encode($response); 197 | file_put_contents($file, $contents); 198 | } 199 | 200 | /** 201 | * Build the response object for a stored json response. 202 | * 203 | * @param Request $request The request. 204 | * 205 | * @return Response The response object. 206 | */ 207 | private function getFromFile(Request $request) 208 | { 209 | $file = $this->getFilePath($request); 210 | $contents = file_get_contents($file); 211 | 212 | return static::decode($request, $contents); 213 | } 214 | 215 | /** 216 | * Check whether a response exists. 217 | * 218 | * @param Request $request The request. 219 | * 220 | * @return bool Whether a response exists. 221 | */ 222 | private function responseExists(Request $request) 223 | { 224 | $file = $this->getFilePath($request); 225 | 226 | return file_exists($file); 227 | } 228 | 229 | /** 230 | * Make a response for a request. 231 | * 232 | * @param Request $request The request. 233 | * 234 | * @return Response The response. 235 | */ 236 | private function makeResponse(Request $request) 237 | { 238 | if ($this->responseExists($request)) { 239 | return $this->getFromFile($request); 240 | } 241 | 242 | $response = $this->http->execute($request); 243 | 244 | if ($response->ok()) { 245 | $this->writeToFile($request, $response); 246 | } else { 247 | echo PHP_EOL.'Request failed >>> '.$request->getMethod().': '.$request->getEndpoint().PHP_EOL; 248 | print_r($response); 249 | echo PHP_EOL; 250 | } 251 | 252 | return $response; 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/Pinterest/Authentication.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest; 15 | 16 | use Pinterest\App\Scope; 17 | use Pinterest\Http\Request; 18 | use Pinterest\Http\ClientInterface; 19 | use Pinterest\Api\Exceptions\TokenMissing; 20 | use Pinterest\Api\Exceptions\TooManyScopesGiven; 21 | use Pinterest\Api\Exceptions\AtLeastOneScopeNeeded; 22 | use Pinterest\Api\Exceptions\InvalidScopeException; 23 | use Pinterest\Http\Exceptions\RateLimitedReached; 24 | 25 | /** 26 | * This class is responsible for authenticating requests. 27 | * 28 | * @author Toon Daelman 29 | */ 30 | final class Authentication implements ClientInterface 31 | { 32 | /** 33 | * The API base uri. 34 | * 35 | * @var string 36 | */ 37 | const BASE_URI = 'https://api.pinterest.com'; 38 | 39 | /** 40 | * The API version. 41 | * 42 | * @var string 43 | */ 44 | const API_VERSION = 'v1'; 45 | 46 | /** 47 | * The HTTP client. 48 | * 49 | * @var ClientInterface 50 | */ 51 | private $http; 52 | 53 | /** 54 | * The client ID. 55 | * 56 | * @var string 57 | */ 58 | private $clientId; 59 | 60 | /** 61 | * The client secret. 62 | * 63 | * @var string 64 | */ 65 | private $clientSecret; 66 | 67 | /** 68 | * The access token. 69 | * 70 | * @var string 71 | */ 72 | private $accessToken; 73 | 74 | /** 75 | * The Constructor. 76 | * 77 | * @param ClientInterface $client The http client. 78 | * @param string $clientId The client id. 79 | * @param string $clientSecret The client secret. 80 | */ 81 | public function __construct(ClientInterface $client, $clientId, $clientSecret) 82 | { 83 | $this->http = $client; 84 | $this->clientId = (string) $clientId; 85 | $this->clientSecret = (string) $clientSecret; 86 | } 87 | 88 | /** 89 | * Alternative constructor for when we already have an accessToken. 90 | * 91 | * @param ClientInterface $client The (un-authenticated) HTTP client. 92 | * @param string $clientId The client id. 93 | * @param string $clientSecret The client secret. 94 | * @param string $accessToken The OAuth access token. 95 | * 96 | * @return static 97 | */ 98 | public static function withAccessToken( 99 | ClientInterface $client, 100 | $clientId, 101 | $clientSecret, 102 | $accessToken 103 | ) { 104 | $authentication = new static($client, $clientId, $clientSecret); 105 | $authentication->accessToken = (string) $accessToken; 106 | 107 | return $authentication; 108 | } 109 | 110 | /** 111 | * Alternative constructor for when we only have an accessToken. 112 | * 113 | * ATTENTION: only the execute method will work, as the others need client id and secret. 114 | * 115 | * @param ClientInterface $client The HTTP client. 116 | * @param string $accessToken The OAuth access token. 117 | * 118 | * @return static 119 | */ 120 | public static function onlyAccessToken( 121 | ClientInterface $client, 122 | $accessToken 123 | ) { 124 | $authentication = new static($client, null, null); 125 | $authentication->accessToken = (string) $accessToken; 126 | 127 | return $authentication; 128 | } 129 | 130 | private function getBaseUriWithVersion() 131 | { 132 | return sprintf('%s/%s/', static::BASE_URI, static::API_VERSION); 133 | } 134 | 135 | /** 136 | * First step of the OAuth process. 137 | * 138 | * @param string $redirectUrl The OAuth redirect url (where code gets sent). 139 | * @param array $scopes An array of scopes (see assertValidScopes). 140 | * @param string $state A unique code you can use to check if the redirect is not spoofed. 141 | * 142 | * @return string The redirect url. 143 | */ 144 | public function getAuthenticationUrl($redirectUrl, array $scopes, $state) 145 | { 146 | $this->assertValidScopes($scopes); 147 | 148 | $params = array( 149 | 'response_type' => 'code', 150 | 'redirect_uri' => (string) $redirectUrl, 151 | 'client_id' => $this->clientId, 152 | 'scope' => implode(',', $scopes), 153 | 'state' => (string) $state, 154 | ); 155 | 156 | return sprintf( 157 | 'https://api.pinterest.com/oauth/?%s', 158 | http_build_query($params) 159 | ); 160 | } 161 | 162 | /** 163 | * Checks if an array of given scopes contains only valid scopes (and at least one). 164 | * 165 | * @param array $scopes The array of scopes to check. 166 | * 167 | * @throws InvalidScopeException When invalid scope in the given array. 168 | * @throws AtLeastOneScopeNeeded When no scopes given. 169 | * @throws TooManyScopesGiven When double scopes in the list. 170 | */ 171 | private function assertValidScopes(array $scopes) 172 | { 173 | $allowedScopes = array( 174 | Scope::READ_PUBLIC, 175 | Scope::WRITE_PUBLIC, 176 | Scope::READ_RELATIONSHIPS, 177 | Scope::WRITE_RELATIONSHIPS, 178 | ); 179 | 180 | foreach ($scopes as $scope) { 181 | if (!in_array($scope, $allowedScopes, true)) { 182 | throw new InvalidScopeException($scope); 183 | } 184 | } 185 | 186 | if (count($scopes) < 1) { 187 | throw new AtLeastOneScopeNeeded(); 188 | } 189 | 190 | if (count($scopes) > count($allowedScopes)) { 191 | throw new TooManyScopesGiven(); 192 | } 193 | } 194 | 195 | /** 196 | * Second step of the OAuth process. 197 | * 198 | * @param string $code The OAuth code, caught from the redirect page. 199 | * 200 | * @return string The OAuth access token. 201 | * 202 | * @throws TokenMissing 203 | * @throws RateLimitedReached 204 | */ 205 | public function requestAccessToken($code) 206 | { 207 | $request = new Request( 208 | 'POST', 209 | $this->getBaseUriWithVersion().'oauth/token', 210 | array( 211 | 'grant_type' => 'authorization_code', 212 | 'client_id' => $this->clientId, 213 | 'client_secret' => $this->clientSecret, 214 | 'code' => (string) $code, 215 | ) 216 | ); 217 | 218 | $response = $this->http->execute($request); 219 | 220 | if ($response->rateLimited()) { 221 | throw new RateLimitedReached($response); 222 | } 223 | 224 | if ( 225 | !isset($response->body) 226 | || !isset($response->body->access_token) 227 | ) { 228 | throw new TokenMissing(); 229 | } 230 | 231 | $this->accessToken = $response->body->access_token; 232 | 233 | return $this->accessToken; 234 | } 235 | 236 | /** 237 | * Executes a http request. 238 | * 239 | * @param Request $request The http request. 240 | * 241 | * @return Http\Response The http response. 242 | */ 243 | public function execute(Request $request) 244 | { 245 | $headers = $request->getHeaders(); 246 | $headers['Authorization'] = sprintf('BEARER %s', $this->accessToken); 247 | 248 | $authenticatedRequest = new Request( 249 | $request->getMethod(), 250 | $this->getBaseUriWithVersion().$request->getEndpoint(), 251 | $request->getParams(), 252 | $headers 253 | ); 254 | 255 | return $this->http->execute($authenticatedRequest); 256 | } 257 | 258 | /** 259 | * Returns the access token for persisting in some storage. 260 | * 261 | * @return string The OAuth access token. 262 | */ 263 | public function getAccessToken() 264 | { 265 | return $this->accessToken; 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /tests/Pinterest/ApiTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest\Tests; 15 | 16 | use Pinterest\Api; 17 | use Pinterest\Image; 18 | use RuntimeException; 19 | use Pinterest\Objects\Pin; 20 | use InvalidArgumentException; 21 | use Pinterest\Authentication; 22 | use Pinterest\Http\BuzzClient; 23 | use Pinterest\Objects\PagedList; 24 | 25 | class ApiTest extends TestCase 26 | { 27 | /** 28 | * @var Api 29 | */ 30 | protected $api; 31 | protected $boardId; 32 | 33 | public function setUp() 34 | { 35 | $cacheDir = sprintf('%s/responses', __DIR__); 36 | if (!is_dir($cacheDir)) { 37 | throw new RuntimeException('The cache directory does not exist or is not a directory'); 38 | } 39 | $client = new BuzzClient(); 40 | $mocked = new MockClient($client, $cacheDir); 41 | $auth = Authentication::withAccessToken($mocked, null, null, getenv('ACCESS_TOKEN')); 42 | $this->api = new Api($auth); 43 | $this->boardId = getenv('BOARD_ID'); 44 | } 45 | 46 | public function test_it_gets_users() 47 | { 48 | $this->assertUser($this->api->getUser('hanso1')); 49 | $this->assertUser($this->api->getUser('314196648911734959')); 50 | } 51 | 52 | /** 53 | * @expectedException InvalidArgumentException 54 | */ 55 | public function test_it_refuses_to_fetch_user_without_username_or_id() 56 | { 57 | $this->api->getUser(''); 58 | } 59 | 60 | public function test_it_gets_a_board() 61 | { 62 | $this->assertBoard($this->api->getBoard('314196580192658592')); 63 | } 64 | 65 | public function test_it_gets_the_users_boards() 66 | { 67 | $this->assertMultipleBoards($this->api->getUserBoards()); 68 | } 69 | 70 | public function test_it_gets_the_users_pins() 71 | { 72 | $this->assertMultiplePins($this->api->getUserPins()); 73 | } 74 | 75 | public function test_it_gets_the_current_user() 76 | { 77 | $this->assertUser($this->api->getCurrentUser()); 78 | } 79 | 80 | public function test_it_get_the_users_followers() 81 | { 82 | $this->assertMultipleUsers($this->api->getUserFollowers()); 83 | } 84 | 85 | public function test_it_gets_the_boards_that_the_user_follows() 86 | { 87 | $this->assertMultipleBoards($this->api->getUserFollowingBoards()); 88 | } 89 | 90 | public function test_it_gets_the_users_that_the_user_follows() 91 | { 92 | $this->assertMultipleUsers($this->api->getUserFollowing()); 93 | } 94 | 95 | public function test_it_gets_the_users_interests() 96 | { 97 | $this->assertMultipleBoards($this->api->getUserInterests()); 98 | } 99 | 100 | public function test_it_follows_and_unfollows_a_user() 101 | { 102 | $username = 'engagor'; 103 | 104 | $response = $this->api->followUser($username); 105 | $this->assertInstanceOf('Pinterest\Http\Response', $response); 106 | $this->assertTrue($response->ok()); 107 | 108 | $response = $this->api->unfollowUser($username); 109 | $this->assertInstanceOf('Pinterest\Http\Response', $response); 110 | $this->assertTrue($response->ok()); 111 | } 112 | 113 | /** 114 | * @expectedException InvalidArgumentException 115 | */ 116 | public function test_it_throws_an_exception_when_trying_to_follow_a_user_with_empty_username() 117 | { 118 | $this->setExpectedException('InvalidArgumentException'); 119 | $username = ''; 120 | $this->api->followUser($username); 121 | } 122 | 123 | /** 124 | * @expectedException InvalidArgumentException 125 | */ 126 | public function test_it_throws_an_exception_when_trying_to_unfollow_a_user_with_empty_username() 127 | { 128 | $this->setExpectedException('InvalidArgumentException'); 129 | $username = ''; 130 | $this->api->unfollowUser($username); 131 | } 132 | 133 | public function test_it_follows_and_unfollows_a_board() 134 | { 135 | $username = 'engagor'; 136 | $boardName = 'engagor-in-the-news'; 137 | 138 | $response = $this->api->followBoard($username, $boardName); 139 | $this->assertInstanceOf('Pinterest\Http\Response', $response); 140 | $this->assertTrue($response->ok()); 141 | 142 | $this->api->unfollowBoard($username, $boardName); 143 | $this->assertInstanceOf('Pinterest\Http\Response', $response); 144 | $this->assertTrue($response->ok()); 145 | } 146 | 147 | /** 148 | * @expectedException InvalidArgumentException 149 | */ 150 | public function test_it_throws_an_exception_when_trying_to_follow_a_board_with_empty_username() 151 | { 152 | $username = ''; 153 | $boardName = 'engagor-in-the-news'; 154 | $this->api->followBoard($username, $boardName); 155 | } 156 | 157 | /** 158 | * @expectedException InvalidArgumentException 159 | */ 160 | public function test_it_throws_an_exception_when_trying_to_follow_a_board_with_empty_board_name() 161 | { 162 | $username = 'engagor'; 163 | $boardName = ''; 164 | $this->api->followBoard($username, $boardName); 165 | } 166 | 167 | /** 168 | * @expectedException InvalidArgumentException 169 | */ 170 | public function test_it_throws_an_exception_when_trying_to_unfollow_a_board_with_empty_username() 171 | { 172 | $username = ''; 173 | $boardName = 'engagor-in-the-news'; 174 | $this->api->unfollowBoard($username, $boardName); 175 | } 176 | 177 | /** 178 | * @expectedException InvalidArgumentException 179 | */ 180 | public function test_it_throws_an_exception_when_trying_to_unfollow_a_board_with_empty_board_name() 181 | { 182 | $username = 'engagor'; 183 | $boardName = ''; 184 | $this->api->unfollowBoard($username, $boardName); 185 | } 186 | 187 | /** 188 | * @dataProvider imageProvider 189 | */ 190 | public function test_it_creates_a_pin(Image $image, $note) 191 | { 192 | $response = $this->api->createPin( 193 | $this->boardId, 194 | $note, 195 | $image 196 | ); 197 | 198 | $this->assertInstanceOf('Pinterest\Http\Response', $response); 199 | $this->assertTrue($response->ok()); 200 | $this->assertInternalType('int', $response->getRateLimit()); 201 | $this->assertInternalType('string', $response->getHeader('X-RateLimit-Limit')); 202 | $this->assertInternalType('string', $response->getHeader('x-ratelimit-limit')); 203 | $this->assertInternalType('int', $response->getRemainingRequests()); 204 | $this->assertInternalType('string', $response->getHeader('X-RateLimit-Remaining')); 205 | $this->assertInternalType('string', $response->getHeader('x-ratelimit-remaining')); 206 | 207 | $this->api->deletePin($response->result()->id); 208 | } 209 | 210 | public function imageProvider() 211 | { 212 | $imageFixture = __DIR__.'/fixtures/cat.jpg'; 213 | 214 | return array( 215 | array(Image::url('http://via.placeholder.com/350x150'), 'Test pin url'), 216 | array(Image::file($imageFixture), 'Test pin file'), 217 | array(Image::base64(base64_encode(file_get_contents($imageFixture))), 'Test pin base64'), 218 | ); 219 | } 220 | 221 | public function test_it_creates_updates_and_deletes_a_pin() 222 | { 223 | $data = $this->imageProvider(); 224 | $createResponse = $this->api->createPin( 225 | $this->boardId, 226 | 'A note!', 227 | $data[0][0] 228 | ); 229 | $this->assertInstanceOf('Pinterest\Http\Response', $createResponse); 230 | $this->assertTrue($createResponse->ok()); 231 | 232 | /** @var Pin $pin */ 233 | $pin = $createResponse->result(); 234 | $response = $this->api->getPin($pin->id); 235 | $this->assertInstanceOf('Pinterest\Http\Response', $response); 236 | $this->assertTrue($response->ok()); 237 | $this->assertEquals($pin, $response->result()); 238 | 239 | $pin->note = 'A new note'; 240 | $pin->link = 'https://google.com'; 241 | $updateResponse = $this->api->updatePin($pin); 242 | $this->assertInstanceOf('Pinterest\Http\Response', $updateResponse); 243 | $this->assertTrue($updateResponse->ok()); 244 | 245 | $pinId = $createResponse->result()->id; 246 | $response = $this->api->deletePin($pinId); 247 | $this->assertInstanceOf('Pinterest\Http\Response', $response); 248 | $this->assertTrue($response->ok()); 249 | } 250 | 251 | /** 252 | * @expectedException InvalidArgumentException 253 | * @expectedExceptionMessage The pin id is required. 254 | */ 255 | public function test_it_throws_an_exception_when_trying_to_update_a_pin_without_id() 256 | { 257 | $pin = new Pin; 258 | $this->api->updatePin($pin); 259 | } 260 | 261 | /** 262 | * @expectedException InvalidArgumentException 263 | * @expectedExceptionMessage You're not changing any values. You can update a pin's note, link and/or board. 264 | */ 265 | public function test_it_throws_an_exception_if_only_id_is_set() 266 | { 267 | $pin = new Pin; 268 | $pin->id = '1'; 269 | $this->api->updatePin($pin); 270 | } 271 | 272 | public function test_it_creates_and_updates_and_deletes_a_board() 273 | { 274 | $response = $this->api->createBoard('Unit test', 'A simple description'); 275 | $this->assertInstanceOf('Pinterest\Http\Response', $response); 276 | $this->assertInstanceOf('Pinterest\Objects\Board', $response->result()); 277 | $this->assertTrue($response->ok()); 278 | 279 | $board = $response->result(); 280 | $boardId = $board->id; 281 | $board->name = 'Unit test update'; 282 | 283 | $response = $this->api->updateBoard($board); 284 | $this->assertInstanceOf('Pinterest\Http\Response', $response); 285 | $this->assertInstanceOf('Pinterest\Objects\Board', $response->result()); 286 | $this->assertTrue($response->ok()); 287 | 288 | $response = $this->api->deleteBoard($boardId); 289 | $this->assertInstanceOf('Pinterest\Http\Response', $response); 290 | $this->assertTrue($response->ok()); 291 | } 292 | 293 | /** 294 | * @expectedException InvalidArgumentException 295 | */ 296 | public function test_it_cannot_get_more_items_for_an_empty_list() 297 | { 298 | $pagedList = new PagedList(array()); 299 | $this->api->getNextItems($pagedList); 300 | } 301 | 302 | public function test_it_returns_the_pins_of_a_board() 303 | { 304 | $this->assertMultiplePins($this->api->getBoardPins($this->boardId)); 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Pinterest PHP](art/pinterest-php.jpg) 2 | 3 |

4 | Scrutinizer Code Quality 5 | Code Coverage 6 | Packagist 7 | Packagist 8 |

9 | 10 | ## Install 11 | 12 | Via [Composer](https://getcomposer.org/) 13 | 14 | ```bash 15 | $ composer require hansott/pinterest-php 16 | ``` 17 | 18 | ## Donate 19 | 20 | If you like this package, please consider buying me a coffee. Thank you for your support! 🙇‍♂️ 21 | 22 | Buy Me A Coffee 23 | 24 | ## Usage 25 | 26 | ### Authentication 27 | 28 | To use the API, you need an access token from Pinterest. [Create a new Pinterest application](https://developers.pinterest.com/apps/) if you haven't already. You then get a client ID and a client secret, specific for that application. 29 | 30 | Back in your PHP application, create a `Pinterest\Http\ClientInterface` instance (the default is `Pinterest\Http\BuzzClient`) and use it to create an `Pinterest\Authentication` instance: 31 | 32 | ```php 33 | $client = new Pinterest\Http\BuzzClient(); 34 | $auth = new Pinterest\Authentication($client, $clientId, $clientSecret); 35 | ``` 36 | 37 | Replace the `$clientId` and `$clientSecret` variables with the data of [your Pinterest application](https://developers.pinterest.com/apps/). 38 | 39 | You can now let your user authenticate with your application be redirecting them to the URL obtained by a call to `$auth->getAuthenticationUrl()`, like this: 40 | 41 | ```php 42 | use Pinterest\App\Scope; 43 | 44 | $url = $auth->getAuthenticationUrl( 45 | 'https://your/redirect/url/here', 46 | array( 47 | Scope::READ_PUBLIC, 48 | Scope::WRITE_PUBLIC, 49 | Scope::READ_RELATIONSHIPS, 50 | Scope::WRITE_RELATIONSHIPS, 51 | ), 52 | 'random-string' 53 | ); 54 | 55 | header('Location: ' . $url); 56 | exit; 57 | ``` 58 | 59 | - The redirect URL is the URL to the page where pinterest will send us the authentication code for the user registering with your application. This URL needs to be accessible over https, and it has to be filled into to form of your Pinterst application (in the Pinterest backend). 60 | - The second parameter is an array of permissions your app needs on the user's account. There needs to be at least one here. 61 | - The validation state is a random code that you generate for the user registering, and persist (in SESSION for instance). Pinterest will send it back to us for further reference. 62 | 63 | When your application user agrees to let your app take control of their Pinterest account via the API, Pinterest will redirect them to the URL you provided as redirect URL, with some added GET parameters. The most important being "code", which we'll trade for an OAuth access token in the next step. They'll also send the validation state back to us as a GET parameter so we can check if we expected this call. 64 | 65 | The last step in the process is trading that code for an access token: 66 | 67 | ```php 68 | $code = $_GET['code']; 69 | $token = $auth->requestAccessToken($code); 70 | ``` 71 | 72 | You should persist that token safely at this point. You can use it from now on to connect to the Pinterest API from your application, on behalf of the user. 73 | 74 | Initialize the `Pinterest\Api` class: 75 | 76 | ```php 77 | $auth = Pinterest\Authentication::onlyAccessToken($client, $token); 78 | $api = new Pinterest\Api($auth); 79 | ``` 80 | 81 | Using the `Pinterest\Api` instance in `$api`, you can now make authenticated API requests to Pinterest's API on behalf of the user. 82 | 83 | ### Get the authenticated user 84 | 85 | ```php 86 | $response = $api->getCurrentUser(); 87 | 88 | if (!$response->ok()) { 89 | die($response->getError()); 90 | } 91 | 92 | $user = $response->result(); // $user instanceof Objects\User 93 | ``` 94 | 95 | ### Get a user 96 | 97 | ```php 98 | // Get user by username 99 | $response = $api->getUser('otthans'); 100 | 101 | // Get user by user id 102 | $response = $api->getUser('314196648911734959'); 103 | 104 | if (!$response->ok()) { 105 | die($response->getError()); 106 | } 107 | 108 | $user = $response->result(); // $user instanceof Objects\User 109 | ``` 110 | 111 | ### Get a board 112 | 113 | ```php 114 | $response = $api->getBoard('314196580192594085'); 115 | 116 | if (!$response->ok()) { 117 | die($response->getError()); 118 | } 119 | 120 | $board = $response->result(); // $board instanceof Objects\Board 121 | ``` 122 | 123 | ### Update a board 124 | 125 | ```php 126 | // First, get the board using getBoard() 127 | $response = $api->getBoard('314196580192594085'); 128 | 129 | if (!$response->ok()) { 130 | die($response->getError()); 131 | } 132 | 133 | $board = $response->result(); // $board instanceof Objects\Board 134 | 135 | // Or create a new board without getBoard() 136 | 137 | $board = new Board; 138 | $board->id = 'the-board-id'; 139 | 140 | // Then, update the fields you want to change 141 | 142 | $board->name = 'New board name'; 143 | $board->description = 'New board description'; 144 | $response = $api->updateBoard($board); 145 | 146 | if (!$response->ok()) { 147 | die($response->getError()); 148 | } 149 | 150 | $updatedBoard = $response->result(); // $updatedBoard instanceof Objects\Board 151 | ``` 152 | 153 | ### Get the boards of the authenticated user 154 | 155 | ```php 156 | $response = $api->getUserBoards(); 157 | 158 | if (!$response->ok()) { 159 | die($response->getError()); 160 | } 161 | 162 | $pagedList = $response->result(); // $pagedList instanceof Objects\PagedList 163 | $boards = $pagedList->items(); // array of Objects\Board objects 164 | ``` 165 | 166 | ### Get the pins of the authenticated user 167 | 168 | ```php 169 | $response = $api->getUserPins(); 170 | 171 | if (!$response->ok()) { 172 | die($response->getError()); 173 | } 174 | 175 | $pagedList = $response->result(); // $pagedList instanceof Objects\PagedList 176 | $pins = $pagedList->items(); // array of Objects\Pin objects 177 | ``` 178 | 179 | ### Get the pins of a board 180 | 181 | ```php 182 | $response = $api->getBoardPins($boardId); 183 | 184 | if (!$response->ok()) { 185 | die($response->getError()); 186 | } 187 | 188 | $pagedList = $response->result(); // $pagedList instanceof Objects\PagedList 189 | $pins = $pagedList->items(); // array of Objects\Pin objects 190 | ``` 191 | 192 | See [Get the next items of a paged list](#get-the-next-items-of-a-paged-list) 193 | 194 | ### Get the followers of the authenticated user 195 | 196 | ```php 197 | $response = $api->getUserFollowers(); 198 | 199 | if (!$response->ok()) { 200 | die($response->getError()); 201 | } 202 | 203 | $pagedList = $response->result(); // $boards instanceof Objects\PagedList 204 | $users = $pagedList->items(); // array of Objects\User objects 205 | ``` 206 | 207 | See [Get the next items of a paged list](#get-the-next-items-of-a-paged-list) 208 | 209 | ### Get the boards that the authenticated user follows 210 | 211 | ```php 212 | $response = $api->getUserFollowingBoards(); 213 | 214 | if (!$response->ok()) { 215 | die($response->getError()); 216 | } 217 | 218 | $pagedList = $response->result(); // $boards instanceof Objects\PagedList 219 | $boards = $pagedList->items(); // array of Objects\Board objects 220 | ``` 221 | 222 | See [Get the next items of a paged list](#get-the-next-items-of-a-paged-list) 223 | 224 | ### Get the users that the authenticated user follows 225 | 226 | ```php 227 | $response = $api->getUserFollowing(); 228 | 229 | if (!$response->ok()) { 230 | die($response->getError()); 231 | } 232 | 233 | $pagedList = $response->result(); // $boards instanceof Objects\PagedList 234 | $users = $pagedList->items(); // array of Objects\User objects 235 | ``` 236 | 237 | See [Get the next items of a paged list](#get-the-next-items-of-a-paged-list) 238 | 239 | ### Get the interests that the authenticated user follows 240 | 241 | Example: [Modern architecture](https://www.pinterest.com/explore/901179409185) 242 | 243 | ```php 244 | $response = $api->getUserInterests(); 245 | 246 | if (!$response->ok()) { 247 | die($response->getError()); 248 | } 249 | 250 | $pagedList = $response->result(); // $boards instanceof Objects\PagedList 251 | $boards = $pagedList->items(); // array of Objects\Board objects 252 | ``` 253 | 254 | See [Get the next items of a paged list](#get-the-next-items-of-a-paged-list) 255 | 256 | ### Follow a user 257 | 258 | ```php 259 | $response = $api->followUser('otthans'); 260 | 261 | if (!$response->ok()) { 262 | die($response->getError()); 263 | } 264 | ``` 265 | 266 | ### Unfollow a user 267 | 268 | ```php 269 | $response = $api->unfollowUser('otthans'); // username or user ID 270 | 271 | if (!$response->ok()) { 272 | die($response->getError()); 273 | } 274 | ``` 275 | 276 | ### Follow a board 277 | 278 | ```php 279 | $response = $api->followBoard('teslamotors', 'model-x'); 280 | 281 | if (!$response->ok()) { 282 | die($response->getError()); 283 | } 284 | ``` 285 | 286 | ### Unfollow a board 287 | 288 | ```php 289 | $response = $api->unfollowBoard('teslamotors', 'model-x'); 290 | 291 | if (!$response->ok()) { 292 | die($response->getError()); 293 | } 294 | ``` 295 | 296 | ### Create a board 297 | 298 | ```php 299 | $name = 'My new board'; 300 | $optionalDescription = 'The description of the board'; 301 | $response = $api->createBoard($name, $optionalDescription); 302 | 303 | if (!$response->ok()) { 304 | die($response->getError()); 305 | } 306 | 307 | $board = $response->result(); // $board instanceof Objects\Board 308 | ``` 309 | 310 | ### Delete a board 311 | 312 | ```php 313 | $boardId = '314196580192594085'; 314 | $response = $api->createBoard($boardId); 315 | 316 | if (!$response->ok()) { 317 | die($response->getError()); 318 | } 319 | ``` 320 | 321 | ### Create a pin 322 | 323 | ```php 324 | $board = '/'; 325 | $note = 'This is an amazing pin!'; 326 | $optionalLink = 'http://hansott.github.io/'; 327 | 328 | // Load an image from a url. 329 | $image = Pinterest\Image::url('http://lorempixel.com/g/400/200/cats/'); 330 | 331 | // Load an image from a file. 332 | $pathToFile = 'myfolder/myimage.png'; 333 | $image = Pinterest\Image::file($pathToFile); 334 | 335 | // Load a base64 encoded image. 336 | $pathToFile = 'myfolder/myimage.png'; 337 | $data = file_get_contents($pathToFile); 338 | $base64 = base64_encode($data); 339 | $image = Pinterest\Image::base64($base64); 340 | 341 | $response = $api->createPin($board, $note, $image, $optionalLink); 342 | 343 | if (!$response->ok()) { 344 | die($response->getError()); 345 | } 346 | 347 | $pin = $response->result(); // $pin instanceof Objects\Pin 348 | ``` 349 | 350 | ### Get a pin 351 | 352 | ```php 353 | $pinId = 'the-pin-id'; 354 | $response = $api->getPin($pinId); 355 | 356 | if (!$response->ok()) { 357 | die($response->getError()); 358 | } 359 | 360 | $pin = $response->result(); // $pin instanceof Objects\Pin 361 | ``` 362 | 363 | ### Update a pin 364 | 365 | ```php 366 | // First, get the pin using getPin() 367 | 368 | $pinId = 'the-pin-id'; 369 | $response = $api->getPin($pinId); 370 | 371 | if (!$response->ok()) { 372 | die($response->getError()); 373 | } 374 | 375 | $pin = $response->result(); 376 | 377 | // Or create a new Pin without getPin() 378 | 379 | $pin = new Pin; 380 | $pin->id = 'the-pin-id'; 381 | 382 | // Then, update the fields you want to change 383 | 384 | // Update note 385 | $pin->note = 'a new note'; 386 | 387 | // Update link 388 | $pin->link = 'https://google.com'; 389 | 390 | $response = $api->updatePin($pin); 391 | 392 | if (!$response->ok()) { 393 | die($response->getError()); 394 | } 395 | 396 | $updatedPin = $response->result(); 397 | ``` 398 | 399 | ### Delete a pin 400 | 401 | ```php 402 | $pinId = 'the-pin-id'; 403 | $response = $api->deletePin($pinId); 404 | 405 | if (!$response->ok()) { 406 | die($response->getError()); 407 | } 408 | ``` 409 | 410 | ### Get the next items of a paged list 411 | 412 | ```php 413 | $hasMoreItems = $pagedList->hasNext(); 414 | 415 | if (!$hasMoreItems) { 416 | return; 417 | } 418 | 419 | $response = $api->getNextItems($pagedList); 420 | 421 | if (!$response->ok()) { 422 | die($response->getError()); 423 | } 424 | 425 | $nextPagedList = $response->result(); 426 | ``` 427 | 428 | ## Contributing 429 | 430 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details. 431 | 432 | ## Security 433 | 434 | If you discover any security related issues, please email **hansott at hotmail be** instead of using the issue tracker. 435 | 436 | ## Credits 437 | 438 | - [Hans Ott](https://github.com/hansott) 439 | - [Toon Daelman](https://github.com/turanct) 440 | 441 | ## License 442 | 443 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 444 | -------------------------------------------------------------------------------- /tests/Pinterest/responses/get_v1_me_pins_0b8461cd5574fc97b8ab9b836a092dfe.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "0": { 5 | "attribution": null, 6 | "creator": { 7 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 8 | "first_name": "H", 9 | "last_name": "O", 10 | "id": "314196648911734959" 11 | }, 12 | "url": "https:\/\/www.pinterest.com\/pin\/314196511498047178\/", 13 | "media": { 14 | "type": "image" 15 | }, 16 | "created_at": "2018-03-22T15:22:35", 17 | "note": "The most beautiful train journey in Sri Lanka - The #Kandy to Ella train", 18 | "color": "#5e6127", 19 | "link": "https:\/\/www.pinterest.com\/r\/pin\/314196511498047178\/4862229257031532989\/fe5157e41a22fbc350bfde8d630ad39014e09083b4d28409a031aede241c9f24", 20 | "board": { 21 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 22 | "id": "314196580192658592", 23 | "name": "Test" 24 | }, 25 | "image": { 26 | "original": { 27 | "url": "https:\/\/i.pinimg.com\/originals\/f4\/59\/b2\/f459b2b23b70b37381add3fe1122d5a3.jpg", 28 | "width": 735, 29 | "height": 1400 30 | } 31 | }, 32 | "counts": { 33 | "saves": 0, 34 | "comments": 0 35 | }, 36 | "id": "314196511498047178", 37 | "metadata": { 38 | "article": { 39 | "published_at": "2015-08-14T00:00:00", 40 | "description": "The Kandy to Ella train journey is often described as one of the worlds most scenic. Slow down and enjoy this slow ride through misty tea plantations.", 41 | "name": "Kandy to Ella Train: Worlds most scenic", 42 | "authors": {} 43 | }, 44 | "link": { 45 | "locale": "en_GB", 46 | "title": "Kandy to Ella Train: Worlds most scenic? | Dan Flying Solo", 47 | "site_name": "Dan Flying Solo", 48 | "description": "The Kandy to Ella train journey is often described as one of the worlds most scenic. Slow down and enjoy this slow ride through misty tea plantations.", 49 | "favicon": "https:\/\/i.pinimg.com\/favicons\/36f3db342dc063bb7677f2da9f953705ccbab32a910afaf237ab7c6d.png?120523c00e489a3b59feaa6c49c32ee2" 50 | } 51 | } 52 | }, 53 | "1": { 54 | "attribution": null, 55 | "creator": { 56 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 57 | "first_name": "H", 58 | "last_name": "O", 59 | "id": "314196648911734959" 60 | }, 61 | "url": "https:\/\/www.pinterest.com\/pin\/314196511496629623\/", 62 | "media": { 63 | "type": "image" 64 | }, 65 | "created_at": "2017-12-20T08:57:39", 66 | "note": "Test pin url", 67 | "color": null, 68 | "link": "", 69 | "board": { 70 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 71 | "id": "314196580192658592", 72 | "name": "Test" 73 | }, 74 | "image": { 75 | "original": { 76 | "url": "https:\/\/i.pinimg.com\/originals\/e4\/84\/e3\/e484e3fa1959913947378735eddd969f.png", 77 | "width": 350, 78 | "height": 150 79 | } 80 | }, 81 | "counts": { 82 | "saves": 0, 83 | "comments": 0 84 | }, 85 | "id": "314196511496629623", 86 | "metadata": {} 87 | }, 88 | "2": { 89 | "attribution": null, 90 | "creator": { 91 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 92 | "first_name": "H", 93 | "last_name": "O", 94 | "id": "314196648911734959" 95 | }, 96 | "url": "https:\/\/www.pinterest.com\/pin\/314196511484779371\/", 97 | "media": { 98 | "type": "image" 99 | }, 100 | "created_at": "2016-03-13T14:59:56", 101 | "note": "Command Line Cheat Sheet for Mac and Linux. I know these, but always good to have on hand!", 102 | "color": "#e7e8ea", 103 | "link": "https:\/\/www.pinterest.com\/r\/pin\/314196511484779371\/4862229257031532989\/6e93ee8e11e329b93970722a93d05401cdbe0320397d08515d27a7b65a4b70fd", 104 | "board": { 105 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 106 | "id": "314196580192658592", 107 | "name": "Test" 108 | }, 109 | "image": { 110 | "original": { 111 | "url": "https:\/\/i.pinimg.com\/originals\/cc\/ba\/66\/ccba6681ac9c3e6ed93034a7a4693394.jpg", 112 | "width": 818, 113 | "height": 3321 114 | } 115 | }, 116 | "counts": { 117 | "saves": 4, 118 | "comments": 0 119 | }, 120 | "id": "314196511484779371", 121 | "metadata": { 122 | "article": { 123 | "published_at": "2015-05-27T00:00:00", 124 | "description": "Here is a helpful Mac terminal commands cheat sheet with frequently used commands. Use this cheat sheet as a unix command reference guide or to memorize.", 125 | "name": "A Quick Cheat Sheet to the Unix\/Mac Terminal", 126 | "authors": { 127 | "0": { 128 | "name": "Laurence Bradford" 129 | } 130 | } 131 | }, 132 | "link": { 133 | "locale": "en", 134 | "title": "A Quick Cheat Sheet to the Unix\/Mac Terminal", 135 | "site_name": "Learn to Code With Me", 136 | "description": "Here is a helpful Mac terminal commands cheat sheet with frequently used commands. Use this cheat sheet as a unix command reference guide or to memorize.", 137 | "favicon": "https:\/\/i.pinimg.com\/favicons\/25d7452c726285e347a473b812084705ab4c924cb1617b869779000a.png?a32f814573f70a7b0624d3b629507fda" 138 | } 139 | } 140 | }, 141 | "3": { 142 | "attribution": null, 143 | "creator": { 144 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 145 | "first_name": "H", 146 | "last_name": "O", 147 | "id": "314196648911734959" 148 | }, 149 | "url": "https:\/\/www.pinterest.com\/pin\/314196511484779368\/", 150 | "media": { 151 | "type": "image" 152 | }, 153 | "created_at": "2016-03-13T14:59:45", 154 | "note": "Which Coding Language Should You Learn?", 155 | "color": "#ae92ad", 156 | "link": "https:\/\/www.pinterest.com\/r\/pin\/314196511484779368\/4862229257031532989\/2229a482f957660d9e564f9ee196ff8c6826fcf50e1f2d0f0dbca689f809541d", 157 | "board": { 158 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 159 | "id": "314196580192658592", 160 | "name": "Test" 161 | }, 162 | "image": { 163 | "original": { 164 | "url": "https:\/\/i.pinimg.com\/originals\/3e\/6a\/b4\/3e6ab4a62a7facb3170911e51484ddba.jpg", 165 | "width": 800, 166 | "height": 9424 167 | } 168 | }, 169 | "counts": { 170 | "saves": 0, 171 | "comments": 0 172 | }, 173 | "id": "314196511484779368", 174 | "metadata": { 175 | "article": { 176 | "published_at": "2014-09-27T00:00:00", 177 | "description": "Learning to code is now one of the best ways to get yourself a job as it can cost absolutely zero to learn and can be learned very quickly if you really get", 178 | "name": "Which Coding Language Should You Learn?", 179 | "authors": { 180 | "0": { 181 | "name": "Oliur" 182 | } 183 | } 184 | }, 185 | "link": { 186 | "locale": "en", 187 | "title": "Which Coding Language Should You Learn?", 188 | "site_name": "UltraLinx", 189 | "description": "Learning to code is now one of the best ways to get yourself a job as it can cost absolutely zero to learn and can be learned very quickly if you really get", 190 | "favicon": "https:\/\/i.pinimg.com\/favicons\/1c6096a37e41f1582c4ff942baa93686b01d768349bcfd71e2f2458c.ico?14b1fc4ff5a2251c16f18da1fbe9c1ba" 191 | } 192 | } 193 | }, 194 | "4": { 195 | "attribution": null, 196 | "creator": { 197 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 198 | "first_name": "H", 199 | "last_name": "O", 200 | "id": "314196648911734959" 201 | }, 202 | "url": "https:\/\/www.pinterest.com\/pin\/314196511484779365\/", 203 | "media": { 204 | "type": "image" 205 | }, 206 | "created_at": "2016-03-13T14:59:34", 207 | "note": "How to Teach Yourself Code Infographic - http:\/\/elearninginfographics.com\/teach-yourself-code\/", 208 | "color": "#0e163b", 209 | "link": "https:\/\/www.pinterest.com\/r\/pin\/314196511484779365\/4862229257031532989\/ae91c34daac7f277ea9d5f90cf1f653d52988351b26998710ce798b8cde35200", 210 | "board": { 211 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 212 | "id": "314196580192658592", 213 | "name": "Test" 214 | }, 215 | "image": { 216 | "original": { 217 | "url": "https:\/\/i.pinimg.com\/originals\/71\/f5\/64\/71f56468c6aaef1da0e4580d4e687914.jpg", 218 | "width": 800, 219 | "height": 3630 220 | } 221 | }, 222 | "counts": { 223 | "saves": 0, 224 | "comments": 0 225 | }, 226 | "id": "314196511484779365", 227 | "metadata": { 228 | "article": { 229 | "published_at": "2016-01-03T00:00:00", 230 | "description": "The How to Teach Yourself Code Infographic presents sources to help you decide what programming language is best for you and how you can learn from others.", 231 | "name": "How to Teach Yourself Code Infographic", 232 | "authors": {} 233 | }, 234 | "link": { 235 | "locale": "en", 236 | "title": "How to Teach Yourself Code Infographic - e-Learning Infographics", 237 | "site_name": "e-Learning Infographics", 238 | "description": "The\u00a0How to Teach Yourself Code Infographic\u00a0presents sources to help you decide what programming language is best for you and how you can learn from others.", 239 | "favicon": "https:\/\/i.pinimg.com\/favicons\/7c093cc0216286e8e7c1104d8d80727421e6b289986fd09629abee2f.ico?75cdee3956766caafbd7beef83363500" 240 | } 241 | } 242 | } 243 | }, 244 | "page": { 245 | "cursor": null, 246 | "next": null 247 | } 248 | }, 249 | "statusCode": 200, 250 | "headers": { 251 | "Access-Control-Allow-Origin": "*", 252 | "Age": "0", 253 | "Cache-Control": "private", 254 | "Content-Type": "application\/json", 255 | "Pinterest-Version": "4f9f34e", 256 | "X-Content-Type-Options": "nosniff", 257 | "X-Pinterest-RID": "604783354391", 258 | "X-Ratelimit-Limit": "10", 259 | "X-Ratelimit-Remaining": "5", 260 | "Content-Length": "7062", 261 | "Date": "Sun, 22 Apr 2018 12:04:22 GMT", 262 | "Connection": "keep-alive", 263 | "Pinterest-Generated-By:": "" 264 | } 265 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/get_v1_boards_314196580192658592_pins_0b8461cd5574fc97b8ab9b836a092dfe.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "0": { 5 | "attribution": null, 6 | "creator": { 7 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 8 | "first_name": "H", 9 | "last_name": "O", 10 | "id": "314196648911734959" 11 | }, 12 | "url": "https:\/\/www.pinterest.com\/pin\/314196511498047178\/", 13 | "media": { 14 | "type": "image" 15 | }, 16 | "created_at": "2018-03-22T15:22:35", 17 | "note": "The most beautiful train journey in Sri Lanka - The #Kandy to Ella train", 18 | "color": "#5e6127", 19 | "link": "https:\/\/www.pinterest.com\/r\/pin\/314196511498047178\/4862229257031532989\/fe5157e41a22fbc350bfde8d630ad39014e09083b4d28409a031aede241c9f24", 20 | "board": { 21 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 22 | "id": "314196580192658592", 23 | "name": "Test" 24 | }, 25 | "image": { 26 | "original": { 27 | "url": "https:\/\/i.pinimg.com\/originals\/f4\/59\/b2\/f459b2b23b70b37381add3fe1122d5a3.jpg", 28 | "width": 735, 29 | "height": 1400 30 | } 31 | }, 32 | "counts": { 33 | "saves": 0, 34 | "comments": 0 35 | }, 36 | "id": "314196511498047178", 37 | "metadata": { 38 | "article": { 39 | "published_at": "2015-08-14T00:00:00", 40 | "description": "The Kandy to Ella train journey is often described as one of the worlds most scenic. Slow down and enjoy this slow ride through misty tea plantations.", 41 | "name": "Kandy to Ella Train: Worlds most scenic", 42 | "authors": {} 43 | }, 44 | "link": { 45 | "locale": "en_GB", 46 | "title": "Kandy to Ella Train: Worlds most scenic? | Dan Flying Solo", 47 | "site_name": "Dan Flying Solo", 48 | "description": "The Kandy to Ella train journey is often described as one of the worlds most scenic. Slow down and enjoy this slow ride through misty tea plantations.", 49 | "favicon": "https:\/\/i.pinimg.com\/favicons\/36f3db342dc063bb7677f2da9f953705ccbab32a910afaf237ab7c6d.png?120523c00e489a3b59feaa6c49c32ee2" 50 | } 51 | } 52 | }, 53 | "1": { 54 | "attribution": null, 55 | "creator": { 56 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 57 | "first_name": "H", 58 | "last_name": "O", 59 | "id": "314196648911734959" 60 | }, 61 | "url": "https:\/\/www.pinterest.com\/pin\/314196511496629623\/", 62 | "media": { 63 | "type": "image" 64 | }, 65 | "created_at": "2017-12-20T08:57:39", 66 | "note": "Test pin url", 67 | "color": null, 68 | "link": "", 69 | "board": { 70 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 71 | "id": "314196580192658592", 72 | "name": "Test" 73 | }, 74 | "image": { 75 | "original": { 76 | "url": "https:\/\/i.pinimg.com\/originals\/e4\/84\/e3\/e484e3fa1959913947378735eddd969f.png", 77 | "width": 350, 78 | "height": 150 79 | } 80 | }, 81 | "counts": { 82 | "saves": 0, 83 | "comments": 0 84 | }, 85 | "id": "314196511496629623", 86 | "metadata": {} 87 | }, 88 | "2": { 89 | "attribution": null, 90 | "creator": { 91 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 92 | "first_name": "H", 93 | "last_name": "O", 94 | "id": "314196648911734959" 95 | }, 96 | "url": "https:\/\/www.pinterest.com\/pin\/314196511484779371\/", 97 | "media": { 98 | "type": "image" 99 | }, 100 | "created_at": "2016-03-13T14:59:56", 101 | "note": "Command Line Cheat Sheet for Mac and Linux. I know these, but always good to have on hand!", 102 | "color": "#e7e8ea", 103 | "link": "https:\/\/www.pinterest.com\/r\/pin\/314196511484779371\/4862229257031532989\/6e93ee8e11e329b93970722a93d05401cdbe0320397d08515d27a7b65a4b70fd", 104 | "board": { 105 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 106 | "id": "314196580192658592", 107 | "name": "Test" 108 | }, 109 | "image": { 110 | "original": { 111 | "url": "https:\/\/i.pinimg.com\/originals\/cc\/ba\/66\/ccba6681ac9c3e6ed93034a7a4693394.jpg", 112 | "width": 818, 113 | "height": 3321 114 | } 115 | }, 116 | "counts": { 117 | "saves": 4, 118 | "comments": 0 119 | }, 120 | "id": "314196511484779371", 121 | "metadata": { 122 | "article": { 123 | "published_at": "2015-05-27T00:00:00", 124 | "description": "Here is a helpful Mac terminal commands cheat sheet with frequently used commands. Use this cheat sheet as a unix command reference guide or to memorize.", 125 | "name": "A Quick Cheat Sheet to the Unix\/Mac Terminal", 126 | "authors": { 127 | "0": { 128 | "name": "Laurence Bradford" 129 | } 130 | } 131 | }, 132 | "link": { 133 | "locale": "en", 134 | "title": "A Quick Cheat Sheet to the Unix\/Mac Terminal", 135 | "site_name": "Learn to Code With Me", 136 | "description": "Here is a helpful Mac terminal commands cheat sheet with frequently used commands. Use this cheat sheet as a unix command reference guide or to memorize.", 137 | "favicon": "https:\/\/i.pinimg.com\/favicons\/25d7452c726285e347a473b812084705ab4c924cb1617b869779000a.png?a32f814573f70a7b0624d3b629507fda" 138 | } 139 | } 140 | }, 141 | "3": { 142 | "attribution": null, 143 | "creator": { 144 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 145 | "first_name": "H", 146 | "last_name": "O", 147 | "id": "314196648911734959" 148 | }, 149 | "url": "https:\/\/www.pinterest.com\/pin\/314196511484779368\/", 150 | "media": { 151 | "type": "image" 152 | }, 153 | "created_at": "2016-03-13T14:59:45", 154 | "note": "Which Coding Language Should You Learn?", 155 | "color": "#ae92ad", 156 | "link": "https:\/\/www.pinterest.com\/r\/pin\/314196511484779368\/4862229257031532989\/2229a482f957660d9e564f9ee196ff8c6826fcf50e1f2d0f0dbca689f809541d", 157 | "board": { 158 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 159 | "id": "314196580192658592", 160 | "name": "Test" 161 | }, 162 | "image": { 163 | "original": { 164 | "url": "https:\/\/i.pinimg.com\/originals\/3e\/6a\/b4\/3e6ab4a62a7facb3170911e51484ddba.jpg", 165 | "width": 800, 166 | "height": 9424 167 | } 168 | }, 169 | "counts": { 170 | "saves": 0, 171 | "comments": 0 172 | }, 173 | "id": "314196511484779368", 174 | "metadata": { 175 | "article": { 176 | "published_at": "2014-09-27T00:00:00", 177 | "description": "Learning to code is now one of the best ways to get yourself a job as it can cost absolutely zero to learn and can be learned very quickly if you really get", 178 | "name": "Which Coding Language Should You Learn?", 179 | "authors": { 180 | "0": { 181 | "name": "Oliur" 182 | } 183 | } 184 | }, 185 | "link": { 186 | "locale": "en", 187 | "title": "Which Coding Language Should You Learn?", 188 | "site_name": "UltraLinx", 189 | "description": "Learning to code is now one of the best ways to get yourself a job as it can cost absolutely zero to learn and can be learned very quickly if you really get", 190 | "favicon": "https:\/\/i.pinimg.com\/favicons\/1c6096a37e41f1582c4ff942baa93686b01d768349bcfd71e2f2458c.ico?14b1fc4ff5a2251c16f18da1fbe9c1ba" 191 | } 192 | } 193 | }, 194 | "4": { 195 | "attribution": null, 196 | "creator": { 197 | "url": "https:\/\/www.pinterest.com\/hanso1\/", 198 | "first_name": "H", 199 | "last_name": "O", 200 | "id": "314196648911734959" 201 | }, 202 | "url": "https:\/\/www.pinterest.com\/pin\/314196511484779365\/", 203 | "media": { 204 | "type": "image" 205 | }, 206 | "created_at": "2016-03-13T14:59:34", 207 | "note": "How to Teach Yourself Code Infographic - http:\/\/elearninginfographics.com\/teach-yourself-code\/", 208 | "color": "#0e163b", 209 | "link": "https:\/\/www.pinterest.com\/r\/pin\/314196511484779365\/4862229257031532989\/ae91c34daac7f277ea9d5f90cf1f653d52988351b26998710ce798b8cde35200", 210 | "board": { 211 | "url": "https:\/\/www.pinterest.com\/hanso1\/test\/", 212 | "id": "314196580192658592", 213 | "name": "Test" 214 | }, 215 | "image": { 216 | "original": { 217 | "url": "https:\/\/i.pinimg.com\/originals\/71\/f5\/64\/71f56468c6aaef1da0e4580d4e687914.jpg", 218 | "width": 800, 219 | "height": 3630 220 | } 221 | }, 222 | "counts": { 223 | "saves": 0, 224 | "comments": 0 225 | }, 226 | "id": "314196511484779365", 227 | "metadata": { 228 | "article": { 229 | "published_at": "2016-01-03T00:00:00", 230 | "description": "The How to Teach Yourself Code Infographic presents sources to help you decide what programming language is best for you and how you can learn from others.", 231 | "name": "How to Teach Yourself Code Infographic", 232 | "authors": {} 233 | }, 234 | "link": { 235 | "locale": "en", 236 | "title": "How to Teach Yourself Code Infographic - e-Learning Infographics", 237 | "site_name": "e-Learning Infographics", 238 | "description": "The\u00a0How to Teach Yourself Code Infographic\u00a0presents sources to help you decide what programming language is best for you and how you can learn from others.", 239 | "favicon": "https:\/\/i.pinimg.com\/favicons\/7c093cc0216286e8e7c1104d8d80727421e6b289986fd09629abee2f.ico?75cdee3956766caafbd7beef83363500" 240 | } 241 | } 242 | } 243 | }, 244 | "page": { 245 | "cursor": null, 246 | "next": null 247 | } 248 | }, 249 | "statusCode": 200, 250 | "headers": { 251 | "Access-Control-Allow-Origin": "*", 252 | "Age": "0", 253 | "Cache-Control": "private", 254 | "Content-Type": "application\/json", 255 | "Pinterest-Version": "4f9f34e", 256 | "X-Content-Type-Options": "nosniff", 257 | "X-Pinterest-RID": "651419837947", 258 | "X-Ratelimit-Limit": "10", 259 | "X-Ratelimit-Remaining": "2", 260 | "Content-Length": "7062", 261 | "Date": "Sun, 22 Apr 2018 15:06:50 GMT", 262 | "Connection": "keep-alive", 263 | "Pinterest-Generated-By:": "" 264 | } 265 | } -------------------------------------------------------------------------------- /tests/Pinterest/responses/get_v1_me_followers_e7f00147b20fff1c562ea111852e576e.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "data": { 4 | "0": { 5 | "username": "claravanpeteghe", 6 | "bio": "", 7 | "first_name": "Clara", 8 | "last_name": "Van Peteghem", 9 | "url": "https:\/\/www.pinterest.com\/claravanpeteghe\/", 10 | "created_at": "2015-04-18T19:15:04", 11 | "image": { 12 | "60x60": { 13 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/66\/2b\/bd\/662bbd77d3541f7f6f7ea2c64cd7b86b.jpg", 14 | "width": 60, 15 | "height": 60 16 | } 17 | }, 18 | "counts": { 19 | "pins": 11, 20 | "following": 62, 21 | "followers": 26, 22 | "boards": 3 23 | }, 24 | "id": "539798842745345952" 25 | }, 26 | "1": { 27 | "username": "lauraelegeert", 28 | "bio": "", 29 | "first_name": "Laura", 30 | "last_name": "Elegeert", 31 | "url": "https:\/\/www.pinterest.com\/lauraelegeert\/", 32 | "created_at": "2015-05-14T14:10:11", 33 | "image": { 34 | "60x60": { 35 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/51\/5e\/31\/515e31f363351d536b6d8b57d1782875.jpg", 36 | "width": 60, 37 | "height": 60 38 | } 39 | }, 40 | "counts": { 41 | "pins": 151, 42 | "following": 245, 43 | "followers": 32, 44 | "boards": 9 45 | }, 46 | "id": "560627991021937396" 47 | }, 48 | "2": { 49 | "username": "silketjepilketj", 50 | "bio": "", 51 | "first_name": "Silke", 52 | "last_name": "Van Hiel", 53 | "url": "https:\/\/www.pinterest.com\/silketjepilketj\/", 54 | "created_at": "2015-04-11T09:31:34", 55 | "image": { 56 | "60x60": { 57 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/c9\/38\/70\/c93870a317915fad2b524998088cb095.jpg", 58 | "width": 60, 59 | "height": 60 60 | } 61 | }, 62 | "counts": { 63 | "pins": 76, 64 | "following": 68, 65 | "followers": 5, 66 | "boards": 4 67 | }, 68 | "id": "543880229907650474" 69 | }, 70 | "3": { 71 | "username": "moniquediericx", 72 | "bio": "", 73 | "first_name": "Monique", 74 | "last_name": "Diericx", 75 | "url": "https:\/\/www.pinterest.com\/moniquediericx\/", 76 | "created_at": "2015-02-23T15:20:26", 77 | "image": { 78 | "60x60": { 79 | "url": "https:\/\/s.pinimg.com\/images\/user\/default_60.png", 80 | "width": 60, 81 | "height": 60 82 | } 83 | }, 84 | "counts": { 85 | "pins": 265, 86 | "following": 22, 87 | "followers": 7, 88 | "boards": 7 89 | }, 90 | "id": "378443312345958237" 91 | }, 92 | "4": { 93 | "username": "laurens_", 94 | "bio": "", 95 | "first_name": "Laurens", 96 | "last_name": "Baes", 97 | "url": "https:\/\/www.pinterest.com\/laurens_\/", 98 | "created_at": "2015-02-15T10:40:23", 99 | "image": { 100 | "60x60": { 101 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/17\/7e\/c0\/177ec0fdf0056301387fe6f439d7296d.jpg", 102 | "width": 60, 103 | "height": 60 104 | } 105 | }, 106 | "counts": { 107 | "pins": 0, 108 | "following": 55, 109 | "followers": 18, 110 | "boards": 0 111 | }, 112 | "id": "484418641077520384" 113 | }, 114 | "5": { 115 | "username": "heleenvanduyse", 116 | "bio": "", 117 | "first_name": "Heleen", 118 | "last_name": "Van Duyse", 119 | "url": "https:\/\/www.pinterest.com\/heleenvanduyse\/", 120 | "created_at": "2015-02-04T17:02:09", 121 | "image": { 122 | "60x60": { 123 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/f8\/02\/7d\/f8027d12b9a682003750566d2a0f2657.jpg", 124 | "width": 60, 125 | "height": 60 126 | } 127 | }, 128 | "counts": { 129 | "pins": 14, 130 | "following": 119, 131 | "followers": 34, 132 | "boards": 2 133 | }, 134 | "id": "551409685534654075" 135 | }, 136 | "6": { 137 | "username": "kelseybeck39", 138 | "bio": "", 139 | "first_name": "Kelsey", 140 | "last_name": "Beck", 141 | "url": "https:\/\/www.pinterest.com\/kelseybeck39\/", 142 | "created_at": "2014-12-23T15:40:16", 143 | "image": { 144 | "60x60": { 145 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/fe\/cf\/9d\/fecf9dd8e6fe88729f0583460c320282.jpg", 146 | "width": 60, 147 | "height": 60 148 | } 149 | }, 150 | "counts": { 151 | "pins": 36, 152 | "following": 129, 153 | "followers": 54, 154 | "boards": 7 155 | }, 156 | "id": "416512802946068621" 157 | }, 158 | "7": { 159 | "username": "sarah__dg", 160 | "bio": "", 161 | "first_name": "Sarah", 162 | "last_name": "De Greyt", 163 | "url": "https:\/\/www.pinterest.com\/sarah__dg\/", 164 | "created_at": "2014-12-18T23:11:01", 165 | "image": { 166 | "60x60": { 167 | "url": "https:\/\/s.pinimg.com\/images\/user\/default_60.png", 168 | "width": 60, 169 | "height": 60 170 | } 171 | }, 172 | "counts": { 173 | "pins": 18, 174 | "following": 116, 175 | "followers": 33, 176 | "boards": 2 177 | }, 178 | "id": "309763418028552126" 179 | }, 180 | "8": { 181 | "username": "chareke_salens", 182 | "bio": "", 183 | "first_name": "Charlotte", 184 | "last_name": "Salens", 185 | "url": "https:\/\/www.pinterest.com\/chareke_salens\/", 186 | "created_at": "2014-12-18T19:41:59", 187 | "image": { 188 | "60x60": { 189 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/0e\/e2\/6b\/0ee26bb9d7bb5d1b2c993b9c47268878.jpg", 190 | "width": 60, 191 | "height": 60 192 | } 193 | }, 194 | "counts": { 195 | "pins": 11, 196 | "following": 60, 197 | "followers": 18, 198 | "boards": 1 199 | }, 200 | "id": "504684839400682235" 201 | }, 202 | "9": { 203 | "username": "maxinetjeuh", 204 | "bio": "", 205 | "first_name": "Maxine", 206 | "last_name": "Van Meirvenne", 207 | "url": "https:\/\/www.pinterest.com\/maxinetjeuh\/", 208 | "created_at": "2014-12-16T16:35:02", 209 | "image": { 210 | "60x60": { 211 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/27\/c0\/af\/27c0af9128463f8edabe4cc160a0ce02.jpg", 212 | "width": 60, 213 | "height": 60 214 | } 215 | }, 216 | "counts": { 217 | "pins": 317, 218 | "following": 147, 219 | "followers": 60, 220 | "boards": 11 221 | }, 222 | "id": "382665436996612342" 223 | }, 224 | "10": { 225 | "username": "0015t9t2u9w595h", 226 | "bio": "", 227 | "first_name": "Sofie", 228 | "last_name": "van der Ha", 229 | "url": "https:\/\/www.pinterest.com\/0015t9t2u9w595h\/", 230 | "created_at": "2014-11-11T16:59:55", 231 | "image": { 232 | "60x60": { 233 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/ad\/62\/ab\/ad62abe98de2a587028a1d788be2f697.jpg", 234 | "width": 60, 235 | "height": 60 236 | } 237 | }, 238 | "counts": { 239 | "pins": 0, 240 | "following": 138, 241 | "followers": 64, 242 | "boards": 0 243 | }, 244 | "id": "512917982469464997" 245 | }, 246 | "11": { 247 | "username": "julievertenten", 248 | "bio": "", 249 | "first_name": "Julie", 250 | "last_name": "Vertenten", 251 | "url": "https:\/\/www.pinterest.com\/julievertenten\/", 252 | "created_at": "2014-11-07T22:17:04", 253 | "image": { 254 | "60x60": { 255 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/f7\/9e\/e9\/f79ee91df40c9553812d379ccb31f289.jpg", 256 | "width": 60, 257 | "height": 60 258 | } 259 | }, 260 | "counts": { 261 | "pins": 266, 262 | "following": 132, 263 | "followers": 71, 264 | "boards": 18 265 | }, 266 | "id": "332281416165401266" 267 | }, 268 | "12": { 269 | "username": "elinevanbauwel", 270 | "bio": "", 271 | "first_name": "Eline", 272 | "last_name": "van Bauwel", 273 | "url": "https:\/\/www.pinterest.com\/elinevanbauwel\/", 274 | "created_at": "2014-11-07T09:10:07", 275 | "image": { 276 | "60x60": { 277 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/3f\/2c\/6c\/3f2c6cdbc5ef92c351498356d0370476.jpg", 278 | "width": 60, 279 | "height": 60 280 | } 281 | }, 282 | "counts": { 283 | "pins": 357, 284 | "following": 88, 285 | "followers": 53, 286 | "boards": 27 287 | }, 288 | "id": "493144365355540908" 289 | }, 290 | "13": { 291 | "username": "keppenssarah", 292 | "bio": "", 293 | "first_name": "Sarah", 294 | "last_name": "Keppens", 295 | "url": "https:\/\/www.pinterest.com\/keppenssarah\/", 296 | "created_at": "2014-10-24T10:12:11", 297 | "image": { 298 | "60x60": { 299 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/e6\/1c\/bb\/e61cbb55e5abca5f1cafb7993451c99a.jpg", 300 | "width": 60, 301 | "height": 60 302 | } 303 | }, 304 | "counts": { 305 | "pins": 130, 306 | "following": 42, 307 | "followers": 21, 308 | "boards": 5 309 | }, 310 | "id": "490611090565143810" 311 | }, 312 | "14": { 313 | "username": "ninatroch", 314 | "bio": "", 315 | "first_name": "Nina", 316 | "last_name": "Troch", 317 | "url": "https:\/\/www.pinterest.com\/ninatroch\/", 318 | "created_at": "2014-10-20T15:29:40", 319 | "image": { 320 | "60x60": { 321 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/d5\/a5\/b0\/d5a5b0120a98c4cf4f32ff4891278978.jpg", 322 | "width": 60, 323 | "height": 60 324 | } 325 | }, 326 | "counts": { 327 | "pins": 27, 328 | "following": 102, 329 | "followers": 57, 330 | "boards": 4 331 | }, 332 | "id": "504192258191432819" 333 | }, 334 | "15": { 335 | "username": "fannitje", 336 | "bio": "", 337 | "first_name": "Fanni", 338 | "last_name": "van Osselaer", 339 | "url": "https:\/\/www.pinterest.com\/fannitje\/", 340 | "created_at": "2014-10-09T21:30:30", 341 | "image": { 342 | "60x60": { 343 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/ef\/1c\/3e\/ef1c3ee7a842af366285fb2ea490990f.jpg", 344 | "width": 60, 345 | "height": 60 346 | } 347 | }, 348 | "counts": { 349 | "pins": 136, 350 | "following": 158, 351 | "followers": 53, 352 | "boards": 6 353 | }, 354 | "id": "444660300617127775" 355 | }, 356 | "16": { 357 | "username": "yasminevanduyse", 358 | "bio": "", 359 | "first_name": "Yasmine", 360 | "last_name": "Van Duyse", 361 | "url": "https:\/\/www.pinterest.com\/yasminevanduyse\/", 362 | "created_at": "2014-10-03T14:24:44", 363 | "image": { 364 | "60x60": { 365 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/70\/48\/2d\/70482dd0205e612aef782fcc69fcae3e.jpg", 366 | "width": 60, 367 | "height": 60 368 | } 369 | }, 370 | "counts": { 371 | "pins": 147, 372 | "following": 99, 373 | "followers": 55, 374 | "boards": 14 375 | }, 376 | "id": "522277025445090532" 377 | }, 378 | "17": { 379 | "username": "sara_linthout", 380 | "bio": "", 381 | "first_name": "Sara", 382 | "last_name": "Linthout", 383 | "url": "https:\/\/www.pinterest.com\/sara_linthout\/", 384 | "created_at": "2014-09-02T18:11:01", 385 | "image": { 386 | "60x60": { 387 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/d8\/f2\/b8\/d8f2b82da0de810e12fa53838610bce2.jpg", 388 | "width": 60, 389 | "height": 60 390 | } 391 | }, 392 | "counts": { 393 | "pins": 301, 394 | "following": 87, 395 | "followers": 54, 396 | "boards": 14 397 | }, 398 | "id": "520588175584823694" 399 | }, 400 | "18": { 401 | "username": "gonegoing", 402 | "bio": "you're looking for me but it's not what you seek, I've got a twisted personality", 403 | "first_name": "Lisa", 404 | "last_name": "Ott", 405 | "url": "https:\/\/www.pinterest.com\/gonegoing\/", 406 | "created_at": "2012-06-21T16:35:38", 407 | "image": { 408 | "60x60": { 409 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/2d\/00\/af\/2d00afdf646209a2c4e4c5de18e8f2ad.jpg", 410 | "width": 60, 411 | "height": 60 412 | } 413 | }, 414 | "counts": { 415 | "pins": 2161, 416 | "following": 129, 417 | "followers": 108, 418 | "boards": 31 419 | }, 420 | "id": "235242917944366432" 421 | }, 422 | "19": { 423 | "username": "amelievanvlierb", 424 | "bio": "", 425 | "first_name": "Amelie", 426 | "last_name": "Van Vlierberghe", 427 | "url": "https:\/\/www.pinterest.com\/amelievanvlierb\/", 428 | "created_at": "2015-09-05T17:04:42", 429 | "image": { 430 | "60x60": { 431 | "url": "https:\/\/s.pinimg.com\/images\/user\/default_60.png", 432 | "width": 60, 433 | "height": 60 434 | } 435 | }, 436 | "counts": { 437 | "pins": 78, 438 | "following": 207, 439 | "followers": 7, 440 | "boards": 2 441 | }, 442 | "id": "14285061226512758" 443 | }, 444 | "20": { 445 | "username": "sarahmtn", 446 | "bio": "A vagabond=a drifter and an itinerant wanderer who roams wherever they please, following the whim of the moment. Vagabonds may lack residence, a job, and even citizenship.www.sarahmouton.wordpress.com", 447 | "first_name": "Sarah", 448 | "last_name": "Mouton", 449 | "url": "https:\/\/www.pinterest.com\/sarahmtn\/", 450 | "created_at": "2012-05-17T06:00:05", 451 | "image": { 452 | "60x60": { 453 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/c7\/59\/44\/c75944feb1d97e2550039941fb1d9158.jpg", 454 | "width": 60, 455 | "height": 60 456 | } 457 | }, 458 | "counts": { 459 | "pins": 11232, 460 | "following": 1962, 461 | "followers": 866, 462 | "boards": 56 463 | }, 464 | "id": "96194279449301856" 465 | }, 466 | "21": { 467 | "username": "delphinevanexem", 468 | "bio": "", 469 | "first_name": "Delphine", 470 | "last_name": "Van Exem", 471 | "url": "https:\/\/www.pinterest.com\/delphinevanexem\/", 472 | "created_at": "2015-05-29T22:55:21", 473 | "image": { 474 | "60x60": { 475 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/c4\/9b\/e3\/c49be3fab5cc979da328a454046010a9.jpg", 476 | "width": 60, 477 | "height": 60 478 | } 479 | }, 480 | "counts": { 481 | "pins": 1106, 482 | "following": 82, 483 | "followers": 24, 484 | "boards": 6 485 | }, 486 | "id": "295830406681388774" 487 | }, 488 | "22": { 489 | "username": "johnyz50a", 490 | "bio": "", 491 | "first_name": "John", 492 | "last_name": "Coppens", 493 | "url": "https:\/\/www.pinterest.com\/johnyz50a\/", 494 | "created_at": "2015-07-21T18:50:10", 495 | "image": { 496 | "60x60": { 497 | "url": "https:\/\/i.pinimg.com\/60x60_RS\/67\/67\/18\/676718eab9a2894a73053f3d43ea3970.jpg", 498 | "width": 60, 499 | "height": 60 500 | } 501 | }, 502 | "counts": { 503 | "pins": 11, 504 | "following": 245, 505 | "followers": 23, 506 | "boards": 1 507 | }, 508 | "id": "468304198660873189" 509 | } 510 | }, 511 | "page": { 512 | "cursor": null, 513 | "next": null 514 | } 515 | }, 516 | "statusCode": 200, 517 | "headers": { 518 | "Access-Control-Allow-Origin": "*", 519 | "Age": "0", 520 | "Cache-Control": "private", 521 | "Content-Type": "application\/json", 522 | "Pinterest-Version": "4f9f34e", 523 | "X-Content-Type-Options": "nosniff", 524 | "X-Pinterest-RID": "693078258137", 525 | "X-Ratelimit-Limit": "10", 526 | "X-Ratelimit-Remaining": "3", 527 | "Content-Length": "9735", 528 | "Date": "Sun, 22 Apr 2018 12:04:22 GMT", 529 | "Connection": "keep-alive", 530 | "Pinterest-Generated-By:": "" 531 | } 532 | } -------------------------------------------------------------------------------- /src/Pinterest/Api.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * This source file is subject to the MIT license that is bundled 9 | * with this source code in the file LICENSE.md. 10 | * 11 | * Source: https://github.com/hansott/pinterest-php 12 | */ 13 | 14 | namespace Pinterest; 15 | 16 | use Pinterest\Objects\Pin; 17 | use Pinterest\Objects\User; 18 | use Pinterest\Http\Request; 19 | use Pinterest\Http\Response; 20 | use Pinterest\Objects\Board; 21 | use InvalidArgumentException; 22 | use Pinterest\Objects\PagedList; 23 | use Pinterest\Http\Exceptions\RateLimitedReached; 24 | 25 | /** 26 | * The api client. 27 | * 28 | * @author Hans Ott 29 | * @author Toon Daelman 30 | */ 31 | class Api 32 | { 33 | /** 34 | * The authentication client. 35 | * 36 | * @var Authentication 37 | */ 38 | private $auth; 39 | 40 | /** 41 | * The constructor. 42 | * 43 | * @param Authentication $auth The authentication client. 44 | */ 45 | public function __construct(Authentication $auth) 46 | { 47 | $this->auth = $auth; 48 | } 49 | 50 | /** 51 | * Processes a response. 52 | * 53 | * @param Response $response The response object. 54 | * @param callable $processor The response processor. 55 | * 56 | * @return Response The response 57 | */ 58 | private function processResponse(Response $response, $processor) 59 | { 60 | if ($response->ok()) { 61 | $result = $processor($response); 62 | $response->setResult($result); 63 | } 64 | 65 | return $response; 66 | } 67 | 68 | /** 69 | * Execute the given http request. 70 | * 71 | * @param Request $request 72 | * @param callable|null $processor 73 | * 74 | * @throws RateLimitedReached 75 | * 76 | * @return Response The response 77 | */ 78 | public function execute(Request $request, $processor = null) 79 | { 80 | $response = $this->auth->execute($request); 81 | 82 | if ($response->rateLimited()) { 83 | throw new RateLimitedReached($response); 84 | } 85 | 86 | if (is_callable($processor)) { 87 | $response = $this->processResponse($response, $processor); 88 | } 89 | 90 | return $response; 91 | } 92 | 93 | /** 94 | * Fetch a single user and processes the response. 95 | * 96 | * @param Request $request 97 | * 98 | * @throws RateLimitedReached 99 | * 100 | * @return Response The response 101 | */ 102 | private function fetchUser(Request $request) 103 | { 104 | $request->setFields(User::fields()); 105 | 106 | return $this->execute($request, function (Response $response) { 107 | $mapper = new Mapper(new User()); 108 | 109 | return $mapper->toSingle($response); 110 | }); 111 | } 112 | 113 | /** 114 | * Fetch a single board and processes the response. 115 | * 116 | * @param Request $request 117 | * 118 | * @throws RateLimitedReached 119 | * 120 | * @return Response The response 121 | */ 122 | private function fetchBoard(Request $request) 123 | { 124 | $request->setFields(Board::fields()); 125 | 126 | return $this->execute($request, function (Response $response) { 127 | $mapper = new Mapper(new Board()); 128 | 129 | return $mapper->toSingle($response); 130 | }); 131 | } 132 | 133 | /** 134 | * Fetch a single pin and processes the response. 135 | * 136 | * @param Request $request 137 | * 138 | * @throws RateLimitedReached 139 | * 140 | * @return Response The response 141 | */ 142 | private function fetchPin(Request $request) 143 | { 144 | $request->setFields(Pin::fields()); 145 | 146 | return $this->execute($request, function (Response $response) { 147 | $mapper = new Mapper(new Pin()); 148 | 149 | return $mapper->toSingle($response); 150 | }); 151 | } 152 | 153 | /** 154 | * Fetch multiple boards and processes the response. 155 | * 156 | * @param Request $request 157 | * @param string[] $fields 158 | * 159 | * @throws RateLimitedReached 160 | * 161 | * @return Response The response 162 | */ 163 | private function fetchMultipleBoards(Request $request, array $fields = null) 164 | { 165 | $fields = $fields ? $fields : Board::fields(); 166 | $request->setFields($fields); 167 | 168 | return $this->execute($request, function (Response $response) { 169 | $mapper = new Mapper(new Board()); 170 | 171 | return $mapper->toList($response); 172 | }); 173 | } 174 | 175 | /** 176 | * Fetch multiple users and processes the response. 177 | * 178 | * @param Request $request 179 | * 180 | * @throws RateLimitedReached 181 | * 182 | * @return Response The response 183 | */ 184 | private function fetchMultipleUsers(Request $request) 185 | { 186 | $request->setFields(User::fields()); 187 | 188 | return $this->execute($request, function (Response $response) { 189 | $mapper = new Mapper(new User()); 190 | 191 | return $mapper->toList($response); 192 | }); 193 | } 194 | 195 | /** 196 | * Fetches multiple pins and processes the response. 197 | * 198 | * @param Request $request 199 | * @param $fields array The fields to require. 200 | * 201 | * @throws RateLimitedReached 202 | * 203 | * @return Response The response 204 | */ 205 | private function fetchMultiplePins(Request $request, array $fields = null) 206 | { 207 | $fields = $fields ? $fields : Pin::fields(); 208 | $request->setFields($fields); 209 | 210 | return $this->execute($request, function (Response $response) { 211 | $mapper = new Mapper(new Pin()); 212 | 213 | return $mapper->toList($response); 214 | }); 215 | } 216 | 217 | /** 218 | * Get a user. 219 | * 220 | * @param string $usernameOrId The username or identifier of the user. 221 | * 222 | * @throws RateLimitedReached 223 | * 224 | * @return Response The response 225 | */ 226 | public function getUser($usernameOrId) 227 | { 228 | if (empty($usernameOrId)) { 229 | throw new InvalidArgumentException('The username or id should not be empty.'); 230 | } 231 | 232 | $request = new Request('GET', sprintf('users/%s/', $usernameOrId)); 233 | 234 | return $this->fetchUser($request); 235 | } 236 | 237 | /** 238 | * Get a board. 239 | * 240 | * @param string $boardId The board id. 241 | * 242 | * @throws RateLimitedReached 243 | * 244 | * @return Response The response 245 | */ 246 | public function getBoard($boardId) 247 | { 248 | if (empty($boardId)) { 249 | throw new InvalidArgumentException('The board id should not be empty.'); 250 | } 251 | 252 | $request = new Request('GET', sprintf('boards/%s/', $boardId)); 253 | 254 | return $this->fetchBoard($request); 255 | } 256 | 257 | /** 258 | * Update a board. 259 | * 260 | * @param Board $board The updated board. 261 | * 262 | * @throws RateLimitedReached 263 | * 264 | * @return Response The response 265 | */ 266 | public function updateBoard(Board $board) 267 | { 268 | $params = array(); 269 | 270 | if (empty($board->id)) { 271 | throw new InvalidArgumentException('The board id is required.'); 272 | } 273 | 274 | if (isset($board->name) && empty($board->name) === false) { 275 | $params['name'] = (string) $board->name; 276 | } 277 | 278 | if (isset($board->description) && empty($board->description) === false) { 279 | $params['description'] = (string) $board->description; 280 | } 281 | 282 | $request = new Request('PATCH', sprintf('boards/%s/', $board->id), $params); 283 | 284 | return $this->fetchBoard($request); 285 | } 286 | 287 | /** 288 | * Get the boards of the authenticated user. 289 | * 290 | * @throws RateLimitedReached 291 | * 292 | * @return Response The response 293 | */ 294 | public function getUserBoards() 295 | { 296 | $request = new Request('GET', 'me/boards/'); 297 | 298 | return $this->fetchMultipleBoards($request); 299 | } 300 | 301 | /** 302 | * Get the pins of the authenticated user. 303 | * 304 | * @throws RateLimitedReached 305 | * 306 | * @return Response The response 307 | */ 308 | public function getUserPins() 309 | { 310 | $request = new Request('GET', 'me/pins/'); 311 | 312 | return $this->fetchMultiplePins($request); 313 | } 314 | 315 | /** 316 | * Get the authenticated user. 317 | * 318 | * @throws RateLimitedReached 319 | * 320 | * @return Response The response 321 | */ 322 | public function getCurrentUser() 323 | { 324 | $request = new Request('GET', 'me/'); 325 | 326 | return $this->fetchUser($request); 327 | } 328 | 329 | /** 330 | * Get the followers of the authenticated user. 331 | * 332 | * @throws RateLimitedReached 333 | * 334 | * @return Response The response 335 | */ 336 | public function getUserFollowers() 337 | { 338 | $request = new Request('GET', 'me/followers/'); 339 | 340 | return $this->fetchMultipleUsers($request); 341 | } 342 | 343 | /** 344 | * Get the boards that the authenticated user follows. 345 | * 346 | * @throws RateLimitedReached 347 | * 348 | * @return Response The response 349 | */ 350 | public function getUserFollowingBoards() 351 | { 352 | $request = new Request('GET', 'me/following/boards/'); 353 | 354 | return $this->fetchMultipleBoards($request); 355 | } 356 | 357 | /** 358 | * Get the users that the authenticated user follows. 359 | * 360 | * @throws RateLimitedReached 361 | * 362 | * @return Response The response 363 | */ 364 | public function getUserFollowing() 365 | { 366 | $request = new Request('GET', 'me/following/users/'); 367 | 368 | return $this->fetchMultipleUsers($request); 369 | } 370 | 371 | /** 372 | * Get the interests that the authenticated user follows. 373 | * 374 | * @link https://www.pinterest.com/explore/901179409185 375 | * 376 | * @throws RateLimitedReached 377 | * 378 | * @return Response The response 379 | */ 380 | public function getUserInterests() 381 | { 382 | $request = new Request('GET', 'me/following/interests/'); 383 | 384 | return $this->fetchMultipleBoards($request, array('id', 'name')); 385 | } 386 | 387 | /** 388 | * Follow a user. 389 | * 390 | * @param string $username The username of the user to follow. 391 | * 392 | * @throws RateLimitedReached 393 | * 394 | * @return Response The response 395 | */ 396 | public function followUser($username) 397 | { 398 | if (empty($username)) { 399 | throw new InvalidArgumentException('Username is required.'); 400 | } 401 | 402 | $request = new Request( 403 | 'POST', 404 | 'me/following/users/', 405 | array( 406 | 'user' => (string) $username, 407 | ) 408 | ); 409 | 410 | return $this->execute($request); 411 | } 412 | 413 | /** 414 | * Unfollow a user. 415 | * 416 | * @param string $usernameOrUserId The username or ID of the user to unfollow. 417 | * 418 | * @throws RateLimitedReached 419 | * 420 | * @return Response The response 421 | */ 422 | public function unfollowUser($usernameOrUserId) 423 | { 424 | if (empty($usernameOrUserId)) { 425 | throw new InvalidArgumentException('Username or user ID is required.'); 426 | } 427 | 428 | $request = new Request( 429 | 'DELETE', 430 | "me/following/users/{$usernameOrUserId}" 431 | ); 432 | 433 | return $this->execute($request); 434 | } 435 | 436 | /** 437 | * Follow a board. 438 | * 439 | * @param string $username The username of the user that owns the board 440 | * @param string $boardName The name of the board 441 | * 442 | * @return Response The response 443 | * 444 | * @throws RateLimitedReached 445 | */ 446 | public function followBoard($username, $boardName) 447 | { 448 | if (empty($username)) { 449 | throw new InvalidArgumentException('Username is required.'); 450 | } 451 | 452 | if (empty($boardName)) { 453 | throw new InvalidArgumentException('The board name is required.'); 454 | } 455 | 456 | $request = new Request( 457 | 'POST', 458 | 'me/following/boards/', 459 | array( 460 | 'board' => "{$username}/{$boardName}", 461 | ) 462 | ); 463 | 464 | return $this->execute($request); 465 | } 466 | 467 | /** 468 | * Unfollow a board. 469 | * 470 | * @param string $username The username of the user that owns the board 471 | * @param string $boardName The name of the board 472 | * 473 | * @return Response The response 474 | * 475 | * @throws RateLimitedReached 476 | */ 477 | public function unfollowBoard($username, $boardName) 478 | { 479 | if (empty($username)) { 480 | throw new InvalidArgumentException('Username is required.'); 481 | } 482 | 483 | if (empty($boardName)) { 484 | throw new InvalidArgumentException('The board name is required.'); 485 | } 486 | 487 | $request = new Request( 488 | 'DELETE', 489 | "me/following/boards/{$username}/{$boardName}" 490 | ); 491 | 492 | return $this->execute($request); 493 | } 494 | 495 | /** 496 | * Create a board. 497 | * 498 | * @param string $name The board name. 499 | * @param string $description The board description. 500 | * 501 | * @throws RateLimitedReached 502 | * 503 | * @return Response The response 504 | */ 505 | public function createBoard($name, $description = null) 506 | { 507 | if (empty($name)) { 508 | throw new InvalidArgumentException('The name should not be empty.'); 509 | } 510 | 511 | $params = array( 512 | 'name' => (string) $name, 513 | ); 514 | 515 | if (empty($description) === false) { 516 | $params['description'] = (string) $description; 517 | } 518 | 519 | $request = new Request('POST', 'boards/', $params); 520 | 521 | return $this->fetchBoard($request); 522 | } 523 | 524 | /** 525 | * Delete a board. 526 | * 527 | * @param int $boardId The board id. 528 | * 529 | * @throws RateLimitedReached 530 | * 531 | * @return Response The response 532 | */ 533 | public function deleteBoard($boardId) 534 | { 535 | if (empty($boardId)) { 536 | throw new InvalidArgumentException('The board id should not be empty.'); 537 | } 538 | 539 | $request = new Request('DELETE', "boards/{$boardId}/"); 540 | 541 | return $this->execute($request); 542 | } 543 | 544 | /** 545 | * Create a pin on a board. 546 | * 547 | * @param string $boardId The board id. 548 | * @param string $note The note. 549 | * @param Image $image The image. 550 | * @param string|null $link The link (Optional). 551 | * 552 | * @throws RateLimitedReached 553 | * 554 | * @return Response The response 555 | */ 556 | public function createPin($boardId, $note, Image $image, $link = null) 557 | { 558 | if (empty($boardId)) { 559 | throw new InvalidArgumentException('The board id should not be empty.'); 560 | } 561 | 562 | if (empty($note)) { 563 | throw new InvalidArgumentException('The note should not be empty.'); 564 | } 565 | 566 | $params = array( 567 | 'board' => $boardId, 568 | 'note' => (string) $note, 569 | ); 570 | 571 | if (empty($link) === false) { 572 | $params['link'] = (string) $link; 573 | } 574 | 575 | $imageKey = $image->isUrl() ? 'image_url' : ($image->isBase64() ? 'image_base64' : 'image'); 576 | 577 | if ($image->isFile()) { 578 | $params[$imageKey] = $image; 579 | } else { 580 | $params[$imageKey] = $image->getData(); 581 | } 582 | 583 | $request = new Request('POST', 'pins/', $params); 584 | 585 | return $this->fetchPin($request); 586 | } 587 | 588 | /** 589 | * Delete a Pin. 590 | * 591 | * @param string $pinId The id of the pin to delete. 592 | * 593 | * @throws RateLimitedReached 594 | * 595 | * @return Response The response 596 | */ 597 | public function deletePin($pinId) 598 | { 599 | if (empty($pinId)) { 600 | throw new InvalidArgumentException('The pin id should not be empty.'); 601 | } 602 | 603 | $request = new Request('DELETE', "pins/{$pinId}/"); 604 | 605 | return $this->execute($request); 606 | } 607 | 608 | /** 609 | * Get the next items for a paged list. 610 | * 611 | * @param PagedList $pagedList 612 | * 613 | * @throws RateLimitedReached 614 | * 615 | * @return Response The response 616 | */ 617 | public function getNextItems(PagedList $pagedList) 618 | { 619 | if (!$pagedList->hasNext()) { 620 | throw new InvalidArgumentException('The list has no more items'); 621 | } 622 | 623 | $items = $pagedList->items(); 624 | 625 | if (empty($items)) { 626 | throw new InvalidArgumentException( 627 | 'Unable to detect object type because the list contains no items' 628 | ); 629 | } 630 | 631 | $item = reset($items); 632 | $objectClassName = get_class($item); 633 | $objectInstance = new $objectClassName(); 634 | 635 | $request = $this->buildRequestForPagedList($pagedList); 636 | 637 | return $this->execute($request, function (Response $response) use ($objectInstance) { 638 | $mapper = new Mapper($objectInstance); 639 | 640 | return $mapper->toList($response); 641 | }); 642 | } 643 | 644 | /** 645 | * Build a request to get the next items of a paged list. 646 | * 647 | * @param PagedList $pagedList 648 | * 649 | * @return Request 650 | */ 651 | private function buildRequestForPagedList(PagedList $pagedList) 652 | { 653 | $nextItemsUri = $pagedList->getNextUrl(); 654 | 655 | $params = array(); 656 | $components = parse_url($nextItemsUri); 657 | parse_str($components['query'], $params); 658 | 659 | $path = $components['path']; 660 | $versionPath = sprintf('/%s/', Authentication::API_VERSION); 661 | $versionPathLength = strlen($versionPath); 662 | $path = substr($path, $versionPathLength); 663 | 664 | return new Request('GET', $path, $params); 665 | } 666 | 667 | /** 668 | * Get the pins of a board. 669 | * 670 | * @param string $boardId 671 | * 672 | * @throws RateLimitedReached 673 | * 674 | * @return Response The response 675 | */ 676 | public function getBoardPins($boardId) 677 | { 678 | if (empty($boardId)) { 679 | throw new InvalidArgumentException('The board id should not be empty.'); 680 | } 681 | 682 | $endpoint = sprintf('boards/%s/pins/', $boardId); 683 | $request = new Request('GET', $endpoint); 684 | 685 | return $this->fetchMultiplePins($request); 686 | } 687 | 688 | /** 689 | * Get a single pin. 690 | * 691 | * @param string $pinId 692 | * 693 | * @throws RateLimitedReached 694 | * 695 | * @return Response The Response 696 | */ 697 | public function getPin($pinId) 698 | { 699 | if (empty($pinId)) { 700 | throw new InvalidArgumentException('The pin id should not be empty.'); 701 | } 702 | 703 | $endpoint = sprintf('pins/%s/', $pinId); 704 | $request = new Request('GET', $endpoint); 705 | 706 | return $this->fetchPin($request); 707 | } 708 | 709 | /** 710 | * Update a pin. 711 | * 712 | * @param Pin $pin 713 | * 714 | * @throws RateLimitedReached 715 | * 716 | * @return Response The response 717 | */ 718 | public function updatePin(Pin $pin) 719 | { 720 | if (empty($pin->id)) { 721 | throw new InvalidArgumentException('The pin id is required.'); 722 | } 723 | 724 | $params = array(); 725 | 726 | if (isset($pin->note) && empty($pin->note) === false) { 727 | $params['note'] = (string) $pin->note; 728 | } 729 | 730 | if (isset($pin->link) && empty($pin->link) === false) { 731 | $params['link'] = (string) $pin->link; 732 | } 733 | 734 | if ( 735 | isset($pin->board, $pin->board->name, $pin->board->creator, $pin->board->creator->username) 736 | && (empty($pin->board->name) === false && empty($pin->board->creator->username) === false) 737 | ) { 738 | $params['board'] = "{$pin->board->creator->username}/{$pin->board->name}"; 739 | } 740 | 741 | if (empty($params)) { 742 | throw new InvalidArgumentException( 743 | "You're not changing any values. You can update a pin's note, link and/or board." 744 | ); 745 | } 746 | 747 | $endpoint = sprintf('pins/%s/', $pin->id); 748 | $request = new Request('PATCH', $endpoint, $params); 749 | 750 | return $this->fetchPin($request); 751 | } 752 | } 753 | --------------------------------------------------------------------------------