Completed
Push — master ( 2c1e48...9ea7af )
by Haridarshan
02:31
created

Instagram::generateExceptionMessage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 14
rs 9.4285
cc 2
eloc 12
nc 2
nop 1
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 mixed|integer|string|array<integer,string>
80
	*/
81
	private $x_rate_limit_remaining = 500;
82
	
83
	/*
84
	* @var \GuzzleHttp\Client $client
85
	*/
86
	private $client;
87
	
88
	/*
89
	* @var $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
		$this->client = new \GuzzleHttp\Client([
108
			'base_uri' => self::API_HOST
109
		]);
110
	}
111
	
112
	/*
113
	* Make URLs for user browser navigation.
114
	*
115
	* @param string $path
116
	* @param array  $parameters
117
	*
118
	* @return string
119
	*/
120
	public function getUrl($path, array $parameters) {			
121
		if (!isset($parameters['scope'])) {
122
			throw new \Haridarshan\Instagram\InstagramException("Missing or Invalid Scope permission used", 400);
123
		}				
124
		if (count(array_diff($parameters['scope'], $this->default_scopes)) === 0) {
125
			$this->scopes = $parameters['scope']; 
126
		} else {
127
			throw new \Haridarshan\Instagram\InstagramException("Missing or Invalid Scope permission used", 400);
128
		}
129
		$query = 'client_id='.$this->getClientId().'&redirect_uri='.urlencode($this->getCallbackUrl()).'&response_type=code&state='.$this->state;
130
		$query .= isset($this->scopes) ? '&scope='.urlencode(str_replace(",", " ", implode(",", $parameters['scope']))) : '';				
131
		return sprintf('%s%s?%s', self::API_HOST, $path, $query);
132
	}
133
	
134
	/*
135
	* Get the Oauth Access Token of a user from callback code
136
	* 
137
	* @param string $path - OAuth Access Token Path
138
	* @param string $code - Oauth2 Code returned with callback url after successfull login
139
	* @param boolean $token - true will return only access token
140
	*/
141
	public function getToken($path, $code, $token = false) {
142
		$options = array(
143
			"grant_type" => "authorization_code",
144
			"client_id" => $this->getClientId(),
145
			"client_secret" => $this->getClientSecret(),
146
			"redirect_uri" => $this->getCallbackUrl(),
147
			"code" => $code,
148
			"state" => $this->state
149
		);			
150
		$this->execute($path, $options, 'POST');				
151
		if (isset($this->response->code)) {
152
			throw new \Haridarshan\Instagram\InstagramException("returns error type: ".$this->response->error_type." message: ".$this->response->error_message, $this->response->code);
153
		}				
154
		$this->setAccessToken($this->response);				
155
		return !$token ? $this->response : $this->response->access_token;
156
	}
157
	
158
	/*
159
	* Secure API Request by using endpoint, paramters and API secret
160
	* copy from Instagram API Documentation: https://www.instagram.com/developer/secure-api-requests/
161
	* 
162
	* @param string $endpoint
163
	* @param array|string $params
164
	*
165
	* @return string (Signature)
166
	*/
167
	protected function secureRequest($endpoint, $params) {			
168
		$signature = $endpoint;
169
		ksort($params);		
170
		foreach ($params as $key => $value) {
171
			$signature .= "|$key=$value";	
172
		}		
173
		return hash_hmac('sha256', $signature, $this->getClientSecret(), false);
174
	}
175
	
176
	/* 
177
	* Method to make api requests
178
	* @return mixed
179
	*/
180
	public function request($path, array $params, $method = 'GET') {
181
		$this->isRateLimitReached();		
182
		$this->isAccessTokenPresent($path, $params);								
183
		$this->setAccessToken($params['access_token']);			
184
		$authentication_method = '?access_token='.$this->access_token;				
185
		$endpoint = self::API_VERSION.$path.(('GET' === $method) ? '?'.http_build_query($params) : $authentication_method);			
186
		$endpoint .= (strstr($endpoint, '?') ? '&' : '?').'sig='.$this->secureRequest($path, $params);				
187
		$this->execute($endpoint, $params, $method);		
188
		return $this->response;	
189
	}
190
	
191
	/*
192
	* Method to make GuzzleHttp Client Request to Instagram APIs
193
	*
194
	* @param string $endpoint
195
	* @param array|string $options in case of POST [optional]
196
	* @param string $method GET|POST
197
	*
198
	* @throws InstagramException $e
199
	*/
200
	protected function execute($endpoint, $options, $method = 'GET') {	
201
		try {	
202
			$result = $this->client->request($method, $endpoint, [
203
					'headers' => ['Accept'     => 'application/json'],
204
					'body' => $this->createBody($options, $method)
205
			]);					
206
			$this->x_rate_limit_remaining = $result->getHeader('x-ratelimit-remaining');
207
			$this->response = json_decode($result->getBody()->getContents());
208
		} catch (\GuzzleHttp\Exception\ClientException $e) {
209
			$exception_message = json_decode($this->generateExceptionMessage($e));	
210
			throw new \Haridarshan\Instagram\InstagramException(
211
				json_encode(array("Type" => $exception_message->error_type, "Message" => $exception_message->error_message)), 
212
				$exception_message->error_code, 
213
				$e
214
			);			
215
		}
216
	}
217
	
218
	/*
219
	* @param \GuzzleHttp\Exception\ClientException $ex
220
	* @return object
221
	*/
222
	private function generateExceptionMessage($ex) {
223
		$exception = json_decode($ex->getResponse()->getBody()->getContents());					
224
		$message = array();		
225
		if (isset($exception->meta)) {
226
			$message['error_type'] = $exception->meta->error_type;
227
			$message['error_message'] = $exception->meta->error_message;
228
			$message['error_code'] = $exception->meta->code;
229
		} else {
230
			$message['error_type'] = $exception->error_type;
231
			$message['error_message'] = $exception->error_message;
232
			$message['error_code'] = $exception->code;
233
		}		
234
		return json_encode($message);
235
	}
236
	
237
	/*
238
	* Create body for Guzzle client request
239
	* @param array|null|string $options
240
	* @param string $method GET|POST
241
	* @return string|mixed
242
	*/
243
	private function createBody($options, $method) {
244
		return ('GET' !== $method) ? is_array($options) ? http_build_query($options) : ltrim($options, '&') : null;
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