├── 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']);
132 | $this->setApiSecret($config['apiSecret']);
133 | $this->setApiCallback($config['apiCallback']);
134 | } elseif (is_string($config)) {
135 | // if you only want to access public data
136 | $this->setApiKey($config);
137 | } else {
138 | throw new InstagramException('Error: __construct() - Configuration data is missing.');
139 | }
140 | }
141 |
142 | /**
143 | * Generates the OAuth login URL.
144 | *
145 | * @param string[] $scopes Requesting additional permissions
146 | *
147 | * @return string Instagram OAuth login URL
148 | *
149 | * @throws \MetzWeb\Instagram\InstagramException
150 | */
151 | public function getLoginUrl($scopes = array('basic'))
152 | {
153 | if (is_array($scopes) && count(array_intersect($scopes, $this->_scopes)) === count($scopes)) {
154 | return self::API_OAUTH_URL . '?client_id=' . $this->getApiKey() . '&redirect_uri=' . urlencode($this->getApiCallback()) . '&scope=' . implode('+',
155 | $scopes) . '&response_type=code';
156 | }
157 |
158 | throw new InstagramException("Error: getLoginUrl() - The parameter isn't an array or invalid scope permissions used.");
159 | }
160 |
161 | /**
162 | * Search for a user.
163 | *
164 | * @param string $name Instagram username
165 | * @param int $limit Limit of returned results
166 | *
167 | * @return mixed
168 | */
169 | public function searchUser($name, $limit = 0)
170 | {
171 | $params = array();
172 |
173 | $params['q'] = $name;
174 | if ($limit > 0) {
175 | $params['count'] = $limit;
176 | }
177 |
178 | return $this->_makeCall('users/search', $params);
179 | }
180 |
181 | /**
182 | * Get user info.
183 | *
184 | * @param int $id Instagram user ID
185 | *
186 | * @return mixed
187 | */
188 | public function getUser($id = 0)
189 | {
190 | if ($id === 0) {
191 | $id = 'self';
192 | }
193 |
194 | return $this->_makeCall('users/' . $id);
195 | }
196 |
197 | /**
198 | * Get user recent media.
199 | *
200 | * @param int|string $id Instagram user ID
201 | * @param int $limit Limit of returned results
202 | * @param int $min_id Return media later than this min_id
203 | * @param int $max_id Return media earlier than this max_id
204 | *
205 | * @return mixed
206 | */
207 | public function getUserMedia($id = 'self', $limit = 0, $min_id = null, $max_id = null)
208 | {
209 | $params = array();
210 |
211 | if ($limit > 0) {
212 | $params['count'] = $limit;
213 | }
214 | if (isset($min_id)) {
215 | $params['min_id'] = $min_id;
216 | }
217 | if (isset($max_id)) {
218 | $params['max_id'] = $max_id;
219 | }
220 |
221 | return $this->_makeCall('users/' . $id . '/media/recent', $params);
222 | }
223 |
224 | /**
225 | * Get the liked photos of a user.
226 | *
227 | * @param int $limit Limit of returned results
228 | * @param int $max_like_id Return media liked before this id
229 | *
230 | * @return mixed
231 | */
232 | public function getUserLikes($limit = 0, $max_like_id = null)
233 | {
234 | $params = array();
235 |
236 | if ($limit > 0) {
237 | $params['count'] = $limit;
238 | }
239 | if (isset($max_id)) {
240 | $params['max_like_id'] = $max_like_id;
241 | }
242 |
243 | return $this->_makeCall('users/self/media/liked', $params);
244 | }
245 |
246 | /**
247 | * DEPRECATED
248 | * Get the list of users this user follows
249 | *
250 | * @param int|string $id Instagram user ID.
251 | * @param int $limit Limit of returned results
252 | *
253 | * @return void
254 | */
255 | public function getUserFollows($id = 'self', $limit = 0)
256 | {
257 | return $this->getFollows($id, $limit);
258 | }
259 |
260 | /**
261 | * Get the list of users the authenticated user follows.
262 | *
263 | * @return mixed
264 | */
265 | public function getFollows($id = 'self', $limit = 0)
266 | {
267 | $params = array();
268 |
269 | if ($limit > 0) {
270 | $params['count'] = $limit;
271 | }
272 |
273 | return $this->_makeCall('users/' . $id . '/follows', $params);
274 | }
275 |
276 | /**
277 | * DEPRECATED
278 | * Get the list of users this user is followed by.
279 | *
280 | * @param int|string $id Instagram user ID
281 | * @param int $limit Limit of returned results
282 | *
283 | * @return void
284 | */
285 | public function getUserFollower($id = 'self', $limit = 0)
286 | {
287 | return $this->getFollower($id, $limit);
288 | }
289 |
290 | /**
291 | * Get the list of users this user is followed by.
292 | *
293 | * @return mixed
294 | */
295 | public function getFollower($id = 'self', $limit = 0)
296 | {
297 | $params = array();
298 |
299 | if ($limit > 0) {
300 | $params['count'] = $limit;
301 | }
302 |
303 | return $this->_makeCall('users/' . $id . '/followed-by', $params);
304 | }
305 |
306 | /**
307 | * Get information about a relationship to another user.
308 | *
309 | * @param int $id Instagram user ID
310 | *
311 | * @return mixed
312 | */
313 | public function getUserRelationship($id)
314 | {
315 | return $this->_makeCall('users/' . $id . '/relationship');
316 | }
317 |
318 | /**
319 | * Get the value of X-RateLimit-Remaining header field.
320 | *
321 | * @return int X-RateLimit-Remaining API calls left within 1 hour
322 | */
323 | public function getRateLimit()
324 | {
325 | return $this->_xRateLimitRemaining;
326 | }
327 |
328 | /**
329 | * Modify the relationship between the current user and the target user.
330 | *
331 | * @param string $action Action command (follow/unfollow/approve/ignore)
332 | * @param int $user Target user ID
333 | *
334 | * @return mixed
335 | *
336 | * @throws \MetzWeb\Instagram\InstagramException
337 | */
338 | public function modifyRelationship($action, $user)
339 | {
340 | if (in_array($action, $this->_actions) && isset($user)) {
341 | return $this->_makeCall('users/' . $user . '/relationship', array('action' => $action), 'POST');
342 | }
343 |
344 | throw new InstagramException('Error: modifyRelationship() | This method requires an action command and the target user id.');
345 | }
346 |
347 | /**
348 | * Search media by its location.
349 | *
350 | * @param float $lat Latitude of the center search coordinate
351 | * @param float $lng Longitude of the center search coordinate
352 | * @param int $distance Distance in metres (default is 1km (distance=1000), max. is 5km)
353 | *
354 | * @return mixed
355 | */
356 | public function searchMedia($lat, $lng, $distance = 1000)
357 | {
358 | return $this->_makeCall('media/search', array(
359 | 'lat' => $lat,
360 | 'lng' => $lng,
361 | 'distance' => $distance
362 | ));
363 | }
364 |
365 | /**
366 | * Get media by its id.
367 | *
368 | * @param int $id Instagram media ID
369 | *
370 | * @return mixed
371 | */
372 | public function getMedia($id)
373 | {
374 | return $this->_makeCall('media/' . $id);
375 | }
376 |
377 | /**
378 | * Search for tags by name.
379 | *
380 | * @param string $name Valid tag name
381 | *
382 | * @return mixed
383 | */
384 | public function searchTags($name)
385 | {
386 | return $this->_makeCall('tags/search', array('q' => $name));
387 | }
388 |
389 | /**
390 | * Get info about a tag
391 | *
392 | * @param string $name Valid tag name
393 | *
394 | * @return mixed
395 | */
396 | public function getTag($name)
397 | {
398 | return $this->_makeCall('tags/' . $name);
399 | }
400 |
401 | /**
402 | * Get a recently tagged media.
403 | *
404 | * @param string $name Valid tag name
405 | * @param int $limit Limit of returned results
406 | * @param int $min_tag_id Return media before this min_tag_id
407 | * @param int $max_tag_id Return media after this max_tag_id
408 | *
409 | * @return mixed
410 | */
411 | public function getTagMedia($name, $limit = 0, $min_tag_id = null, $max_tag_id = null)
412 | {
413 | $params = array();
414 |
415 | if ($limit > 0) {
416 | $params['count'] = $limit;
417 | }
418 | if (isset($min_tag_id)) {
419 | $params['min_tag_id'] = $min_tag_id;
420 | }
421 | if (isset($max_tag_id)) {
422 | $params['max_tag_id'] = $max_tag_id;
423 | }
424 |
425 | return $this->_makeCall('tags/' . $name . '/media/recent', $params);
426 | }
427 |
428 | /**
429 | * Get a list of users who have liked this media.
430 | *
431 | * @param int $id Instagram media ID
432 | *
433 | * @return mixed
434 | */
435 | public function getMediaLikes($id)
436 | {
437 | return $this->_makeCall('media/' . $id . '/likes');
438 | }
439 |
440 | /**
441 | * Get a list of comments for this media.
442 | *
443 | * @param int $id Instagram media ID
444 | *
445 | * @return mixed
446 | */
447 | public function getMediaComments($id)
448 | {
449 | return $this->_makeCall('media/' . $id . '/comments');
450 | }
451 |
452 | /**
453 | * Add a comment on a media.
454 | *
455 | * @param int $id Instagram media ID
456 | * @param string $text Comment content
457 | *
458 | * @return mixed
459 | */
460 | public function addMediaComment($id, $text)
461 | {
462 | return $this->_makeCall('media/' . $id . '/comments', array('text' => $text), 'POST');
463 | }
464 |
465 | /**
466 | * Remove user comment on a media.
467 | *
468 | * @param int $id Instagram media ID
469 | * @param string $commentID User comment ID
470 | *
471 | * @return mixed
472 | */
473 | public function deleteMediaComment($id, $commentID)
474 | {
475 | return $this->_makeCall('media/' . $id . '/comments/' . $commentID, null, 'DELETE');
476 | }
477 |
478 | /**
479 | * Set user like on a media.
480 | *
481 | * @param int $id Instagram media ID
482 | *
483 | * @return mixed
484 | */
485 | public function likeMedia($id)
486 | {
487 | return $this->_makeCall('media/' . $id . '/likes', null, 'POST');
488 | }
489 |
490 | /**
491 | * Remove user like on a media.
492 | *
493 | * @param int $id Instagram media ID
494 | *
495 | * @return mixed
496 | */
497 | public function deleteLikedMedia($id)
498 | {
499 | return $this->_makeCall('media/' . $id . '/likes', null, 'DELETE');
500 | }
501 |
502 | /**
503 | * Get information about a location.
504 | *
505 | * @param int $id Instagram location ID
506 | *
507 | * @return mixed
508 | */
509 | public function getLocation($id)
510 | {
511 | return $this->_makeCall('locations/' . $id);
512 | }
513 |
514 | /**
515 | * Get recent media from a given location.
516 | *
517 | * @param int $id Instagram location ID
518 | * @param int $min_id Return media before this min_id
519 | * @param int $max_id Return media after this max_id
520 | *
521 | * @return mixed
522 | */
523 | public function getLocationMedia($id, $min_id = null, $max_id = null)
524 | {
525 | $params = array();
526 |
527 | if (isset($min_id)) {
528 | $params['min_id'] = $min_id;
529 | }
530 | if (isset($max_id)) {
531 | $params['max_id'] = $max_id;
532 | }
533 |
534 | return $this->_makeCall('locations/' . $id . '/media/recent', $params);
535 | }
536 |
537 | /**
538 | * Get recent media from a given location.
539 | *
540 | * @param float $lat Latitude of the center search coordinate
541 | * @param float $lng Longitude of the center search coordinate
542 | * @param int $distance Distance in meter (max. distance: 5km = 5000)
543 | * @param int $facebook_places_id Returns a location mapped off of a
544 | * Facebook places id. If used, a Foursquare
545 | * id and lat, lng are not required.
546 | * @param int $foursquare_id Returns a location mapped off of a foursquare v2
547 | * api location id. If used, you are not
548 | * required to use lat and lng.
549 | *
550 | * @return mixed
551 | */
552 | public function searchLocation($lat, $lng, $distance = 1000, $facebook_places_id = null, $foursquare_id = null)
553 | {
554 | $params['lat'] = $lat;
555 | $params['lng'] = $lng;
556 | $params['distance'] = $distance;
557 | if (isset($facebook_places_id)) {
558 | $params['facebook_places_id'] = $facebook_places_id;
559 | }
560 | if (isset($foursquare_id)) {
561 | $params['foursquare_id'] = $foursquare_id;
562 | }
563 |
564 | return $this->_makeCall('locations/search', $params);
565 | }
566 |
567 | /**
568 | * Pagination feature.
569 | *
570 | * @param object $obj Instagram object returned by a method
571 | * @param int $limit Limit of returned results
572 | *
573 | * @return mixed
574 | *
575 | * @throws \MetzWeb\Instagram\InstagramException
576 | */
577 | public function pagination($obj, $limit = 0)
578 | {
579 | if (is_object($obj) && !is_null($obj->pagination)) {
580 | if (!isset($obj->pagination->next_url)) {
581 | return;
582 | }
583 |
584 | $apiCall = explode('?', $obj->pagination->next_url);
585 |
586 | if (count($apiCall) < 2) {
587 | return;
588 | }
589 |
590 | $function = str_replace(self::API_URL, '', $apiCall[0]);
591 | $count = ($limit) ? $limit : count($obj->data);
592 |
593 | if (isset($obj->pagination->next_max_tag_id)) {
594 | return $this->_makeCall($function, array('max_tag_id' => $obj->pagination->next_max_tag_id, 'count' => $count));
595 | }
596 |
597 | return $this->_makeCall($function, array('next_max_id' => $obj->pagination->next_max_id, 'count' => $count));
598 | }
599 | throw new InstagramException("Error: pagination() | This method doesn't support pagination.");
600 | }
601 |
602 | /**
603 | * Get the OAuth data of a user by the returned callback code.
604 | *
605 | * @param string $code OAuth2 code variable (after a successful login)
606 | * @param bool $token If it's true, only the access token will be returned
607 | *
608 | * @return mixed
609 | */
610 | public function getOAuthToken($code, $token = false)
611 | {
612 | $apiData = array(
613 | 'grant_type' => 'authorization_code',
614 | 'client_id' => $this->getApiKey(),
615 | 'client_secret' => $this->getApiSecret(),
616 | 'redirect_uri' => $this->getApiCallback(),
617 | 'code' => $code
618 | );
619 |
620 | $result = $this->_makeOAuthCall($apiData);
621 |
622 | return !$token ? $result : $result->access_token;
623 | }
624 |
625 | /**
626 | * The call operator.
627 | *
628 | * @param string $function API resource path
629 | * @param array $params Additional request parameters
630 | * @param string $method Request type GET|POST
631 | *
632 | * @return mixed
633 | *
634 | * @throws \MetzWeb\Instagram\InstagramException
635 | */
636 | protected function _makeCall($function, $params = null, $method = 'GET')
637 | {
638 | if (!isset($this->_accesstoken)) {
639 | throw new InstagramException("Error: _makeCall() | $function - This method requires an authenticated users access token.");
640 | }
641 |
642 | $authMethod = '?access_token=' . $this->getAccessToken();
643 |
644 | $paramString = null;
645 |
646 | if (isset($params) && is_array($params)) {
647 | $paramString = '&' . http_build_query($params);
648 | }
649 |
650 | $apiCall = self::API_URL . $function . $authMethod . (('GET' === $method) ? $paramString : null);
651 |
652 | // we want JSON
653 | $headerData = array('Accept: application/json');
654 |
655 | if ($this->_signedheader) {
656 | $apiCall .= (strstr($apiCall, '?') ? '&' : '?') . 'sig=' . $this->_signHeader($function, $authMethod, $params);
657 | }
658 |
659 | $ch = curl_init();
660 |
661 | if ($this->_proxyServer) {
662 | curl_setopt($ch, CURLOPT_PROXY, $this->_proxyServer);
663 | curl_setopt($ch, CURLOPT_PROXYPORT, $this->_proxyPort);
664 | if ($this->_proxyUser) {
665 | $proxyauth = $this->_proxyUser . ':' . $this->_proxyPwd;
666 | curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyauth);
667 | }
668 | }
669 |
670 | curl_setopt($ch, CURLOPT_URL, $apiCall);
671 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headerData);
672 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
673 | curl_setopt($ch, CURLOPT_TIMEOUT, 90);
674 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
675 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
676 | curl_setopt($ch, CURLOPT_HEADER, true);
677 |
678 | switch ($method) {
679 | case 'POST':
680 | curl_setopt($ch, CURLOPT_POST, count($params));
681 | curl_setopt($ch, CURLOPT_POSTFIELDS, ltrim($paramString, '&'));
682 | break;
683 | case 'DELETE':
684 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
685 | break;
686 | }
687 |
688 | $jsonData = curl_exec($ch);
689 |
690 | if (!$jsonData) {
691 | throw new InstagramException('Error: _makeCall() - cURL error: ' . curl_error($ch));
692 | }
693 |
694 | // split header from JSON data
695 | // and assign each to a variable
696 | list($headerContent, $jsonData) = explode("\r\n\r\n", $jsonData, 2);
697 |
698 | // convert header content into an array
699 | $headers = $this->processHeaders($headerContent);
700 |
701 | // get the 'X-Ratelimit-Remaining' header value
702 | if (isset($headers['X-Ratelimit-Remaining'])) {
703 | $this->_xRateLimitRemaining = trim($headers['X-Ratelimit-Remaining']);
704 | }
705 |
706 | curl_close($ch);
707 |
708 | return json_decode($jsonData);
709 | }
710 |
711 | /**
712 | * The OAuth call operator.
713 | *
714 | * @param array $apiData The post API data
715 | *
716 | * @return mixed
717 | *
718 | * @throws \MetzWeb\Instagram\InstagramException
719 | */
720 | private function _makeOAuthCall($apiData)
721 | {
722 | $apiHost = self::API_OAUTH_TOKEN_URL;
723 |
724 | $ch = curl_init();
725 | curl_setopt($ch, CURLOPT_URL, $apiHost);
726 |
727 | if ($this->_proxyServer) {
728 | curl_setopt($ch, CURLOPT_PROXY, $this->_proxyServer);
729 | curl_setopt($ch, CURLOPT_PROXYPORT, $this->_proxyPort);
730 | if ($this->_proxyUser) {
731 | $proxyAuth = $this->_proxyUser . ':' . $this->_proxyPwd;
732 | curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyAuth);
733 | }
734 | }
735 |
736 | curl_setopt($ch, CURLOPT_POST, count($apiData));
737 | curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($apiData));
738 | curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: application/json'));
739 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
740 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
741 | curl_setopt($ch, CURLOPT_TIMEOUT, 90);
742 | $jsonData = curl_exec($ch);
743 |
744 | if (!$jsonData) {
745 | throw new InstagramException('Error: _makeOAuthCall() - cURL error: ' . curl_error($ch));
746 | }
747 |
748 | curl_close($ch);
749 |
750 | return json_decode($jsonData);
751 | }
752 |
753 | /**
754 | * Sign header by using endpoint, parameters and the API secret.
755 | *
756 | * @param string
757 | * @param string
758 | * @param array
759 | *
760 | * @return string The signature
761 | */
762 | private function _signHeader($endpoint, $authMethod, $params)
763 | {
764 | if (!is_array($params)) {
765 | $params = array();
766 | }
767 | if ($authMethod) {
768 | list($key, $value) = explode('=', substr($authMethod, 1), 2);
769 | $params[$key] = $value;
770 | }
771 | $baseString = '/' . $endpoint;
772 | ksort($params);
773 | foreach ($params as $key => $value) {
774 | $baseString .= '|' . $key . '=' . $value;
775 | }
776 | $signature = hash_hmac('sha256', $baseString, $this->_apisecret, false);
777 |
778 | return $signature;
779 | }
780 |
781 | /**
782 | * Read and process response header content.
783 | *
784 | * @param array
785 | *
786 | * @return array
787 | */
788 | private function processHeaders($headerContent)
789 | {
790 | $headers = array();
791 |
792 | foreach (explode("\r\n", $headerContent) as $i => $line) {
793 | if ($i === 0) {
794 | $headers['http_code'] = $line;
795 | continue;
796 | }
797 |
798 | list($key, $value) = explode(':', $line);
799 | $headers[$key] = $value;
800 | }
801 |
802 | return $headers;
803 | }
804 |
805 | /**
806 | * Access Token Setter.
807 | *
808 | * @param object|string $data
809 | *
810 | * @return void
811 | */
812 | public function setAccessToken($data)
813 | {
814 | $token = is_object($data) ? $data->access_token : $data;
815 |
816 | $this->_accesstoken = $token;
817 | }
818 |
819 | /**
820 | * Access Token Getter.
821 | *
822 | * @return string
823 | */
824 | public function getAccessToken()
825 | {
826 | return $this->_accesstoken;
827 | }
828 |
829 | /**
830 | * API-key Setter
831 | *
832 | * @param string $apiKey
833 | *
834 | * @return void
835 | */
836 | public function setApiKey($apiKey)
837 | {
838 | $this->_apikey = $apiKey;
839 | }
840 |
841 | /**
842 | * API Key Getter
843 | *
844 | * @return string
845 | */
846 | public function getApiKey()
847 | {
848 | return $this->_apikey;
849 | }
850 |
851 | /**
852 | * API Secret Setter
853 | *
854 | * @param string $apiSecret
855 | *
856 | * @return void
857 | */
858 | public function setApiSecret($apiSecret)
859 | {
860 | $this->_apisecret = $apiSecret;
861 | }
862 |
863 | /**
864 | * API Secret Getter.
865 | *
866 | * @return string
867 | */
868 | public function getApiSecret()
869 | {
870 | return $this->_apisecret;
871 | }
872 |
873 | /**
874 | * API Callback URL Setter.
875 | *
876 | * @param string $apiCallback
877 | *
878 | * @return void
879 | */
880 | public function setApiCallback($apiCallback)
881 | {
882 | $this->_callbackurl = $apiCallback;
883 | }
884 |
885 | /**
886 | * API Callback URL Getter.
887 | *
888 | * @return string
889 | */
890 | public function getApiCallback()
891 | {
892 | return $this->_callbackurl;
893 | }
894 |
895 | /**
896 | * Enforce Signed Header.
897 | *
898 | * @param bool $signedHeader
899 | *
900 | * @return void
901 | */
902 | public function setSignedHeader($signedHeader)
903 | {
904 | $this->_signedheader = $signedHeader;
905 | }
906 |
907 | /**
908 | * Get the proxy server.
909 | *
910 | * @return string
911 | */
912 | public function getProxyServer() {
913 | return $this->_proxyServer;
914 | }
915 |
916 | /**
917 | * Set the proxy server.
918 | *
919 | * @param string $server
920 | *
921 | * @return void
922 | */
923 | public function setProxyServer($server) {
924 | $this->_proxyServer = $server;
925 | }
926 |
927 | /**
928 | * Get the proxy server username.
929 | *
930 | * @return string
931 | */
932 | public function getProxyUser(){
933 | return $this->_proxyUser;
934 | }
935 |
936 | /**
937 | * Get the proxy server password.
938 | *
939 | * @return string
940 | */
941 | public function getProxyPwd(){
942 | return $this->_proxyPwd;
943 | }
944 |
945 | /**
946 | * Set the proxy username.
947 | *
948 | * @param string $user
949 | *
950 | * @return void
951 | */
952 | public function setProxyUser($user){
953 | $this->_proxyUser = $user;
954 | }
955 |
956 | /**
957 | * Set the proxy password.
958 | *
959 | * @param string $pwd
960 | *
961 | * @return void
962 | */
963 | public function setProxyPwd($pwd){
964 | $this->_proxyPwd = $pwd;
965 | }
966 |
967 | /**
968 | * Get proxy port.
969 | *
970 | * @return int
971 | */
972 | public function getProxyPort() {
973 | return $this->_proxyPort;
974 | }
975 |
976 | /**
977 | * Set the proxy port.
978 | *
979 | * @param int $port
980 | *
981 | * @return void
982 | */
983 | public function setProxyPort($port){
984 | $this->_proxyPort = $port;
985 | }
986 | }
987 |
988 |
--------------------------------------------------------------------------------
/src/InstagramException.php:
--------------------------------------------------------------------------------
1 |