Completed
Push — master ( 7ff5fc...cb0fb0 )
by Haridarshan
02:32
created

Instagram::generateState()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
c 2
b 1
f 1
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
namespace Haridarshan\Instagram;
3
4
/*
5
* Instagram API class
6
*
7
* API Documentation: http://instagram.com/developer/
8
* Class Documentation: https://github.com/haridarshan/Instagram-php
9
*
10
* @author Haridarshan Gorana
11
* @since May 09, 2016
12
* @copyright Haridarshan Gorana
13
* @version 1.0
14
* @license: MIT
15
*
16
*/
17
class Instagram {
18
	/*
19
	* Library Version
20
	*/
21
	const VERSION = '1.0.1';
22
	
23
	/*
24
	* API End Point
25
	*/  
26
	const API_VERSION = 'v1';
27
	
28
	/*
29
	* API End Point
30
	*/  
31
	const API_HOST = 'https://api.instagram.com/';
32
		
33
	/*
34
	* Client Id
35
	* @var string
36
	*/
37
	private $client_id;
38
	
39
	/*
40
	* Client Secret
41
	* @var string
42
	*/
43
	private $client_secret;
44
	
45
	/*
46
	* Instagram Callback url
47
	* @var string
48
	*/
49
	private $callback_url;
50
	
51
	/*
52
	* Oauth Access Token
53
	* @var string
54
	*/
55
	private $access_token;
56
	
57
	/*
58
	* Instagram Available Scopes
59
	* @var array of strings
60
	*/
61
	private $default_scopes = array("basic", "public_content", "follower_list", "comments", "relationships", "likes");
62
	
63
	/*
64
	* User's Scope
65
	* @var array of strings
66
	*/
67
	private $scopes = array();		
68
	
69
	/*
70
	* Random string indicating the state to prevent spoofing
71
	* @var string
72
	*/
73
	private $state;
74
	
75
	/*
76
	* Remaining Rate Limit
77
	* Sandbox = 500
78
	* Live = 5000
79
	* @var integer
80
	*/
81
	private $x_rate_limit_remaining = 500;
82
	
83
	/*
84
	* @var GuzzleHttp\ClientInterface $http
85
	*/
86
	private $client;
87
	
88
	/*
89
	* @var GuzzleHttp\Psr7\Response $response
90
	*/
91
	private $response;
92
		
93
	/*
94
	* Default Constructor 
95
	* Instagram Configuration Data
96
	* @param array|object|string $config
97
	*/
98
	public function __construct($config) {		
99
		if (is_array($config)) {			
100
			$this->setClientId($config['ClientId']);
101
			$this->setClientSecret($config['ClientSecret']);
102
			$this->setCallbackUrl($config['Callback']);	
103
			$this->state = isset($config['State']) ? $config['State'] : substr(md5(rand()), 0, 7);
104
		} else {
105
			throw new \Haridarshan\Instagram\InstagramException('Invalid Instagram Configuration data', 400);			
106
		}
107
		
108
		$this->client = new \GuzzleHttp\Client([
0 ignored issues
show
Documentation Bug introduced by
It seems like new \GuzzleHttp\Client(a...ri' => self::API_HOST)) of type object<GuzzleHttp\Client> is incompatible with the declared type object<Haridarshan\Insta...leHttp\ClientInterface> of property $client.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
109
			'base_uri' => self::API_HOST
110
		]);
111
	}
112
	
113
	/*
114
	* Make URLs for user browser navigation.
115
	*
116
	* @param string $path
117
	* @param array  $parameters
118
	*
119
	* @return string
120
	*/
121
	public function getUrl($path, array $parameters) {	
122
		
123
		if (!isset($parameters['scope'])) {
124
			throw new \Haridarshan\Instagram\InstagramException("Missing or Invalid Scope permission used", 400);
125
		}
126
				
127
		if (count(array_diff($parameters['scope'], $this->default_scopes)) === 0) {
128
			$this->scopes = $parameters['scope']; 
129
		} else {
130
			throw new \Haridarshan\Instagram\InstagramException("Missing or Invalid Scope permission used", 400);
131
		}
132
133
		$query = 'client_id='.$this->getClientId().'&redirect_uri='.urlencode($this->getCallbackUrl()).'&response_type=code&state='.$this->state;
134
135
		$query .= isset($this->scopes) ? '&scope='.urlencode(str_replace(",", " ", implode(",", $parameters['scope']))) : '';
136
				
137
		return sprintf('%s%s?%s', self::API_HOST, $path, $query);
138
	}
139
	
140
	/*
141
	* Get the Oauth Access Token of a user from callback code
142
	* 
143
	* @param string $path - OAuth Access Token Path
144
	* @param string $code - Oauth2 Code returned with callback url after successfull login
145
	* @param boolean $token - true will return only access token
146
	*/
147
	public function getToken($path, $code, $token = false) {
148
		$options = array(
149
			"grant_type" => "authorization_code",
150
			"client_id" => $this->getClientId(),
151
			"client_secret" => $this->getClientSecret(),
152
			"redirect_uri" => $this->getCallbackUrl(),
153
			"code" => $code,
154
			"state" => $this->state
155
		);
156
			
157
		$this->execute($path, $options, 'POST');
158
		
159
		if (isset($this->response->code)) {
160
			throw new \Haridarshan\Instagram\InstagramException("returns error type: ".$this->response->error_type." message: ".$this->response->error_message, $this->response->code);
161
		}
162
				
163
		$this->setAccessToken($this->response);
164
				
165
		return !$token ? $this->response : $this->response->access_token;
166
	}
167
	
168
	/*
169
	* Secure API Request by using endpoint, paramters and API secret
170
	* copy from Instagram API Documentation: https://www.instagram.com/developer/secure-api-requests/
171
	* 
172
	* @param string $endpoint
173
	* @param array|string $params
174
	*
175
	* @return string (Signature)
176
	*/
177
	protected function secureRequest($endpoint, $params) {			
178
		$signature = $endpoint;
179
		ksort($params);
180
		
181
		foreach ($params as $key => $value) {
182
			$signature .= "|$key=$value";	
183
		}		
184
		return hash_hmac('sha256', $signature, $this->getClientSecret(), false);
185
	}
186
	
187
	/* 
188
	* Method to make api requests
189
	* @return mixed
190
	*/
191
	public function request($path, array $params, $method = 'GET') {
192
		$this->isRateLimitReached();
193
		
194
		$this->isAccessTokenPresent($path, $params);
195
								
196
		$this->setAccessToken($params['access_token']);	
197
		
198
		$authentication_method = '?access_token='.$this->access_token;
199
				
200
		$endpoint = self::API_VERSION.$path.(('GET' === $method) ? '?'.http_build_query($params) : $authentication_method);
201
			
202
		$endpoint .= (strstr($endpoint, '?') ? '&' : '?').'sig='.$this->secureRequest($path, $params);
203
				
204
		$this->execute($endpoint, $params, $method);
205
		return $this->response;	
206
	}
207
	
208
	/*
209
	* Method to make GuzzleHttp Client Request to Instagram APIs
210
	*
211
	* @param string $endpoint
212
	* @param array|string $options in case of POST [optional]
213
	* @param string $method GET|POST
214
	*
215
	* throws InstagramException $e
216
	*/
217
	protected function execute($endpoint, $options, $method = 'GET') {	
218
		try {	
219
			$result = $this->client->request($method, $endpoint, [
220
					'headers' => ['Accept'     => 'application/json'],
221
					'body' => ('GET' !== $method) ? is_array($options) ? http_build_query($options) : ltrim($options, '&') : null
222
			]);					
223
			$limit = $result->getHeader('x-ratelimit-remaining');
224
			$this->x_rate_limit_remaining = $limit;
225
			$this->response = json_decode($result->getBody()->getContents());
226
		} catch (\GuzzleHttp\Exception\ClientException $e) {	
227
			$exception_response = json_decode($e->getResponse()->getBody()->getContents());			
228
			
229
			if (isset($exception_response->meta)) {
230
				$error_type = $exception_response->meta->error_type;
231
				$error_message = $exception_response->meta->error_message;
232
				$error_code = $exception_response->meta->code;
233
			} else {
234
				$error_type = $exception_response->error_type;
235
				$error_message = $exception_response->error_message;
236
				$error_code = $exception_response->code;
237
			}
238
			
239
			throw new \Haridarshan\Instagram\InstagramException(
240
				json_encode(array("Type" => $error_type, "Message" => $error_message)), 
241
				$error_code, 
242
				$e
243
			);			
244
		}
245
	}
246
	
247
	/*
248
	* Setter: Client Id
249
	* @param string $clientId
250
	* @return void
251
	*/
252
	public function setClientId($clientId) {
253
		$this->client_id = $clientId;	
254
	}
255
	
256
	/*
257
	* Getter: Client Id
258
	* @return string
259
	*/
260
	public function getClientId() {
261
		return $this->client_id;	
262
	}
263
	
264
	/*
265
	* Setter: Client Secret
266
	* @param string $secret
267
	* @return void
268
	*/
269
	public function setClientSecret($secret) {
270
		$this->client_secret = $secret;	
271
	}
272
	
273
	/*
274
	* Getter: Client Id
275
	* @return string
276
	*/
277
	public function getClientSecret() {
278
		return $this->client_secret;	
279
	}
280
	
281
	/*
282
	* Setter: Callback Url
283
	* @param string $url
284
	* @return void
285
	*/
286
	public function setCallbackUrl($url) {
287
		$this->callback_url = $url;	
288
	}
289
	
290
	/*
291
	* Getter: Callback Url
292
	* @return string
293
	*/
294
	public function getCallbackUrl() {
295
		return $this->callback_url;	
296
	}
297
		
298
	/*
299
	* Setter: User Access Token
300
	* @param object|string $data
301
	* @return void
302
	*/
303
	private function setAccessToken($data) {		
304
		$token = is_object($data) ? $data->access_token : $data;
305
		$this->access_token = $token;
306
	}
307
	
308
	/*
309
	* Getter: User Access Token
310
	* @return string
311
	*/
312
	public function getAccessToken() {
313
		return isset($this->access_token) ? $this->access_token : null;
314
	}
315
		
316
	/*
317
	* Get a string containing the version of the library.
318
	* @return string
319
	*/
320
	public function getLibraryVersion() {
321
		return self::VERSION;
322
	}
323
	
324
	/* 
325
	* Get state value
326
	* @return string | mixed
327
	*/
328
	public function getState() {
329
		return $this->state;	
330
	}
331
	
332
	/*
333
	* Check whether api rate limit is reached or not
334
	* @throws \Haridarshan\Instagram\InstagramException
335
	*/
336
	private function isRateLimitReached() {
337
		if (!$this->x_rate_limit_remaining) {
338
			throw new \Haridarshan\Instagram\InstagramException("You have reached Instagram API Rate Limit", 400);
339
		}
340
	}
341
	
342
	/*
343
	* Check whether access token is present or not
344
	* @throws \Haridarshan\Instagram\InstagramException
345
	*/
346
	private function isAccessTokenPresent($api, array $params) {
347
		if (!isset($params['access_token'])) {
348
			throw new \Haridarshan\Instagram\InstagramException("$api - api requires an authenticated users access token.", 400);
349
		}	
350
	}
351
}
352