├── CHANGELOG.md
├── .gitattributes
├── .gitignore
├── .styleci.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── composer.json
└── src
├── Instagram.php
└── InstagramException.php
/ CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Changelog
2 |
3 | All notable changes to `cosenary/instagram` will be documented in this file.
4 |
5 | > Version 3.0 is in development and includes support for real-time subscriptions.
6 |
7 | **Instagram 2.2 - 04/10/2014**
8 |
9 | - `feature` Added "Enforce signed header"
10 | - `feature` Implemented PSR4 autoloading.
11 | - `update` Increased timeout from 5 to 20 seconds
12 | - `update` Class name, package renamed
13 |
14 | **Instagram 2.1 - 30/01/2014**
15 |
16 | - `update` added min and max_timestamp to `searchMedia()`
17 | - `update` public authentication for `getUserMedia()` method
18 | - `fix` support for inconsistent pagination return type (*relationship endpoint*)
19 |
20 | **Instagram 2.0 - 24/12/2013**
21 |
22 | - `release` version 2.0
23 |
24 | **Instagram 2.0 beta - 20/11/2013**
25 |
26 | - `feature` Added *Locations* endpoint
27 | - `update` Updated example project to display Instagram videos
28 |
29 | **Instagram 2.0 alpha 4 - 01/11/2013**
30 |
31 | - `feature` Comment endpoint implemented
32 | - `feature` New example with a fancy GUI
33 | - `update` Improved documentation
34 |
35 | **Instagram 2.0 alpha 3 - 04/09/2013**
36 |
37 | - `merge` Merged master branch updates
38 | - `update` Updated documentation
39 | - `bug` / `change` cURL CURLOPT_SSL_VERIFYPEER disabled (fixes #6, #7, #8, #16)
40 | - `feature` Added cURL error message
41 | - `feature` Added `limit` to `getTagMedia()` method
42 |
43 | **Instagram 2.0 alpha 2 - 14/06/2013**
44 |
45 | - `feature` Improved Pagination functionality
46 | - `change` Added `distance` parameter to `searchMedia()` method (thanks @jonathanwkelly)
47 |
48 | **Instagram 2.0 alpha 1 - 28/05/2012**
49 |
50 | - `feature` Added Pagination method
51 | - `feature` Added User Relationship endpoints
52 | - `feature` Added scope parameter table for the `getLoginUrl()` method
53 |
54 | **Instagram 1.5 - 31/01/2012**
55 |
56 | - `release` Second master version
57 | - `feature` Added Tag endpoints
58 | - `change` Edited the "Get started" example
59 | - `change` Now you can pass the `getOAuthToken()` object directly into `setAccessToken()`
60 |
61 | **Instagram 1.0 - 20/11/2011**
62 |
63 | - `release` First public release
64 | - `feature` Added sample App with documented code
65 | - `update` New detailed documentation
66 |
67 | **Instagram 0.8 - 16/11/2011**
68 |
69 | - `release` First inital released version
70 | - `feature` Initialize the class with a config array or string (see example)
71 |
72 | **Instagram 0.5 - 12/11/2011**
73 |
74 | - `release` Beta version
75 | - `update` Small documentation
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | /example export-ignore
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.lock
3 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: psr2
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | CONTRIBUTING
2 | ============
3 |
4 | Contributions are welcome, and are accepted via pull requests. Please review these guidelines before submitting any pull requests.
5 |
6 | ## Guidelines
7 |
8 | * Please follow the [PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) and [PHP-FIG Naming Conventions](https://github.com/php-fig/fig-standards/blob/master/bylaws/002-psr-naming-conventions.md).
9 | * Remember that we follow [SemVer](http://semver.org). If you are changing the behaviour, or the public api, you may need to update the docs.
10 | * Send a coherent commit history, making sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash](http://git-scm.com/book/en/Git-Tools-Rewriting-History) them before submitting.
11 | * You may also need to [rebase](http://git-scm.com/book/en/Git-Branching-Rebasing) to avoid merge conflicts.
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011-2015, Christian Metz
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 | * Neither the name of the organisation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9 |
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #  Instagram PHP API V2
2 |
3 | > **Note:** On the 17 Nov 2015 [Instagram](http://developers.instagram.com/post/133424514006/instagram-platform-update) made [changes to their API ](https://instagram.com/developer/changelog/). Apps created before Nov 17, 2015 wont be affected until Jun 2016. Apps created on or after Nov 17 2015 will require to use their updated API. Please note that this library doesn't yet support their new updates. For more information, please see [#182](https://github.com/cosenary/Instagram-PHP-API/issues/182).
4 |
5 | A PHP wrapper for the Instagram API. Feedback or bug reports are appreciated.
6 |
7 | [](https://packagist.org/packages/cosenary/instagram)
8 | [](https://packagist.org/packages/cosenary/instagram)
9 | [](https://packagist.org/packages/cosenary/instagram)
10 |
11 | > [Composer](#installation) package available.
12 | > Supports [Instagram Video](#instagram-videos) and [Signed Header](#signed-header).
13 |
14 | ## Requirements
15 |
16 | - PHP 5.3 or higher
17 | - cURL
18 | - Registered Instagram App
19 |
20 | ## Get started
21 |
22 | To use the Instagram API you have to register yourself as a developer at the [Instagram Developer Platform](http://instagr.am/developer/register/) and create an application. Take a look at the [uri guidelines](#samples-for-redirect-urls) before registering a redirect URI. You will receive your `client_id` and `client_secret`.
23 |
24 | ---
25 |
26 | Please note that Instagram mainly refers to »Clients« instead of »Apps«. So »Client ID« and »Client Secret« are the same as »App Key« and »App Secret«.
27 |
28 | ---
29 |
30 | > A good place to get started is the [example project](example/README.md).
31 |
32 | ### Installation
33 |
34 | I strongly advice using [Composer](https://getcomposer.org) to keep updates as smooth as possible.
35 |
36 | ```
37 | $ composer require cosenary/instagram
38 | ```
39 |
40 | ### Initialize the class
41 |
42 | ```php
43 | use MetzWeb\Instagram\Instagram;
44 |
45 | $instagram = new Instagram(array(
46 | 'apiKey' => 'YOUR_APP_KEY',
47 | 'apiSecret' => 'YOUR_APP_SECRET',
48 | 'apiCallback' => 'YOUR_APP_CALLBACK'
49 | ));
50 |
51 | echo "Login with Instagram";
52 | ```
53 |
54 | ### Authenticate user (OAuth2)
55 |
56 | ```php
57 | // grab OAuth callback code
58 | $code = $_GET['code'];
59 | $data = $instagram->getOAuthToken($code);
60 |
61 | echo 'Your username is: ' . $data->user->username;
62 | ```
63 |
64 | ### Get user likes
65 |
66 | ```php
67 | // set user access token
68 | $instagram->setAccessToken($data);
69 |
70 | // get all user likes
71 | $likes = $instagram->getUserLikes();
72 |
73 | // take a look at the API response
74 | echo '
';
75 | print_r($likes);
76 | echo '';
77 | ```
78 |
79 | **All methods return the API data `json_decode()` - so you can directly access the data.**
80 |
81 | ## Available methods
82 |
83 | ### Setup Instagram
84 |
85 | `new Instagram(/);`
86 |
87 | `array` if you want to authenticate a user and access its data:
88 |
89 | ```php
90 | new Instagram(array(
91 | 'apiKey' => 'YOUR_APP_KEY',
92 | 'apiSecret' => 'YOUR_APP_SECRET',
93 | 'apiCallback' => 'YOUR_APP_CALLBACK'
94 | ));
95 | ```
96 |
97 | `string` if you *only* want to access public data:
98 |
99 | ```php
100 | new Instagram('YOUR_APP_KEY');
101 | ```
102 |
103 | ### Get login URL
104 |
105 | `getLoginUrl()`
106 |
107 | ```php
108 | getLoginUrl(array(
109 | 'basic',
110 | 'likes'
111 | ));
112 | ```
113 |
114 | **Optional scope parameters:**
115 |
116 |
117 |
118 | Scope |
119 | Legend |
120 | Methods |
121 |
122 |
123 | basic |
124 | to use all user related methods [default] |
125 | getUser() , getUserFeed() , getUserFollower() etc. |
126 |
127 |
128 | relationships |
129 | to follow and unfollow users |
130 | modifyRelationship() |
131 |
132 |
133 | likes |
134 | to like and unlike items |
135 | getMediaLikes() , likeMedia() , deleteLikedMedia() |
136 |
137 |
138 | comments |
139 | to create or delete comments |
140 | getMediaComments() , addMediaComment() , deleteMediaComment() |
141 |
142 |
143 |
144 | ### Get OAuth token
145 |
146 | `getOAuthToken($code, /)`
147 |
148 | `true` : Returns only the OAuth token
149 | `false` *[default]* : Returns OAuth token and profile data of the authenticated user
150 |
151 | ### Set / Get access token
152 |
153 | - Set the access token, for further method calls: `setAccessToken($token)`
154 | - Get the access token, if you want to store it for later usage: `getAccessToken()`
155 |
156 | ### User methods
157 |
158 | **Public methods**
159 |
160 | - `getUser($id)`
161 | - `searchUser($name, <$limit>)`
162 | - `getUserMedia($id, <$limit>)`
163 |
164 | **Authenticated methods**
165 |
166 | - `getUser()`
167 | - `getUserLikes(<$limit>)`
168 | - `getUserFeed(<$limit>)`
169 | - `getUserMedia(<$id>, <$limit>)`
170 | - if an `$id` isn't defined or equals `'self'`, it returns the media of the logged in user
171 |
172 | > [Sample responses of the User Endpoints.](http://instagram.com/developer/endpoints/users/)
173 |
174 | ### Relationship methods
175 |
176 | **Authenticated methods**
177 |
178 | - `getUserFollows($id, <$limit>)`
179 | - `getUserFollower($id, <$limit>)`
180 | - `getUserRelationship($id)`
181 | - `modifyRelationship($action, $user)`
182 | - `$action` : Action command (follow / unfollow / block / unblock / approve / deny)
183 | - `$user` : Target user id
184 |
185 | ```php
186 | // Follow the user with the ID 1574083
187 | $instagram->modifyRelationship('follow', 1574083);
188 | ```
189 |
190 | ---
191 |
192 | Please note that the `modifyRelationship()` method requires the `relationships` [scope](#get-login-url).
193 |
194 | ---
195 |
196 | > [Sample responses of the Relationship Endpoints.](http://instagram.com/developer/endpoints/relationships/)
197 |
198 | ### Media methods
199 |
200 | **Public methods**
201 |
202 | - `getMedia($id)`
203 | - authenticated users receive the info, whether the queried media is liked
204 | - `getPopularMedia()`
205 | - `searchMedia($lat, $lng, <$distance>, <$minTimestamp>, <$maxTimestamp>)`
206 | - `$lat` and `$lng` are coordinates and have to be floats like: `48.145441892290336`,`11.568603515625`
207 | - `$distance` : Radial distance in meter (default is 1km = 1000, max. is 5km = 5000)
208 | - `$minTimestamp` : All media returned will be taken *later* than this timestamp (default: 5 days ago)
209 | - `$maxTimestamp` : All media returned will be taken *earlier* than this timestamp (default: now)
210 |
211 | > [Sample responses of the Media Endpoints.](http://instagram.com/developer/endpoints/media/)
212 |
213 | ### Comment methods
214 |
215 | **Public methods**
216 |
217 | - `getMediaComments($id)`
218 |
219 | **Authenticated methods**
220 |
221 | - `addMediaComment($id, $text)`
222 | - **restricted access:** please email `apidevelopers[at]instagram.com` for access
223 | - `deleteMediaComment($id, $commentID)`
224 | - the comment must be authored by the authenticated user
225 |
226 | ---
227 |
228 | Please note that the authenticated methods require the `comments` [scope](#get-login-url).
229 |
230 | ---
231 |
232 | > [Sample responses of the Comment Endpoints.](http://instagram.com/developer/endpoints/comments/)
233 |
234 | ### Tag methods
235 |
236 | **Public methods**
237 |
238 | - `getTag($name)`
239 | - `getTagMedia($name)`
240 | - `searchTags($name)`
241 |
242 | > [Sample responses of the Tag Endpoints.](http://instagram.com/developer/endpoints/tags/)
243 |
244 | ### Likes methods
245 |
246 | **Authenticated methods**
247 |
248 | - `getMediaLikes($id)`
249 | - `likeMedia($id)`
250 | - `deleteLikedMedia($id)`
251 |
252 | > How to like a Media: [Example usage](https://gist.github.com/3287237)
253 | > [Sample responses of the Likes Endpoints.](http://instagram.com/developer/endpoints/likes/)
254 |
255 | All `<...>` parameters are optional. If the limit is undefined, all available results will be returned.
256 |
257 | ## Instagram videos
258 |
259 | Instagram entries are marked with a `type` attribute (`image` or `video`), that allows you to identify videos.
260 |
261 | An example of how to embed Instagram videos by using [Video.js](http://www.videojs.com), can be found in the `/example` folder.
262 |
263 | ---
264 |
265 | **Please note:** Instagram currently doesn't allow to filter videos.
266 |
267 | ---
268 |
269 | ## Signed Header
270 |
271 | In order to prevent that your access tokens gets stolen, Instagram recommends to sign your requests with a hash of your API secret, the called endpoint and parameters.
272 |
273 | 1. Activate ["Enforce Signed Header"](http://instagram.com/developer/clients/manage/) in your Instagram client settings.
274 | 2. Enable the signed-header in your Instagram class:
275 |
276 | ```php
277 | $instagram->setSignedHeader(true);
278 | ```
279 |
280 | 3. You are good to go! Now, all your requests will be secured with a signed header.
281 |
282 | Go into more detail about how it works in the [Instagram API Docs](http://instagram.com/developer/restrict-api-requests/#enforce-signed-header).
283 |
284 | ## Pagination
285 |
286 | Each endpoint has a maximum range of results, so increasing the `limit` parameter above the limit won't help (e.g. `getUserMedia()` has a limit of 90).
287 |
288 | That's the point where the "pagination" feature comes into play.
289 | Simply pass an object into the `pagination()` method and receive your next dataset:
290 |
291 | ```php
292 | $photos = $instagram->getTagMedia('kitten');
293 |
294 | $result = $instagram->pagination($photos);
295 | ```
296 |
297 | Iteration with `do-while` loop.
298 |
299 | ## Samples for redirect URLs
300 |
301 |
302 |
303 | Registered Redirect URI |
304 | Redirect URI sent to /authorize |
305 | Valid? |
306 |
307 |
308 | http://yourcallback.com/ |
309 | http://yourcallback.com/ |
310 | yes |
311 |
312 |
313 | http://yourcallback.com/ |
314 | http://yourcallback.com/?this=that |
315 | yes |
316 |
317 |
318 | http://yourcallback.com/?this=that |
319 | http://yourcallback.com/ |
320 | no |
321 |
322 |
323 | http://yourcallback.com/?this=that |
324 | http://yourcallback.com/?this=that&another=true |
325 | yes |
326 |
327 |
328 | http://yourcallback.com/?this=that |
329 | http://yourcallback.com/?another=true&this=that |
330 | no |
331 |
332 |
333 | http://yourcallback.com/callback |
334 | http://yourcallback.com/ |
335 | no |
336 |
337 |
338 | http://yourcallback.com/callback |
339 | http://yourcallback.com/callback/?type=mobile |
340 | yes |
341 |
342 |
343 |
344 | > If you need further information about an endpoint, take a look at the [Instagram API docs](http://instagram.com/developer/authentication/).
345 |
346 | ## Example App
347 |
348 | 
349 |
350 | This example project, located in the `example/` folder, helps you to get started.
351 | The code is well documented and takes you through all required steps of the OAuth2 process.
352 | Credit for the awesome Instagram icons goes to [Ricardo de Zoete Pro](http://dribbble.com/RZDESIGN).
353 |
354 | #### More examples and tutorials:
355 |
356 | - [User likes](https://gist.github.com/cosenary/3287237)
357 | - [Follow user](https://gist.github.com/cosenary/8322459)
358 | - [User follower](https://gist.github.com/cosenary/7267139)
359 | - [Load more button](https://gist.github.com/cosenary/2975779)
360 | - [User most recent media](https://gist.github.com/cosenary/1711218)
361 | - [Instagram login](https://gist.github.com/cosenary/8803601)
362 | - [Instagram signup (9lessons tutorial)](http://www.9lessons.info/2012/05/login-with-instagram-php.html)
363 | - [Laravel Wrapper](https://github.com/vinkla/instagram)
364 |
365 | > Let me know if you have to share a code example, too.
366 |
367 | ## Changelog
368 |
369 | Please see the [changelog file](CHANGELOG.md) for more information.
370 |
371 | ## Credits
372 |
373 | Copyright (c) 2011-2015 - Programmed by Christian Metz
374 |
375 | Released under the [BSD License](LICENSE).
376 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cosenary/instagram",
3 | "type": "library",
4 | "description": "An easy-to-use PHP Class for accessing Instagram's API.",
5 | "keywords": ["instagram", "api"],
6 | "homepage": "https://github.com/cosenary/Instagram-PHP-API",
7 | "license": "BSD",
8 | "authors": [
9 | {
10 | "name": "Christian Metz",
11 | "email": "christian-metz1@gmx.net",
12 | "homepage": "http://metzweb.net"
13 | }
14 | ],
15 | "require": {
16 | "php": ">=5.3.0",
17 | "ext-curl": "*"
18 | },
19 | "autoload": {
20 | "psr-4": {
21 | "MetzWeb\\Instagram\\": "src"
22 | }
23 | },
24 | "extra": {
25 | "branch-alias": {
26 | "dev-master": "2.4-dev"
27 | }
28 | },
29 | "minimum-stability": "dev",
30 | "prefer-stable": true
31 | }
32 |
--------------------------------------------------------------------------------
/src/Instagram.php:
--------------------------------------------------------------------------------
1 | setApiKey($config['apiKey']);
104 | $this->setApiSecret($config['apiSecret']);
105 | $this->setApiCallback($config['apiCallback']);
106 | } elseif (is_string($config)) {
107 | // if you only want to access public data
108 | $this->setApiKey($config);
109 | } else {
110 | throw new InstagramException('Error: __construct() - Configuration data is missing.');
111 | }
112 | }
113 |
114 | /**
115 | * Generates the OAuth login URL.
116 | *
117 | * @param string[] $scopes Requesting additional permissions
118 | *
119 | * @return string Instagram OAuth login URL
120 | *
121 | * @throws \MetzWeb\Instagram\InstagramException
122 | */
123 | public function getLoginUrl($scopes = array('basic'))
124 | {
125 | if (is_array($scopes) && count(array_intersect($scopes, $this->_scopes)) === count($scopes)) {
126 | return self::API_OAUTH_URL . '?client_id=' . $this->getApiKey() . '&redirect_uri=' . urlencode($this->getApiCallback()) . '&scope=' . implode('+',
127 | $scopes) . '&response_type=code';
128 | }
129 |
130 | throw new InstagramException("Error: getLoginUrl() - The parameter isn't an array or invalid scope permissions used.");
131 | }
132 |
133 | /**
134 | * Search for a user.
135 | *
136 | * @param string $name Instagram username
137 | * @param int $limit Limit of returned results
138 | *
139 | * @return mixed
140 | */
141 | public function searchUser($name, $limit = 0)
142 | {
143 | $params = array();
144 |
145 | $params['q'] = $name;
146 | if ($limit > 0) {
147 | $params['count'] = $limit;
148 | }
149 |
150 | return $this->_makeCall('users/search', false, $params);
151 | }
152 |
153 | /**
154 | * Get user info.
155 | *
156 | * @param int $id Instagram user ID
157 | *
158 | * @return mixed
159 | */
160 | public function getUser($id = 0)
161 | {
162 | $auth = false;
163 |
164 | if ($id === 0 && isset($this->_accesstoken)) {
165 | $id = 'self';
166 | $auth = true;
167 | }
168 |
169 | return $this->_makeCall('users/' . $id, $auth);
170 | }
171 |
172 | /**
173 | * Get user activity feed.
174 | *
175 | * @param int $limit Limit of returned results
176 | *
177 | * @return mixed
178 | */
179 | public function getUserFeed($limit = 0)
180 | {
181 | $params = array();
182 | if ($limit > 0) {
183 | $params['count'] = $limit;
184 | }
185 |
186 | return $this->_makeCall('users/self/feed', true, $params);
187 | }
188 |
189 | /**
190 | * Get user recent media.
191 | *
192 | * @param int|string $id Instagram user ID
193 | * @param int $limit Limit of returned results
194 | *
195 | * @return mixed
196 | */
197 | public function getUserMedia($id = 'self', $limit = 0)
198 | {
199 | $params = array();
200 |
201 | if ($limit > 0) {
202 | $params['count'] = $limit;
203 | }
204 |
205 | return $this->_makeCall('users/' . $id . '/media/recent', strlen($this->getAccessToken()), $params);
206 | }
207 |
208 | /**
209 | * Get the liked photos of a user.
210 | *
211 | * @param int $limit Limit of returned results
212 | *
213 | * @return mixed
214 | */
215 | public function getUserLikes($limit = 0)
216 | {
217 | $params = array();
218 |
219 | if ($limit > 0) {
220 | $params['count'] = $limit;
221 | }
222 |
223 | return $this->_makeCall('users/self/media/liked', true, $params);
224 | }
225 |
226 | /**
227 | * Get the list of users this user follows
228 | *
229 | * @param int|string $id Instagram user ID.
230 | * @param int $limit Limit of returned results
231 | *
232 | * @return mixed
233 | */
234 | public function getUserFollows($id = 'self', $limit = 0)
235 | {
236 | $params = array();
237 |
238 | if ($limit > 0) {
239 | $params['count'] = $limit;
240 | }
241 |
242 | return $this->_makeCall('users/' . $id . '/follows', true, $params);
243 | }
244 |
245 | /**
246 | * Get the list of users this user is followed by.
247 | *
248 | * @param int|string $id Instagram user ID
249 | * @param int $limit Limit of returned results
250 | *
251 | * @return mixed
252 | */
253 | public function getUserFollower($id = 'self', $limit = 0)
254 | {
255 | $params = array();
256 |
257 | if ($limit > 0) {
258 | $params['count'] = $limit;
259 | }
260 |
261 | return $this->_makeCall('users/' . $id . '/followed-by', true, $params);
262 | }
263 |
264 | /**
265 | * Get information about a relationship to another user.
266 | *
267 | * @param int $id Instagram user ID
268 | *
269 | * @return mixed
270 | */
271 | public function getUserRelationship($id)
272 | {
273 | return $this->_makeCall('users/' . $id . '/relationship', true);
274 | }
275 |
276 | /**
277 | * Get the value of X-RateLimit-Remaining header field.
278 | *
279 | * @return int X-RateLimit-Remaining API calls left within 1 hour
280 | */
281 | public function getRateLimit()
282 | {
283 | return $this->_xRateLimitRemaining;
284 | }
285 |
286 | /**
287 | * Modify the relationship between the current user and the target user.
288 | *
289 | * @param string $action Action command (follow/unfollow/block/unblock/approve/deny)
290 | * @param int $user Target user ID
291 | *
292 | * @return mixed
293 | *
294 | * @throws \MetzWeb\Instagram\InstagramException
295 | */
296 | public function modifyRelationship($action, $user)
297 | {
298 | if (in_array($action, $this->_actions) && isset($user)) {
299 | return $this->_makeCall('users/' . $user . '/relationship', true, array('action' => $action), 'POST');
300 | }
301 |
302 | throw new InstagramException('Error: modifyRelationship() | This method requires an action command and the target user id.');
303 | }
304 |
305 | /**
306 | * Search media by its location.
307 | *
308 | * @param float $lat Latitude of the center search coordinate
309 | * @param float $lng Longitude of the center search coordinate
310 | * @param int $distance Distance in metres (default is 1km (distance=1000), max. is 5km)
311 | * @param long $minTimestamp Media taken later than this timestamp (default: 5 days ago)
312 | * @param long $maxTimestamp Media taken earlier than this timestamp (default: now)
313 | *
314 | * @return mixed
315 | */
316 | public function searchMedia($lat, $lng, $distance = 1000, $minTimestamp = null, $maxTimestamp = null)
317 | {
318 | return $this->_makeCall('media/search', false, array(
319 | 'lat' => $lat,
320 | 'lng' => $lng,
321 | 'distance' => $distance,
322 | 'min_timestamp' => $minTimestamp,
323 | 'max_timestamp' => $maxTimestamp
324 | ));
325 | }
326 |
327 | /**
328 | * Get media by its id.
329 | *
330 | * @param int $id Instagram media ID
331 | *
332 | * @return mixed
333 | */
334 | public function getMedia($id)
335 | {
336 | return $this->_makeCall('media/' . $id, isset($this->_accesstoken));
337 | }
338 |
339 | /**
340 | * Get the most popular media.
341 | *
342 | * @return mixed
343 | */
344 | public function getPopularMedia()
345 | {
346 | return $this->_makeCall('media/popular');
347 | }
348 |
349 | /**
350 | * Search for tags by name.
351 | *
352 | * @param string $name Valid tag name
353 | *
354 | * @return mixed
355 | */
356 | public function searchTags($name)
357 | {
358 | return $this->_makeCall('tags/search', false, array('q' => $name));
359 | }
360 |
361 | /**
362 | * Get info about a tag
363 | *
364 | * @param string $name Valid tag name
365 | *
366 | * @return mixed
367 | */
368 | public function getTag($name)
369 | {
370 | return $this->_makeCall('tags/' . $name);
371 | }
372 |
373 | /**
374 | * Get a recently tagged media.
375 | *
376 | * @param string $name Valid tag name
377 | * @param int $limit Limit of returned results
378 | *
379 | * @return mixed
380 | */
381 | public function getTagMedia($name, $limit = 0)
382 | {
383 | $params = array();
384 |
385 | if ($limit > 0) {
386 | $params['count'] = $limit;
387 | }
388 |
389 | return $this->_makeCall('tags/' . $name . '/media/recent', false, $params);
390 | }
391 |
392 | /**
393 | * Get a list of users who have liked this media.
394 | *
395 | * @param int $id Instagram media ID
396 | *
397 | * @return mixed
398 | */
399 | public function getMediaLikes($id)
400 | {
401 | return $this->_makeCall('media/' . $id . '/likes', true);
402 | }
403 |
404 | /**
405 | * Get a list of comments for this media.
406 | *
407 | * @param int $id Instagram media ID
408 | *
409 | * @return mixed
410 | */
411 | public function getMediaComments($id)
412 | {
413 | return $this->_makeCall('media/' . $id . '/comments', false);
414 | }
415 |
416 | /**
417 | * Add a comment on a media.
418 | *
419 | * @param int $id Instagram media ID
420 | * @param string $text Comment content
421 | *
422 | * @return mixed
423 | */
424 | public function addMediaComment($id, $text)
425 | {
426 | return $this->_makeCall('media/' . $id . '/comments', true, array('text' => $text), 'POST');
427 | }
428 |
429 | /**
430 | * Remove user comment on a media.
431 | *
432 | * @param int $id Instagram media ID
433 | * @param string $commentID User comment ID
434 | *
435 | * @return mixed
436 | */
437 | public function deleteMediaComment($id, $commentID)
438 | {
439 | return $this->_makeCall('media/' . $id . '/comments/' . $commentID, true, null, 'DELETE');
440 | }
441 |
442 | /**
443 | * Set user like on a media.
444 | *
445 | * @param int $id Instagram media ID
446 | *
447 | * @return mixed
448 | */
449 | public function likeMedia($id)
450 | {
451 | return $this->_makeCall('media/' . $id . '/likes', true, null, 'POST');
452 | }
453 |
454 | /**
455 | * Remove user like on a media.
456 | *
457 | * @param int $id Instagram media ID
458 | *
459 | * @return mixed
460 | */
461 | public function deleteLikedMedia($id)
462 | {
463 | return $this->_makeCall('media/' . $id . '/likes', true, null, 'DELETE');
464 | }
465 |
466 | /**
467 | * Get information about a location.
468 | *
469 | * @param int $id Instagram location ID
470 | *
471 | * @return mixed
472 | */
473 | public function getLocation($id)
474 | {
475 | return $this->_makeCall('locations/' . $id, false);
476 | }
477 |
478 | /**
479 | * Get recent media from a given location.
480 | *
481 | * @param int $id Instagram location ID
482 | *
483 | * @return mixed
484 | */
485 | public function getLocationMedia($id)
486 | {
487 | return $this->_makeCall('locations/' . $id . '/media/recent', false);
488 | }
489 |
490 | /**
491 | * Get recent media from a given location.
492 | *
493 | * @param float $lat Latitude of the center search coordinate
494 | * @param float $lng Longitude of the center search coordinate
495 | * @param int $distance Distance in meter (max. distance: 5km = 5000)
496 | *
497 | * @return mixed
498 | */
499 | public function searchLocation($lat, $lng, $distance = 1000)
500 | {
501 | return $this->_makeCall('locations/search', false, array('lat' => $lat, 'lng' => $lng, 'distance' => $distance));
502 | }
503 |
504 | /**
505 | * Pagination feature.
506 | *
507 | * @param object $obj Instagram object returned by a method
508 | * @param int $limit Limit of returned results
509 | *
510 | * @return mixed
511 | *
512 | * @throws \MetzWeb\Instagram\InstagramException
513 | */
514 | public function pagination($obj, $limit = 0)
515 | {
516 | if (is_object($obj) && !is_null($obj->pagination)) {
517 | if (!isset($obj->pagination->next_url)) {
518 | return;
519 | }
520 |
521 | $apiCall = explode('?', $obj->pagination->next_url);
522 |
523 | if (count($apiCall) < 2) {
524 | return;
525 | }
526 |
527 | $function = str_replace(self::API_URL, '', $apiCall[0]);
528 |
529 | $auth = (strpos($apiCall[1], 'access_token') !== false);
530 |
531 | if (isset($obj->pagination->next_max_id)) {
532 | return $this->_makeCall($function, $auth, array('max_id' => $obj->pagination->next_max_id, 'count' => $limit));
533 | }
534 |
535 | return $this->_makeCall($function, $auth, array('cursor' => $obj->pagination->next_cursor, 'count' => $limit));
536 | }
537 |
538 | throw new InstagramException("Error: pagination() | This method doesn't support pagination.");
539 | }
540 |
541 | /**
542 | * Get the OAuth data of a user by the returned callback code.
543 | *
544 | * @param string $code OAuth2 code variable (after a successful login)
545 | * @param bool $token If it's true, only the access token will be returned
546 | *
547 | * @return mixed
548 | */
549 | public function getOAuthToken($code, $token = false)
550 | {
551 | $apiData = array(
552 | 'grant_type' => 'authorization_code',
553 | 'client_id' => $this->getApiKey(),
554 | 'client_secret' => $this->getApiSecret(),
555 | 'redirect_uri' => $this->getApiCallback(),
556 | 'code' => $code
557 | );
558 |
559 | $result = $this->_makeOAuthCall($apiData);
560 |
561 | return !$token ? $result : $result->access_token;
562 | }
563 |
564 | /**
565 | * The call operator.
566 | *
567 | * @param string $function API resource path
568 | * @param bool $auth Whether the function requires an access token
569 | * @param array $params Additional request parameters
570 | * @param string $method Request type GET|POST
571 | *
572 | * @return mixed
573 | *
574 | * @throws \MetzWeb\Instagram\InstagramException
575 | */
576 | protected function _makeCall($function, $auth = false, $params = null, $method = 'GET')
577 | {
578 | if (!$auth) {
579 | // if the call doesn't requires authentication
580 | $authMethod = '?client_id=' . $this->getApiKey();
581 | } else {
582 | // if the call needs an authenticated user
583 | if (!isset($this->_accesstoken)) {
584 | throw new InstagramException("Error: _makeCall() | $function - This method requires an authenticated users access token.");
585 | }
586 |
587 | $authMethod = '?access_token=' . $this->getAccessToken();
588 | }
589 |
590 | $paramString = null;
591 |
592 | if (isset($params) && is_array($params)) {
593 | $paramString = '&' . http_build_query($params);
594 | }
595 |
596 | $apiCall = self::API_URL . $function . $authMethod . (('GET' === $method) ? $paramString : null);
597 |
598 | // we want JSON
599 | $headerData = array('Accept: application/json');
600 |
601 | if ($this->_signedheader) {
602 | $apiCall .= (strstr($apiCall, '?') ? '&' : '?') . 'sig=' . $this->_signHeader($function, $authMethod, $params);
603 | }
604 |
605 | $ch = curl_init();
606 | curl_setopt($ch, CURLOPT_URL, $apiCall);
607 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headerData);
608 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
609 | curl_setopt($ch, CURLOPT_TIMEOUT, 90);
610 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
611 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
612 | curl_setopt($ch, CURLOPT_HEADER, true);
613 |
614 | switch ($method) {
615 | case 'POST':
616 | curl_setopt($ch, CURLOPT_POST, count($params));
617 | curl_setopt($ch, CURLOPT_POSTFIELDS, ltrim($paramString, '&'));
618 | break;
619 | case 'DELETE':
620 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
621 | break;
622 | }
623 |
624 | $jsonData = curl_exec($ch);
625 | // split header from JSON data
626 | // and assign each to a variable
627 | list($headerContent, $jsonData) = explode("\r\n\r\n", $jsonData, 2);
628 |
629 | // convert header content into an array
630 | $headers = $this->processHeaders($headerContent);
631 |
632 | // get the 'X-Ratelimit-Remaining' header value
633 | $this->_xRateLimitRemaining = $headers['X-Ratelimit-Remaining'];
634 |
635 | if (!$jsonData) {
636 | throw new InstagramException('Error: _makeCall() - cURL error: ' . curl_error($ch));
637 | }
638 |
639 | curl_close($ch);
640 |
641 | return json_decode($jsonData);
642 | }
643 |
644 | /**
645 | * The OAuth call operator.
646 | *
647 | * @param array $apiData The post API data
648 | *
649 | * @return mixed
650 | *
651 | * @throws \MetzWeb\Instagram\InstagramException
652 | */
653 | private function _makeOAuthCall($apiData)
654 | {
655 | $apiHost = self::API_OAUTH_TOKEN_URL;
656 |
657 | $ch = curl_init();
658 | curl_setopt($ch, CURLOPT_URL, $apiHost);
659 | curl_setopt($ch, CURLOPT_POST, count($apiData));
660 | curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($apiData));
661 | curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: application/json'));
662 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
663 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
664 | curl_setopt($ch, CURLOPT_TIMEOUT, 90);
665 | $jsonData = curl_exec($ch);
666 |
667 | if (!$jsonData) {
668 | throw new InstagramException('Error: _makeOAuthCall() - cURL error: ' . curl_error($ch));
669 | }
670 |
671 | curl_close($ch);
672 |
673 | return json_decode($jsonData);
674 | }
675 |
676 | /**
677 | * Sign header by using endpoint, parameters and the API secret.
678 | *
679 | * @param string
680 | * @param string
681 | * @param array
682 | *
683 | * @return string The signature
684 | */
685 | private function _signHeader($endpoint, $authMethod, $params)
686 | {
687 | if (!is_array($params)) {
688 | $params = array();
689 | }
690 | if ($authMethod) {
691 | list($key, $value) = explode('=', substr($authMethod, 1), 2);
692 | $params[$key] = $value;
693 | }
694 | $baseString = '/' . $endpoint;
695 | ksort($params);
696 | foreach ($params as $key => $value) {
697 | $baseString .= '|' . $key . '=' . $value;
698 | }
699 | $signature = hash_hmac('sha256', $baseString, $this->_apisecret, false);
700 |
701 | return $signature;
702 | }
703 |
704 | /**
705 | * Read and process response header content.
706 | *
707 | * @param array
708 | *
709 | * @return array
710 | */
711 | private function processHeaders($headerContent)
712 | {
713 | $headers = array();
714 |
715 | foreach (explode("\r\n", $headerContent) as $i => $line) {
716 | if ($i === 0) {
717 | $headers['http_code'] = $line;
718 | continue;
719 | }
720 |
721 | list($key, $value) = explode(':', $line);
722 | $headers[$key] = $value;
723 | }
724 |
725 | return $headers;
726 | }
727 |
728 | /**
729 | * Access Token Setter.
730 | *
731 | * @param object|string $data
732 | *
733 | * @return void
734 | */
735 | public function setAccessToken($data)
736 | {
737 | $token = is_object($data) ? $data->access_token : $data;
738 |
739 | $this->_accesstoken = $token;
740 | }
741 |
742 | /**
743 | * Access Token Getter.
744 | *
745 | * @return string
746 | */
747 | public function getAccessToken()
748 | {
749 | return $this->_accesstoken;
750 | }
751 |
752 | /**
753 | * API-key Setter
754 | *
755 | * @param string $apiKey
756 | *
757 | * @return void
758 | */
759 | public function setApiKey($apiKey)
760 | {
761 | $this->_apikey = $apiKey;
762 | }
763 |
764 | /**
765 | * API Key Getter
766 | *
767 | * @return string
768 | */
769 | public function getApiKey()
770 | {
771 | return $this->_apikey;
772 | }
773 |
774 | /**
775 | * API Secret Setter
776 | *
777 | * @param string $apiSecret
778 | *
779 | * @return void
780 | */
781 | public function setApiSecret($apiSecret)
782 | {
783 | $this->_apisecret = $apiSecret;
784 | }
785 |
786 | /**
787 | * API Secret Getter.
788 | *
789 | * @return string
790 | */
791 | public function getApiSecret()
792 | {
793 | return $this->_apisecret;
794 | }
795 |
796 | /**
797 | * API Callback URL Setter.
798 | *
799 | * @param string $apiCallback
800 | *
801 | * @return void
802 | */
803 | public function setApiCallback($apiCallback)
804 | {
805 | $this->_callbackurl = $apiCallback;
806 | }
807 |
808 | /**
809 | * API Callback URL Getter.
810 | *
811 | * @return string
812 | */
813 | public function getApiCallback()
814 | {
815 | return $this->_callbackurl;
816 | }
817 |
818 | /**
819 | * Enforce Signed Header.
820 | *
821 | * @param bool $signedHeader
822 | *
823 | * @return void
824 | */
825 | public function setSignedHeader($signedHeader)
826 | {
827 | $this->_signedheader = $signedHeader;
828 | }
829 | }
830 |
--------------------------------------------------------------------------------
/src/InstagramException.php:
--------------------------------------------------------------------------------
1 |