PluginProbe
Photonic Gallery & Lightbox for Flickr, SmugMug & Others / 2.40
Photonic Gallery & Lightbox for Flickr, SmugMug & Others v2.40
3.36 3.35 3.34 3.33 2.19 2.20 2.21 2.22 2.23 2.24 2.25 2.26 2.27 2.28 2.29 2.30 2.31 2.32 2.33 2.34 2.40 2.41 2.42 2.43 2.44 All 141 releases
photonic / Modules / Instagram.php

Instagram.php in Photonic Gallery & Lightbox for Flickr, SmugMug & Others 2.40, at Modules/Instagram.php

457 lines 16.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Photonic_Plugin\Modules;
3
4 use WP_Error;
5
6 require_once('OAuth2.php');
7 require_once('Level_One_Module.php');
8
9 /**
10 * Module to handle Instagram. Instagram uses OAuth2 authentication, and authentication is mandatory to display content.
11 * Instagram first issues a short-lived token. This is exchanged for a long-lived token right on an external site.
12 * The long-lived token is valid for 60 days, though, to be safe, Photonic swaps it out for a new token if there are less then
13 * 30 days of validity left on it.
14 *
15 */
16 class Instagram extends OAuth2 implements Level_One_Module {
17 var $response_type, $scope, $cached_token, $field_list, $token_valid;
18 private static $instance = null;
19
20 protected function __construct() {
21 parent::__construct();
22 global $photonic_instagram_disable_title_link, $photonic_instagram_access_token;
23 $this->provider = 'instagram';
24 $this->oauth_version = '2.0';
25 $this->response_type = 'token';
26 $this->scope = 'basic';
27 $this->api_key = 'not-required-but-not-empty';
28 $this->api_secret = 'not-required-but-not-empty';
29 $this->token = $photonic_instagram_access_token; // Used in the Authentication page to see if a token is set
30 $this->access_token = $photonic_instagram_access_token; // Used everywhere else. This is updated later based on the cached value in memory
31 $this->field_list = 'id,caption,media_type,media_url,permalink,thumbnail_url,timestamp,username';
32
33 $this->link_lightbox_title = empty($photonic_instagram_disable_title_link);
34 $this->doc_links = [
35 'general' => 'https://aquoid.com/plugins/photonic/instagram/',
36 ];
37 $this->authenticate($photonic_instagram_access_token);
38
39 $this->cached_token = $this->get_cached_token();
40 if (!empty($this->cached_token)) {
41 $this->access_token = $this->cached_token['oauth_token'];
42 }
43 }
44
45 public static function get_instance() {
46 if (self::$instance == null) {
47 self::$instance = new Instagram();
48 }
49 return self::$instance;
50 }
51
52 /**
53 * Main function that fetches the images associated with the shortcode.
54 *
55 * @param array $attr
56 * @param array $gallery_meta
57 * @return string
58 */
59 public function get_gallery_images($attr = [], &$gallery_meta = []) {
60 global $photonic_instagram_main_size, $photonic_instagram_tile_size, $photonic_instagram_media, $photonic_thumbnail_style;
61 $this->gallery_index++;
62 $this->push_to_stack('Get Gallery Images');
63 $attr = array_merge(
64 $this->common_parameters,
65 [
66 // Common overrides ...
67 'caption' => 'title',
68 'thumb_size' => 75,
69 'main_size' => $photonic_instagram_main_size,
70 'tile_size' => $photonic_instagram_tile_size,
71
72 // Instagram-specific ...
73 'count' => 25,
74 'layout' => (empty($photonic_thumbnail_style) || $photonic_thumbnail_style == 'square') ? 'random' : $photonic_thumbnail_style,
75 'media' => $photonic_instagram_media,
76 'embed_type' => 'embed',
77 'carousel_handling' => 'expand',
78 ], $attr);
79
80 if ($attr['tile_size'] == 'same') {
81 $attr['tile_size'] = $attr['main_size'];
82 }
83 $attr = array_map('trim', $attr);
84
85 extract($attr);
86
87 if (empty($this->token) || empty($this->cached_token) || !$this->token_valid) {
88 return $this->error(esc_html__("Instagram Access Token not valid. Please reauthenticate.", 'photonic'));
89 }
90
91 if (empty($user_id)) {
92 $user_id = 'me';
93 }
94
95 $base_url = 'https://graph.instagram.com/';
96
97 $display_what = 'media';
98 if (!empty($media_id)) {// Trumps all else. A single photo will be shown.
99 $id_format = preg_match('/^[A-Z][A-Z0-9_-]+/i', $media_id) ? 'old' : 'new';
100 if ($id_format == 'old') {
101 $query_url = 'http://api.instagram.com/oembed?url='.urlencode('http://instagram.com/p/'.$media_id.'/');
102 $attr['embed_type'] = 'embed';
103 }
104 else {
105 $query_url = $base_url.$media_id.'?fields='.$this->field_list;
106 }
107 $display_what = 'single-media';
108 }
109 else if (!empty($carousel)) {
110 $query_url = $base_url.$carousel.'/children?fields=id,media_type,media_url,permalink,thumbnail_url';
111 $display_what = 'carousel';
112 }
113 else if (!empty($user_id)) {
114 $query_url = $base_url.$user_id.'/media?fields='.$this->field_list; // Doesn't matter what the other values are. User's photos will be shown.
115 }
116 else {
117 if (empty($view)) {
118 return $this->error(sprintf(esc_html__('The %s parameter has to be defined.', 'photonic'), '<code>view</code>'));
119 }
120 else {
121 return $this->error(sprintf(esc_html__('Malformed shortcode. Either %1$s or %2$s or %3$s has to be defined.', 'photonic'),
122 '<code>media_id</code>', '<code>carousel</code>', '<code>view</code>'));
123 }
124 }
125
126 if (isset($count)) {
127 $query_url = add_query_arg(['limit' => $count], $query_url);
128 }
129
130 if (isset($after)) {
131 $query_url = add_query_arg(['after' => $after], $query_url);
132 }
133
134 $ret = $this->make_call($query_url, $display_what, $attr);
135 $this->pop_from_stack();
136 return $this->finalize_markup($ret, $attr).$this->get_stack_markup();
137 }
138
139 protected function make_call($query_url, $display_what, &$shortcode_attr) {
140 $this->push_to_stack("Make call $query_url");
141 $ret = '';
142
143 if (!empty($this->cached_token) && $this->token_valid) {
144 $query = add_query_arg(['access_token' => $this->access_token], $query_url);
145 }
146 else {
147 $this->pop_from_stack(); // Make call, error encountered
148 return $this->error(esc_html__("Instagram Access Token not valid. Please reauthenticate.", 'photonic'));
149 }
150
151 $this->push_to_stack('Send request');
152 $response = wp_remote_request($query, [
153 'sslverify' => PHOTONIC_SSL_VERIFY,
154 ]);
155 $this->pop_from_stack(); // Send request
156
157 $this->push_to_stack('Process response');
158 if (!is_wp_error($response)) {
159 if (isset($response['response']) && isset($response['response']['code'])) {
160 if ($response['response']['code'] == 200) {
161 $body = json_decode($response['body']);
162
163 if (isset($body->paging) && isset($body->paging->next)&& isset($body->paging->cursors) && isset($body->paging->cursors->after)) {
164 $shortcode_attr['after'] = $body->paging->cursors->after;
165 if (empty($shortcode_attr['more'])) {
166 $shortcode_attr['more'] = esc_html__('More', 'photonic');
167 }
168 }
169 else {
170 if (isset($shortcode_attr['after'])) {
171 unset($shortcode_attr['after']);
172 }
173 }
174
175 if (isset($body->data) && $display_what != 'single-media') {
176 $data = $body->data;
177 $ret .= $this->process_media($data, $shortcode_attr);
178 }
179 else if ($display_what == 'single-media') {
180 if (!empty($body->html)) { // Old-style id, can only operate in "embed" mode. Directly show the "embed"
181 $ret .= $body->html;
182 }
183 else if (!empty($body->permalink)) { // New style id; need to re-execute the call with the permalink to embed, or display single-photo markup for "integrate"
184 if ($shortcode_attr['embed_type'] == 'embed') {
185 $query_url = 'http://api.instagram.com/oembed?url='.urlencode($body->permalink);
186 $embed_response = wp_remote_request($query_url);
187 if (!is_wp_error($embed_response) && isset($embed_response['response']) &&
188 isset($embed_response['response']['code']) && $embed_response['response']['code'] == 200) {
189 $embed_response = json_decode($embed_response['body']);
190 if (!empty($embed_response->html)) {
191 $ret .= $embed_response->html;
192 }
193 else {
194 $this->pop_from_stack(); // 'Process response'
195 $this->pop_from_stack(); // 'Make call'
196 return $this->error(esc_html__('No data returned. Unknown error', 'photonic'));
197 }
198 }
199 else {
200 $this->pop_from_stack(); // 'Process response'
201 $this->pop_from_stack(); // 'Make call'
202 return $this->error(esc_html__('No data returned. Unknown error', 'photonic'));
203 }
204 }
205 else {
206 $this->pop_from_stack(); // 'Process response'
207 $this->pop_from_stack(); // 'Make call'
208 return $this->generate_single_photo_markup([
209 'src' => $body->media_url,
210 'href' => $body->permalink,
211 'title' => '',
212 'caption' => isset($body->caption) ? $body->caption : '',
213 ]
214 );
215 }
216 }
217 }
218 else {
219 $this->pop_from_stack(); // 'Process response'
220 $this->pop_from_stack(); // 'Make call'
221 return $this->error(esc_html__('No data returned. Unknown error', 'photonic'));
222 }
223 }
224 else if (isset($response['body'])) {
225 $body = json_decode($response['body']);
226 if (isset($body->meta) && isset($body->meta->error_message)) {
227 $this->pop_from_stack(); // 'Process response'
228 $this->pop_from_stack(); // 'Make call'
229 return $body->meta->error_message;
230 }
231 else {
232 $this->pop_from_stack(); // 'Process response'
233 $this->pop_from_stack(); // 'Make call'
234 return $this->error(esc_html__('Unknown error', 'photonic'));
235 }
236 }
237 else if (isset($response['response']['message'])) {
238 $this->pop_from_stack(); // 'Process response'
239 $this->pop_from_stack(); // 'Make call'
240 return $this->error($response['response']['message']);
241 }
242 else {
243 $this->pop_from_stack(); // 'Process response'
244 $this->pop_from_stack(); // 'Make call'
245 return $this->error(esc_html__('Unknown error', 'photonic'));
246 }
247 }
248 }
249 else {
250 $this->pop_from_stack(); // 'Process response'
251 $this->pop_from_stack(); // 'Make call'
252 return $this->wp_error_message($response);
253 }
254
255 $this->pop_from_stack(); // 'Process response'
256 $this->pop_from_stack(); // 'Make call'
257 return $ret;
258 }
259
260 function process_media($data, $short_code) {
261 global $photonic_instagram_photos_per_row_constraint, $photonic_instagram_photos_constrain_by_padding, $photonic_instagram_photos_constrain_by_count, $photonic_instagram_photo_title_display;
262
263 $photo_objects = $this->build_level_1_objects($data, $short_code);
264 $row_constraints = ['constraint-type' => $photonic_instagram_photos_per_row_constraint, 'padding' => $photonic_instagram_photos_constrain_by_padding, 'count' => $photonic_instagram_photos_constrain_by_count];
265
266 $ret = $this->layout_gallery($photo_objects,
267 [
268 'title_position' => $photonic_instagram_photo_title_display,
269 'row_constraints' => $row_constraints,
270 'parent' => 'stream',
271 'level_2_meta' => ['end' => 0, 'total' => empty($short_code['after']) ? 0 : $short_code['count']],
272 ],
273 $short_code,
274 1
275 );
276 return $ret;
277 }
278
279 function build_level_1_objects($data, $short_code, $module_parameters = [], $options = []) {
280 $level_1_objects = [];
281
282 $media = explode(',', $short_code['media']);
283 $videos_ok = in_array('videos', $media) || in_array('all', $media);
284 $photos_ok = in_array('photos', $media) || in_array('all', $media);
285
286 foreach ($data as $photo) {
287 if (isset($photo->media_type) && (((strtolower($photo->media_type) == 'image' || strtolower($photo->media_type) == 'carousel_album') && $photos_ok) || (strtolower($photo->media_type) == 'video' && $videos_ok)) && isset($photo->media_url)) {
288 if (strtolower($photo->media_type) == 'carousel_album' && $short_code['carousel_handling'] == 'expand') {
289 $query_url = 'https://graph.instagram.com/'.$photo->id.'/children?fields=id,media_type,media_url,permalink,thumbnail_url&access_token='.$this->access_token;
290 $carousel_contents = $this->get_carousel_contents($query_url);
291 $carousel_caption = empty($photo->caption) ? null : $photo->caption;
292
293 foreach ($carousel_contents as $carousel_photo) {
294 $this->process_single_item($carousel_photo, $level_1_objects, $photos_ok, $videos_ok, $carousel_photo->permalink, $carousel_photo->id, $carousel_caption); // Carousel photos have no caption, so use the post caption
295 }
296 }
297 else {
298 $this->process_single_item($photo, $level_1_objects, $photos_ok, $videos_ok, $photo->permalink, $photo->id);
299 }
300 }
301 }
302 return $level_1_objects;
303 }
304
305 function refresh_access_token($current_token, $save) {
306 $token = [];
307 $soon = $this->is_token_expiring_soon(30);
308
309 if (is_null($soon)) {
310 // No token exists. Do nothing, because the API secret is needed and authentication will need the back-end.
311 // The call to is_token_expired will return true, prompting validation.
312 }
313 else {
314 $photonic_authentication = get_option('photonic_authentication');
315 $instagram_token = $photonic_authentication['instagram'];
316 if ($soon > 0) {
317 if (!empty($current_token)) {
318 $response = wp_remote_request('https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token='.$current_token, [
319 'sslverify' => PHOTONIC_SSL_VERIFY,
320 ]);
321
322 if (!is_wp_error($response)) {
323 $token = $this->parse_token($response);
324 if (!empty($token)) {
325 $token['client_id'] = $instagram_token['client_id'];
326 $user_response = wp_remote_request('https://graph.instagram.com/me?fields=id,username&access_token='.$token['oauth_token'], [
327 'sslverify' => PHOTONIC_SSL_VERIFY,
328 ]);
329 if (!is_wp_error($user_response)) {
330 $user_response = $user_response['body'];
331 $user_response = json_decode($user_response);
332 $token['user'] = $user_response->username;
333 }
334 }
335 if ($save) {
336 $this->save_token($token);
337 }
338 }
339 }
340 }
341 else if ($soon < 0) {
342 // No token exists. Do nothing, because the API secret is needed and authentication will need the back-end.
343 // The call to is_token_expired will return true, prompting validation.
344 }
345 else {
346 $token = $instagram_token;
347 }
348 }
349 return $token;
350 }
351
352 function is_token_expired($token) {
353 if (empty($token)) {
354 return true;
355 }
356
357 if (!isset($token['oauth_token']) || !isset($token['oauth_token_created']) || !isset($token['oauth_token_expires'])) {
358 return true;
359 }
360
361 $current = time();
362 if ($token['oauth_token_created'] + $token['oauth_token_expires'] < $current) {
363 return true;
364 }
365
366 return false;
367 }
368
369 /**
370 * @param $photo
371 * @param array $level_1_objects
372 * @param $photos_ok
373 * @param $videos_ok
374 * @param null $photo_link
375 * @param null $photo_id
376 * @param null $photo_caption
377 */
378 private function process_single_item($photo, array &$level_1_objects, $photos_ok, $videos_ok, $photo_link = null, $photo_id = null, $photo_caption = null) {
379 if (isset($photo->media_type) && (((strtolower($photo->media_type) == 'image' || strtolower($photo->media_type) == 'carousel_album') && $photos_ok) || (strtolower($photo->media_type) == 'video' && $videos_ok)) && isset($photo->media_url)) {
380 $photo_object = [];
381 $main_image = $photo->media_url;
382 if (!empty($photo->thumbnail_url)) {
383 $photo_object['thumbnail'] = $photo->thumbnail_url;
384 $photo_object['tile_image'] = $photo->thumbnail_url;
385 }
386 else {
387 $photo_object['thumbnail'] = $main_image;
388 $photo_object['tile_image'] = $main_image;
389 }
390
391 $photo_object['main_image'] = $main_image;
392
393 if (isset($photo->caption)) {
394 $photo_object['title'] = esc_attr($photo->caption);
395 }
396 else if (!empty($photo_caption)) {
397 $photo_object['title'] = esc_attr($photo_caption);
398 }
399 else {
400 $photo_object['title'] = '';
401 }
402
403 $photo_object['alt_title'] = $photo_object['title'];
404 $photo_object['description'] = $photo_object['title'];
405 $photo_object['main_page'] = $photo_link;
406 $photo_object['id'] = $photo_id;
407
408 if (strtolower($photo->media_type) == 'video') {
409 $photo_object['video'] = $photo->media_url;
410 $parse = wp_parse_url($photo_object['video']);
411 $parse = explode('.', $parse['path']);
412 $photo_object['mime'] = 'video/' . $parse[count($parse) - 1];
413 }
414 $photo_object['provider'] = $this->provider;
415
416 $level_1_objects[] = $photo_object;
417 }
418 }
419
420 public function authentication_url() {
421 // TODO: Implement authentication_url() method.
422 }
423
424 public function access_token_url() {
425 // TODO: Implement access_token_url() method.
426 }
427
428 protected function set_token_validity($validity) {
429 $this->token_valid = $validity;
430 }
431
432 /**
433 * @param string $query_url
434 * @return array|WP_Error
435 */
436 private function get_carousel_contents($query_url) {
437 $this->push_to_stack('Fetch carousel');
438 $response = wp_remote_request($query_url, [
439 'sslverify' => PHOTONIC_SSL_VERIFY,
440 ]);
441
442 $carousel_contents = [];
443
444 if (!is_wp_error($response)) {
445 if (isset($response['response']) && isset($response['response']['code'])) {
446 if ($response['response']['code'] == 200) {
447 $body = json_decode($response['body']);
448 if (isset($body->data)) {
449 $carousel_contents = $body->data;
450 }
451 }
452 }
453 }
454 $this->pop_from_stack(); // Fetch carousel
455 return $carousel_contents;
456 }
457 }