PluginProbe ʕ •ᴥ•ʔ
Smash Balloon Social Photo Feed – Easy Social Feeds Plugin / 6.11.0
Smash Balloon Social Photo Feed – Easy Social Feeds Plugin v6.11.0
6.11.0 1.11.1 1.11.2 1.11.3 1.12 1.12.1 1.12.2 1.2 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.10 1.3.11 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.6.1 1.4.6.2 1.4.7 1.4.8 1.4.9 1.5 1.5.1 1.6 1.6.1 1.6.2 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9 1.9.1 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.2 2.2.1 2.2.2 2.4 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5 2.5.1 2.5.2 2.5.3 2.5.4 2.6 2.6.1 2.6.2 2.7 2.8 2.8.1 2.8.2 2.9 2.9.1 2.9.10 2.9.2 2.9.3 2.9.3.1 2.9.4 2.9.5 2.9.6 2.9.7 2.9.8 2.9.9 6.0 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.0.6 6.0.7 6.0.8 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.1.6 6.10.0 6.10.1 6.2 6.2.1 6.2.10 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.7 6.2.8 6.2.9 6.3 6.3.1 6.4 6.4.1 6.4.2 6.4.3 6.5.0 6.5.1 6.6.0 6.6.1 6.7.0 6.7.1 6.8.0 6.9.0 6.9.1 trunk 1.0 1.0.1 1.0.2 1.1 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.10 1.10.1 1.10.2 1.11
instagram-feed / inc / class-sb-instagram-parse.php
instagram-feed / inc Last commit date
Builder 2 weeks ago Helpers 2 weeks ago Integrations 2 weeks ago Services 2 weeks ago admin 2 weeks ago Email_Notification.php 2 weeks ago Platform_Data.php 2 weeks ago class-sb-instagram-api-connect.php 2 weeks ago class-sb-instagram-cache.php 2 weeks ago class-sb-instagram-connected-account.php 2 weeks ago class-sb-instagram-cron-updater.php 2 weeks ago class-sb-instagram-data-encryption.php 2 weeks ago class-sb-instagram-data-manager.php 2 weeks ago class-sb-instagram-display-elements.php 2 weeks ago class-sb-instagram-education.php 2 weeks ago class-sb-instagram-feed-locator.php 2 weeks ago class-sb-instagram-feed.php 2 weeks ago class-sb-instagram-gdpr-integrations.php 2 weeks ago class-sb-instagram-oembed.php 2 weeks ago class-sb-instagram-parse.php 2 weeks ago class-sb-instagram-post-set.php 2 weeks ago class-sb-instagram-post.php 2 weeks ago class-sb-instagram-posts-manager.php 2 weeks ago class-sb-instagram-settings.php 2 weeks ago class-sb-instagram-single.php 2 weeks ago class-sb-instagram-token-refresher.php 2 weeks ago email.php 2 weeks ago if-functions.php 2 weeks ago index.php 2 weeks ago
class-sb-instagram-parse.php
556 lines
1 <?php
2
3 if (!defined('ABSPATH')) {
4 die('-1');
5 }
6
7 /**
8 * Class SB_Instagram_Parse
9 *
10 * The structure of the data coming from the Instagram API is different
11 * for the old API vs the new graph API. This class is used to parse
12 * whatever structure the data has as well as use this to generate
13 * parts of the html used for image sources.
14 *
15 * @since 2.0/5.0
16 */
17 class SB_Instagram_Parse
18 {
19 /**
20 * @param $post array
21 *
22 * @return string
23 *
24 * @since 2.0/5.0
25 */
26 public static function get_account_type($post)
27 {
28 if (isset($post['media_type'])) {
29 return 'business';
30 } else {
31 return 'personal';
32 }
33 }
34
35 /**
36 * @param $post array
37 *
38 * @return false|int
39 *
40 * @since 2.0/5.0
41 */
42 public static function get_timestamp($post)
43 {
44 $timestamp = 0;
45 if (isset($post['created_time'])) {
46 $timestamp = $post['created_time'];
47 } elseif (isset($post['timestamp'])) {
48 // some date formatting functions have trouble with the "T", "+", and extra zeroes added by Instagram.
49 $remove_plus = trim(str_replace(array('T', '+', ' 0000'), ' ', $post['timestamp']));
50 $timestamp = strtotime($remove_plus);
51 }
52
53 return $timestamp;
54 }
55
56 /**
57 * Uses the existing data for the individual instagram post to
58 * set the best image sources for each resolution size. Due to
59 * random bugs or just how the API works, different post types
60 * need special treatment.
61 *
62 * @param array $post
63 * @param array $resized_images
64 *
65 * @return array
66 *
67 * @since 2.0/5.0
68 * @since 2.1.3/5.2.3 added 'd' element as a default backup from the API
69 */
70 public static function get_media_src_set($post, $resized_images = array())
71 {
72 $full_size = self::get_media_url($post);
73 $media_urls = array(
74 'd' => self::get_media_url($post),
75 '150' => '',
76 '320' => '',
77 '640' => ''
78 );
79 $account_type = isset($post['images']) ? 'personal' : 'business';
80
81 if ($account_type === 'personal') {
82 $media_urls['150'] = $post['images']['thumbnail']['url'];
83 $media_urls['320'] = $post['images']['low_resolution']['url'];
84 $media_urls['640'] = $post['images']['standard_resolution']['url'];
85 } else {
86 $post_id = self::get_post_id($post);
87
88 $media_urls['640'] = $full_size;
89 $media_urls['150'] = $full_size;
90 $media_urls['320'] = $full_size;
91
92 // use resized images if exists
93 if (
94 isset($resized_images[$post_id]['id'])
95 && $resized_images[$post_id]['id'] !== 'pending'
96 && $resized_images[$post_id]['id'] !== 'video'
97 && $resized_images[$post_id]['id'] !== 'error'
98 ) {
99 $extension = isset($resized_images[$post_id]['extension']) ? $resized_images[$post_id]['extension'] : '.jpg';
100 if (isset($resized_images[$post_id]['sizes']['full'])) {
101 $media_urls['640'] = sbi_get_resized_uploads_url() . $resized_images[$post_id]['id'] . 'full' . $extension;
102 }
103 if (isset($resized_images[$post_id]['sizes']['low'])) {
104 $media_urls['320'] = sbi_get_resized_uploads_url() . $resized_images[$post_id]['id'] . 'low' . $extension;
105 }
106 }
107 }
108
109 return $media_urls;
110 }
111
112 /**
113 * Get the media URL for the post.
114 *
115 * @param array $post Post data.
116 * @param string $resolution Resolution.
117 *
118 * @return string
119 * @since 2.0/5.0
120 */
121 public static function get_media_url($post, $resolution = 'lightbox')
122 {
123 $account_type = isset($post['images']) ? 'personal' : 'business';
124 $media_type = isset($post['media_type']) ? $post['media_type'] : 'none';
125
126 if ($account_type === 'personal') {
127 return $post['images']['standard_resolution']['url'];
128 }
129
130 if (
131 $media_type === 'CAROUSEL_ALBUM'
132 || $media_type === 'VIDEO'
133 || $media_type === 'OEMBED'
134 ) {
135 if (isset($post['thumbnail_url'])) {
136 return $post['thumbnail_url'];
137 } elseif ($media_type === 'CAROUSEL_ALBUM' && isset($post['media_url'])) {
138 return $post['media_url'];
139 } elseif (isset($post['children'])) {
140 $i = 0;
141 $full_size = '';
142 foreach ($post['children']['data'] as $carousel_item) {
143 if ($carousel_item['media_type'] === 'IMAGE' && empty($full_size)) {
144 if (isset($carousel_item['media_url'])) {
145 $full_size = $carousel_item['media_url'];
146 }
147 } elseif ($carousel_item['media_type'] === 'VIDEO' && empty($full_size)) {
148 if (isset($carousel_item['thumbnail_url'])) {
149 $full_size = $carousel_item['thumbnail_url'];
150 } else {
151 $full_size = self::fetch_carousel_item_media($carousel_item, $post);
152 }
153 }
154
155 $i++;
156 }
157 return $full_size;
158 } else {
159 return self::fetch_single_media($post);
160 }
161 } else {
162 if (isset($post['media_url'])) {
163 return $post['media_url'];
164 }
165
166 return self::fetch_single_media($post);
167 }
168 }
169
170 /**
171 * Fetches media URL using SB_Instagram_Single (oEmbed or Media API).
172 *
173 * @param array|string $post_or_permalink Post data array or permalink string.
174 *
175 * @return string Media URL, thumbnail URL, or placeholder.
176 *
177 * @since 6.10.0
178 */
179 public static function fetch_single_media($post_or_permalink)
180 {
181 if (!class_exists('SB_Instagram_Single')) {
182 return trailingslashit(SBI_PLUGIN_URL) . 'img/thumb-placeholder.png';
183 }
184
185 // Extract permalink and post_data
186 $permalink = is_array($post_or_permalink) ? self::fix_permalink(self::get_permalink($post_or_permalink)) : self::fix_permalink($post_or_permalink);
187 $post_data = is_array($post_or_permalink) ? $post_or_permalink : array();
188
189 $single = new SB_Instagram_Single($permalink, $post_data);
190 $single->init();
191 $fetched = $single->get_post();
192
193 // Return media_url (skip .mp4 files) or thumbnail_url
194 if (isset($fetched['media_url']) && !empty($fetched['media_url']) && strpos($fetched['media_url'], '.mp4') === false) {
195 return $fetched['media_url'];
196 }
197 if (isset($fetched['thumbnail_url']) && !empty($fetched['thumbnail_url'])) {
198 return $fetched['thumbnail_url'];
199 }
200
201 return trailingslashit(SBI_PLUGIN_URL) . 'img/thumb-placeholder.png';
202 }
203
204 /**
205 * Fetches carousel item media with inherited parent context for Media API.
206 *
207 * Carousel children don't have username fields, so we inherit from parent
208 * to enable Media API for user's own carousel posts.
209 *
210 * @param array $carousel_item Carousel child item data.
211 * @param array $parent_post Parent carousel post data.
212 *
213 * @return string Media URL, thumbnail URL, or placeholder.
214 *
215 * @since 6.10.0
216 */
217 protected static function fetch_carousel_item_media($carousel_item, $parent_post)
218 {
219 // Inherit parent context for Media API to work
220 $carousel_item_with_context = $carousel_item;
221
222 // Add username from parent post (enables Media API)
223 if (!empty($parent_post['username'])) {
224 $carousel_item_with_context['username'] = $parent_post['username'];
225 }
226
227 // Add parent ID for tracking
228 if (!empty($parent_post['id'])) {
229 $carousel_item_with_context['parent_id'] = $parent_post['id'];
230 }
231
232 // Fetch via Single class (oEmbed or Media API)
233 return self::fetch_single_media($carousel_item_with_context);
234 }
235
236 /**
237 * There seems to be occasional bugs with the Instagram API
238 * and permalinks. This corrects it.
239 *
240 * @param string $permalink
241 *
242 * @return string
243 *
244 * @since 2.0/5.0
245 */
246 public static function fix_permalink($permalink)
247 {
248 if (substr_count($permalink, '/') > 5) {
249 $permalink_array = explode('/', $permalink);
250 $perm_id = $permalink_array[count($permalink_array) - 2];
251 $permalink = 'https://www.instagram.com/p/' . $perm_id . '/';
252 }
253 return $permalink;
254 }
255
256 /**
257 * @param $post array
258 *
259 * @return mixed
260 *
261 * @since 2.0/5.0
262 */
263 public static function get_permalink($post)
264 {
265 if (isset($post['permalink'])) {
266 return $post['permalink'];
267 }
268
269 return $post['link'];
270 }
271
272 /**
273 * @param $post array
274 *
275 * @return mixed
276 *
277 * @since 2.0/5.0
278 */
279 public static function get_post_id($post)
280 {
281 return $post['id'];
282 }
283
284 /**
285 * A default can be set in the case that the user doesn't use captions
286 * for posts as this is also used as the alt text for the image.
287 *
288 * @param $post
289 * @param string $default
290 *
291 * @return string
292 *
293 * @since 2.0/5.0
294 */
295 public static function get_caption($post, $default = '')
296 {
297 $caption = $default;
298 if (!empty($post['caption']) && !is_array($post['caption'])) {
299 $caption = $post['caption'];
300 } elseif (!empty($post['caption']['text'])) {
301 $caption = $post['caption']['text'];
302 }
303
304 $video_title = self::get_video_title($post);
305
306 if (!empty($video_title)) {
307 $caption = $video_title . '. ' . $caption;
308 }
309
310 return $caption;
311 }
312
313 /**
314 * New in IG Graph API 10.0. A title for IGTV posts
315 *
316 * @param array $post
317 *
318 * @return string
319 *
320 * @since 2.9/5.12
321 */
322 public static function get_video_title($post)
323 {
324 if (isset($post['video_title'])) {
325 return $post['video_title'];
326 }
327
328 return '';
329 }
330
331 /**
332 * @param array $header_data
333 * @param array $settings
334 *
335 * @return string
336 *
337 * @since 2.0/5.0
338 * @since 2.2/5.3 added support for a custom avatar in settings
339 */
340 public static function get_avatar($header_data, $settings = array('favor_local' => false), $is_header_attr = false)
341 {
342 if ($is_header_attr) {
343 return self::get_avatar_url($header_data);
344 }
345
346 if (!empty($settings['customavatar'])) {
347 return $settings['customavatar'];
348 }
349
350 if (!empty($header_data['local_avatar_url'])) {
351 return $header_data['local_avatar_url'];
352 }
353
354 if (!empty($header_data['local_avatar']) && is_string($header_data['local_avatar'])) {
355 return $header_data['local_avatar'];
356 }
357
358 if (SB_Instagram_GDPR_Integrations::doing_gdpr($settings) && !$is_header_attr) {
359 return trailingslashit(SBI_PLUGIN_URL) . 'img/thumb-placeholder.png';
360 }
361
362 if (isset($header_data['profile_picture'])) {
363 return $header_data['profile_picture'];
364 }
365
366 if (isset($header_data['profile_picture_url'])) {
367 return $header_data['profile_picture_url'];
368 }
369
370 if (isset($header_data['user']) && is_array($header_data['user'])) {
371 return $header_data['user']['profile_picture'];
372 }
373
374 if (isset($header_data['data'])) {
375 return $header_data['data']['profile_picture'];
376 }
377
378 return '';
379 }
380
381 /**
382 * Get the avatar URL from the API response
383 *
384 * @param array $account_info
385 *
386 * @return string
387 *
388 * @since 6.0
389 */
390 public static function get_avatar_url($account_info)
391 {
392 if (isset($account_info['profile_picture'])) {
393 return $account_info['profile_picture'];
394 } elseif (isset($account_info['profile_picture_url'])) {
395 return $account_info['profile_picture_url'];
396 } elseif (isset($account_info['user'])) {
397 return $account_info['user']['profile_picture'];
398 } elseif (isset($account_info['data'])) {
399 return $account_info['data']['profile_picture'];
400 }
401
402 return '';
403 }
404
405 /**
406 * The full name attached to the user account
407 *
408 * @param array $header_data
409 *
410 * @return string
411 *
412 * @since 2.0/5.0
413 */
414 public static function get_name($header_data)
415 {
416 if (isset($header_data['name'])) {
417 return $header_data['name'];
418 } elseif (isset($header_data['data']['full_name'])) {
419 return $header_data['data']['full_name'];
420 }
421 return self::get_username($header_data);
422 }
423
424 /**
425 * @param array $header_data
426 *
427 * @return string
428 *
429 * @since 2.0/5.0
430 */
431 public static function get_username($header_data)
432 {
433 if (isset($header_data['username'])) {
434 return $header_data['username'];
435 } elseif (isset($header_data['user'])) {
436 return $header_data['user']['username'];
437 } elseif (isset($header_data['data'])) {
438 return $header_data['data']['username'];
439 }
440 return '';
441 }
442
443 /**
444 * Account bio/description used in header
445 *
446 * @param $header_data
447 *
448 * @return string
449 *
450 * @since 2.0.1/5.0
451 * @since 2.2/5.3 added support for a custom bio in settings
452 */
453 public static function get_bio($header_data, $settings = array())
454 {
455 $customizer = $settings['customizer'];
456 if ($customizer) {
457 return '{{$parent.getHeaderBio()}}';
458 } else {
459 if (!empty($settings['custombio'])) {
460 return $settings['custombio'];
461 } elseif (isset($header_data['data']['bio'])) {
462 return $header_data['data']['bio'];
463 } elseif (isset($header_data['bio'])) {
464 return $header_data['bio'];
465 } elseif (isset($header_data['biography'])) {
466 return $header_data['biography'];
467 }
468 return '';
469 }
470 }
471
472 /**
473 * New in IG Graph API 10.0
474 *
475 * @param array $post
476 *
477 * @return string
478 *
479 * @since 2.9/5.12
480 */
481 public static function get_media_product_type($post)
482 {
483 if (isset($post['media_product_type'])) {
484 return strtolower($post['media_product_type']);
485 }
486
487 // get media_type and permalink and search for reel in permalink.
488 $media_type = self::get_media_type($post);
489 $permalink = self::get_permalink($post);
490 if ($media_type === 'video' && strpos($permalink, 'https://www.instagram.com/reel/') !== false) {
491 return 'reels';
492 }
493
494 return 'feed';
495 }
496
497 /**
498 * @param $post array
499 *
500 * @return string
501 *
502 * @since 2.0/5.0
503 */
504 public static function get_media_type($post)
505 {
506 if (isset($post['type'])) {
507 return $post['type'];
508 }
509
510 return strtolower(str_replace('_ALBUM', '', $post['media_type']));
511 }
512
513 /**
514 * Whether the post is an Instagram Trial Reel.
515 *
516 * Trial Reels are shown only to non-followers and are excluded from the
517 * creator's own profile grid. The API returns them with
518 * `is_shared_to_feed: false`. Cached posts from before this field was
519 * requested won't have the key — treat missing as "not a trial reel" so
520 * existing caches degrade gracefully until they refresh.
521 *
522 * @param array $post A single post from the Instagram Graph API.
523 *
524 * @return bool
525 */
526 public static function is_trial_reel($post)
527 {
528 return isset($post['is_shared_to_feed']) && false === $post['is_shared_to_feed'];
529 }
530
531 /**
532 * Strip trial reels from a post set.
533 *
534 * Runs unconditionally; gated by the `sbi_hide_trial_reels` filter for
535 * advanced users who want to opt back in.
536 *
537 * @param array $posts Raw post set from an API response.
538 *
539 * @return array
540 */
541 public static function filter_out_trial_reels($posts)
542 {
543 if (!apply_filters('sbi_hide_trial_reels', true)) {
544 return $posts;
545 }
546
547 $filtered = [];
548 foreach ($posts as $post) {
549 if (!self::is_trial_reel($post)) {
550 $filtered[] = $post;
551 }
552 }
553 return $filtered;
554 }
555 }
556