Created
February 12, 2019 19:47
-
-
Save asadkn/e0aa74254d06cb5c1216048aa927ada1 to your computer and use it in GitHub Desktop.
Flood prevention and persistent storage for data of WP Instagram Widget.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
Plugin Name: WP Instagram Widget | |
Plugin URI: https://github.com/scottsweb/wp-instagram-widget | |
Description: A WordPress widget for showing your latest Instagram photos. | |
Version: 2.0.3 | |
Author: Scott Evans | |
Author URI: https://scott.ee | |
Text Domain: wp-instagram-widget | |
Domain Path: /assets/languages/ | |
License: GPLv2 or later | |
License URI: http://www.gnu.org/licenses/gpl-2.0.html | |
Copyright © 2013 Scott Evans | |
This program is free software; you can redistribute it and/or modify | |
it under the terms of the GNU General Public License as published by | |
the Free Software Foundation; either version 2 of the License, or | |
(at your option) any later version. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU General Public License for more details. | |
*/ | |
function wpiw_init() { | |
// define some constants. | |
define( 'WP_INSTAGRAM_WIDGET_JS_URL', plugins_url( '/assets/js', __FILE__ ) ); | |
define( 'WP_INSTAGRAM_WIDGET_CSS_URL', plugins_url( '/assets/css', __FILE__ ) ); | |
define( 'WP_INSTAGRAM_WIDGET_IMAGES_URL', plugins_url( '/assets/images', __FILE__ ) ); | |
define( 'WP_INSTAGRAM_WIDGET_PATH', dirname( __FILE__ ) ); | |
define( 'WP_INSTAGRAM_WIDGET_BASE', plugin_basename( __FILE__ ) ); | |
define( 'WP_INSTAGRAM_WIDGET_FILE', __FILE__ ); | |
// load language files. | |
load_plugin_textdomain( 'wp-instagram-widget', false, dirname( WP_INSTAGRAM_WIDGET_BASE ) . '/assets/languages/' ); | |
} | |
add_action( 'init', 'wpiw_init' ); | |
function wpiw_widget() { | |
register_widget( 'null_instagram_widget' ); | |
} | |
add_action( 'widgets_init', 'wpiw_widget' ); | |
Class null_instagram_widget extends WP_Widget { | |
function __construct() { | |
parent::__construct( | |
'null-instagram-feed', | |
__( 'Instagram', 'wp-instagram-widget' ), | |
array( | |
'classname' => 'null-instagram-feed', | |
'description' => esc_html__( 'Displays your latest Instagram photos', 'wp-instagram-widget' ), | |
'customize_selective_refresh' => true, | |
) | |
); | |
} | |
function widget( $args, $instance ) { | |
$title = empty( $instance['title'] ) ? '' : apply_filters( 'widget_title', $instance['title'] ); | |
$username = empty( $instance['username'] ) ? '' : $instance['username']; | |
$limit = empty( $instance['number'] ) ? 9 : $instance['number']; | |
$size = empty( $instance['size'] ) ? 'large' : $instance['size']; | |
$target = empty( $instance['target'] ) ? '_self' : $instance['target']; | |
$link = empty( $instance['link'] ) ? '' : $instance['link']; | |
echo $args['before_widget']; | |
if ( ! empty( $title ) ) { echo $args['before_title'] . wp_kses_post( $title ) . $args['after_title']; }; | |
do_action( 'wpiw_before_widget', $instance ); | |
if ( '' !== $username ) { | |
$media_array = $this->scrape_instagram( $username ); | |
if ( is_wp_error( $media_array ) ) { | |
echo wp_kses_post( $media_array->get_error_message() ); | |
} else { | |
// filter for images only? | |
if ( $images_only = apply_filters( 'wpiw_images_only', false ) ) { | |
$media_array = array_filter( $media_array, array( $this, 'images_only' ) ); | |
} | |
// slice list down to required limit. | |
$media_array = array_slice( apply_filters( 'wpiw_media_array', $media_array ), 0, $limit ); | |
// filters for custom classes. | |
$ulclass = apply_filters( 'wpiw_list_class', 'instagram-pics instagram-size-' . $size ); | |
$liclass = apply_filters( 'wpiw_item_class', '' ); | |
$aclass = apply_filters( 'wpiw_a_class', '' ); | |
$imgclass = apply_filters( 'wpiw_img_class', '' ); | |
$template_part = apply_filters( 'wpiw_template_part', 'parts/wp-instagram-widget.php' ); | |
?><ul class="<?php echo esc_attr( $ulclass ); ?>"><?php | |
foreach( $media_array as $item ) { | |
// copy the else line into a new file (parts/wp-instagram-widget.php) within your theme and customise accordingly. | |
if ( locate_template( $template_part ) !== '' ) { | |
include locate_template( $template_part ); | |
} else { | |
echo '<li class="' . esc_attr( $liclass ) . '"><a href="' . esc_url( $item['link'] ) . '" target="' . esc_attr( $target ) . '" class="' . esc_attr( $aclass ) . '"><img src="' . esc_url( $item[$size] ) . '" alt="' . esc_attr( $item['description'] ) . '" title="' . esc_attr( $item['description'] ) . '" class="' . esc_attr( $imgclass ) . '"/></a></li>'; | |
} | |
} | |
?></ul><?php | |
} | |
} | |
$linkclass = apply_filters( 'wpiw_link_class', 'clear' ); | |
$linkaclass = apply_filters( 'wpiw_linka_class', '' ); | |
switch ( substr( $username, 0, 1 ) ) { | |
case '#': | |
$url = '//instagram.com/explore/tags/' . str_replace( '#', '', $username ); | |
break; | |
default: | |
$url = '//instagram.com/' . str_replace( '@', '', $username ); | |
break; | |
} | |
if ( '' !== $link ) { | |
?><p class="<?php echo esc_attr( $linkclass ); ?>"><a href="<?php echo trailingslashit( esc_url( $url ) ); ?>" rel="me" target="<?php echo esc_attr( $target ); ?>" class="<?php echo esc_attr( $linkaclass ); ?>"><?php echo wp_kses_post( $link ); ?></a></p><?php | |
} | |
do_action( 'wpiw_after_widget', $instance ); | |
echo $args['after_widget']; | |
} | |
function form( $instance ) { | |
$instance = wp_parse_args( (array) $instance, array( | |
'title' => __( 'Instagram', 'wp-instagram-widget' ), | |
'username' => '', | |
'size' => 'large', | |
'link' => __( 'Follow Me!', 'wp-instagram-widget' ), | |
'number' => 9, | |
'target' => '_self', | |
) ); | |
$title = $instance['title']; | |
$username = $instance['username']; | |
$number = absint( $instance['number'] ); | |
$size = $instance['size']; | |
$target = $instance['target']; | |
$link = $instance['link']; | |
?> | |
<p><label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php esc_html_e( 'Title', 'wp-instagram-widget' ); ?>: <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" /></label></p> | |
<p><label for="<?php echo esc_attr( $this->get_field_id( 'username' ) ); ?>"><?php esc_html_e( '@username or #tag', 'wp-instagram-widget' ); ?>: <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'username' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'username' ) ); ?>" type="text" value="<?php echo esc_attr( $username ); ?>" /></label></p> | |
<p><label for="<?php echo esc_attr( $this->get_field_id( 'number' ) ); ?>"><?php esc_html_e( 'Number of photos', 'wp-instagram-widget' ); ?>: <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'number' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'number' ) ); ?>" type="text" value="<?php echo esc_attr( $number ); ?>" /></label></p> | |
<p><label for="<?php echo esc_attr( $this->get_field_id( 'size' ) ); ?>"><?php esc_html_e( 'Photo size', 'wp-instagram-widget' ); ?>:</label> | |
<select id="<?php echo esc_attr( $this->get_field_id( 'size' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'size' ) ); ?>" class="widefat"> | |
<option value="thumbnail" <?php selected( 'thumbnail', $size ); ?>><?php esc_html_e( 'Thumbnail', 'wp-instagram-widget' ); ?></option> | |
<option value="small" <?php selected( 'small', $size ); ?>><?php esc_html_e( 'Small', 'wp-instagram-widget' ); ?></option> | |
<option value="large" <?php selected( 'large', $size ); ?>><?php esc_html_e( 'Large', 'wp-instagram-widget' ); ?></option> | |
<option value="original" <?php selected( 'original', $size ); ?>><?php esc_html_e( 'Original', 'wp-instagram-widget' ); ?></option> | |
</select> | |
</p> | |
<p><label for="<?php echo esc_attr( $this->get_field_id( 'target' ) ); ?>"><?php esc_html_e( 'Open links in', 'wp-instagram-widget' ); ?>:</label> | |
<select id="<?php echo esc_attr( $this->get_field_id( 'target' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'target' ) ); ?>" class="widefat"> | |
<option value="_self" <?php selected( '_self', $target ); ?>><?php esc_html_e( 'Current window (_self)', 'wp-instagram-widget' ); ?></option> | |
<option value="_blank" <?php selected( '_blank', $target ); ?>><?php esc_html_e( 'New window (_blank)', 'wp-instagram-widget' ); ?></option> | |
</select> | |
</p> | |
<p><label for="<?php echo esc_attr( $this->get_field_id( 'link' ) ); ?>"><?php esc_html_e( 'Link text', 'wp-instagram-widget' ); ?>: <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'link' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'link' ) ); ?>" type="text" value="<?php echo esc_attr( $link ); ?>" /></label></p> | |
<?php | |
} | |
function update( $new_instance, $old_instance ) { | |
$instance = $old_instance; | |
$instance['title'] = strip_tags( $new_instance['title'] ); | |
$instance['username'] = trim( strip_tags( $new_instance['username'] ) ); | |
$instance['number'] = ! absint( $new_instance['number'] ) ? 9 : $new_instance['number']; | |
$instance['size'] = ( ( 'thumbnail' === $new_instance['size'] || 'large' === $new_instance['size'] || 'small' === $new_instance['size'] || 'original' === $new_instance['size'] ) ? $new_instance['size'] : 'large' ); | |
$instance['target'] = ( ( '_self' === $new_instance['target'] || '_blank' === $new_instance['target'] ) ? $new_instance['target'] : '_self' ); | |
$instance['link'] = strip_tags( $new_instance['link'] ); | |
return $instance; | |
} | |
/** | |
* Scrape from instagram while handling errors gracefully to prevent a flood. | |
* | |
* @param string $username | |
* @return WP_Error|array | |
*/ | |
function scrape_instagram( $username ) { | |
$username = trim( strtolower( $username ) ); | |
// delete_option( $this->get_storage_id($username) ); | |
// Return errors, or previously cached data, if any throttled errors exist. | |
if ( true !== ( $cache_or_errors = $this->_throttle_errors( $username ) ) ) { | |
return $cache_or_errors; | |
} | |
$result = $this->_scrape_instagram( $username ); | |
// On error, set a throttle transient and extend data transient. | |
if ( is_wp_error( $result ) ) { | |
set_transient( 'insta-a10-errors-throttle', $result, 30 ); | |
return $this->_throttle_errors( $username ); | |
} | |
return $result; | |
} | |
/** | |
* If errors' been registered (a 30 sec default throttle), deliver previously | |
* successful results (if ever recorded), or return stored WP_Error object. | |
*/ | |
function _throttle_errors( $username ) { | |
$throttled_errors = get_transient( 'insta-a10-errors-throttle' ); | |
if ( ! $throttled_errors ) { | |
return true; | |
} | |
$previous_results = get_option( $this->get_storage_id( $username ) ); | |
return !empty($previous_results['data']) ? $previous_results['data'] : $throttled_errors; | |
} | |
// based on https://gist.github.com/cosmocatalano/4544576. | |
function _scrape_instagram( $username ) { | |
switch ( substr( $username, 0, 1 ) ) { | |
case '#': | |
$url = 'https://instagram.com/explore/tags/' . str_replace( '#', '', $username ); | |
break; | |
default: | |
$url = 'https://instagram.com/' . str_replace( '@', '', $username ); | |
break; | |
} | |
$instagram = get_option( $this->get_storage_id( $username ) ); | |
if ( false === $instagram || $instagram['refresh_in'] < time() ) { | |
$remote = wp_remote_get( $url ); | |
if ( is_wp_error( $remote ) ) { | |
return new WP_Error( 'site_down', esc_html__( 'Unable to communicate with Instagram.', 'wp-instagram-widget' ) ); | |
} | |
if ( 200 !== wp_remote_retrieve_response_code( $remote ) ) { | |
return new WP_Error( 'invalid_response', esc_html__( 'Instagram did not return a 200.', 'wp-instagram-widget' ) ); | |
} | |
$shards = explode( 'window._sharedData = ', $remote['body'] ); | |
$insta_json = explode( ';</script>', $shards[1] ); | |
$insta_array = json_decode( $insta_json[0], true ); | |
if ( ! $insta_array ) { | |
return new WP_Error( 'bad_json', esc_html__( 'Instagram has returned invalid data.', 'wp-instagram-widget' ) ); | |
} | |
if ( isset( $insta_array['entry_data']['ProfilePage'][0]['graphql']['user']['edge_owner_to_timeline_media']['edges'] ) ) { | |
$images = $insta_array['entry_data']['ProfilePage'][0]['graphql']['user']['edge_owner_to_timeline_media']['edges']; | |
} elseif ( isset( $insta_array['entry_data']['TagPage'][0]['graphql']['hashtag']['edge_hashtag_to_media']['edges'] ) ) { | |
$images = $insta_array['entry_data']['TagPage'][0]['graphql']['hashtag']['edge_hashtag_to_media']['edges']; | |
} else { | |
return new WP_Error( 'bad_json_2', esc_html__( 'Instagram has returned invalid data.', 'wp-instagram-widget' ) ); | |
} | |
if ( ! is_array( $images ) ) { | |
return new WP_Error( 'bad_array', esc_html__( 'Instagram has returned invalid data.', 'wp-instagram-widget' ) ); | |
} | |
$instagram = array(); | |
foreach ( $images as $image ) { | |
if ( true === $image['node']['is_video'] ) { | |
$type = 'video'; | |
} else { | |
$type = 'image'; | |
} | |
$caption = __( 'Instagram Image', 'wp-instagram-widget' ); | |
if ( ! empty( $image['node']['edge_media_to_caption']['edges'][0]['node']['text'] ) ) { | |
$caption = wp_kses( $image['node']['edge_media_to_caption']['edges'][0]['node']['text'], array() ); | |
} | |
$instagram[] = array( | |
'description' => $caption, | |
'link' => trailingslashit( '//instagram.com/p/' . $image['node']['shortcode'] ), | |
'time' => $image['node']['taken_at_timestamp'], | |
'comments' => $image['node']['edge_media_to_comment']['count'], | |
'likes' => $image['node']['edge_liked_by']['count'], | |
'thumbnail' => preg_replace( '/^https?\:/i', '', $image['node']['thumbnail_resources'][0]['src'] ), | |
'small' => preg_replace( '/^https?\:/i', '', $image['node']['thumbnail_resources'][2]['src'] ), | |
'large' => preg_replace( '/^https?\:/i', '', $image['node']['thumbnail_resources'][4]['src'] ), | |
'original' => preg_replace( '/^https?\:/i', '', $image['node']['display_url'] ), | |
'type' => $type, | |
); | |
} // End foreach(). | |
// Do not set an empty cache - should help catch private or empty accounts. | |
if ( ! empty( $instagram ) ) { | |
$instagram = array( | |
'refresh_in' => time() + apply_filters( 'null_instagram_cache_time', HOUR_IN_SECONDS * 2 ), | |
'data' => $instagram, | |
); | |
update_option( $this->get_storage_id( $username ), $instagram, false ); | |
} | |
} | |
if ( ! empty( $instagram['data'] ) ) { | |
return $instagram['data']; | |
} else { | |
return new WP_Error( 'no_images', esc_html__( 'Instagram did not return any images.', 'wp-instagram-widget' ) ); | |
} | |
} | |
/** | |
* Gets the id we use for storage and caches. | |
* | |
* @param string $username | |
* @return string | |
*/ | |
function get_storage_id( $username ) { | |
switch ( substr( $username, 0, 1 ) ) { | |
case '#': | |
$prefix = 'h'; | |
break; | |
default: | |
$prefix = 'u'; | |
break; | |
} | |
return 'insta-a10-' . $prefix . '-' . sanitize_title_with_dashes( $username ); | |
} | |
function images_only( $media_item ) { | |
if ( 'image' === $media_item['type'] ) { | |
return true; | |
} | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment