1

I have an Instagram widget uses an instagram-scrapper that caches its data in a transient to display photos, based on an username input from the Customizer.

Unfortunately, when I update my username, the transient doesn't update. It uses the old one (from the old username), this, effectively, doesn't let me render photos from the new username, making the widget somewhat useless after the first use.

In short I'm looking for something like:

if( is_updated($username) ? new_transient($username) : get_transient($username).

scrapper[skip to next block for the transient bit]:

<?php

namespace Social_Space;

/**
 * Interface to scrape most popular JSON-enabled social networks.
 */
interface Scrape_Interface {

    /* Builds the end-point, or the link that will be queried */
    public function build_endpoint();

    /* Ingests the pure, non-altered JSON data */
    public function get_json();

    /* Handles and cleans the data */
    public function get_data();
}

class Scrape_Instagram implements Scrape_Interface {

    private $username;

    public function set_username( $username ) {
        $this->username = $username;
    }

    /**
     * Builds the endpoint for the Instagram API, provided the username from constructor.
     * @param $end_cursor   Used in case the user wants more pictures to be scrapped.
     */
    public function build_endpoint() {
        return 'https://www.instagram.com/' . trim( strtolower( $this->username ) ) . '/?__a=1';
    }

    /**
     * GETs the data from Instagram, provided the endpoint.
     */
    public function get_json() {
        if ( false === ( $data = get_transient( 'instagram_data' ) ) )  {
            $request = wp_remote_get( $this->build_endpoint() );

            if( is_wp_error( $request ) ) {
                return new WP_Error( 'site-down', esc_html__( 'Instagram may be down. Unable to communicate.', '_s') );
            }

            elseif( wp_remote_retrieve_response_code( $request ) !== 200 ) {
                return new WP_Error( 'invalid-response', esc_html__( 'Got an invalid response.', '_s') );
            }

            else {
                set_transient( 'instagram_data', wp_remote_retrieve_body( $request ), 5 );
            }
        }
        return $data;
    }

    /**
     * Ingests the data from get_json() then converts / decodes into an array.
     */
    public function get_data() {
        $json = $this->get_json();

        if( is_wp_error( $json ) ) {
            return new WP_Error( 'invalid-json', esc_html__( 'Something is wrong. Did you enter the right username?', '_s' ) );
        }

        $data = json_decode( $json, true);

        if( is_wp_error( $data ) ) {
            return new WP_Error( 'invalid-data', esc_html__( 'Something is wrong. Did you enter the right username?', '_s' ) );
        } else {
            return $data;
        }
    }
}

class Generate_Instagram_Links {

    private $instagram_object;
    private $username;

    public function __construct( $username, Scrape_Instagram $instagram_object = null ) {
        $this->username = $username;
        $this->instagram_object = $instagram_object === null ? new Scrape_Instagram : $instagram_object;
        $this->instagram_object->set_username( $this->username );
    }

    /**
     * Generates the link to the source image, as well as to the instagram post of each image
     * requested by our script.
     */
    public function get_links() {
        $response = $this->instagram_object->get_data();
        /**
         * Based on the array generated from get_data(), some nodes have resulted that contain    * information about
         * each photo the user has, as such, we'll loop through each photo and access any data.
         * @see ['user']['media']['nodes'] - individual node / image.
         */
        if( is_wp_error( $response ) ) {
            return new WP_Error( 'invalid-json', esc_html__( 'Ouch. The data was not parsed correctly. Cannot continue.' ) );
        } else {
            foreach( $response['user']['media']['nodes'] as $node ) {
                $image = array('real_link' => 'src' );
                $image['real_link'] = 'https://www.instagram.com/p/' . $node['code'] . '?taken-by=' . $this->username;
                $image['src'] = $node['thumbnail_resources'][0]['src'];
                $images_links[] = $image;
            }
        }
        return $images_links;
    }

    /**
     * Getter for the instagram links (non-local).
     */
    public function get_instagram_photos_links() {
        $links = $this->get_links();

        if( is_wp_error( $links) ) {
            return new WP_error( 'unknown-error', esc_html__( 'Something went wrong. Cannot get data. Check username or contact developer.') );
        } else {
            return $links;
        }
    }

    /**
     * Gets the local photo links of the newly added instagram photo links, or retrieves the
     * links if they already exist(to not flood the media library).
     * @todo        Add an "expiry" date for each image, or remove if no longer used.
     */
    private function get_local_links() {
        // Requires loading of the wp-admin scripts.
        require_once ('wp-load.php');
        require_once ('wp-admin/includes/admin.php');

        $urls = $this->get_instagram_photos_links();

        $local_images   = array();

        foreach( $urls as $url ) {

            $tmp = download_url( $url['src'] );

            $file_array = array(
                'name'      => basename( $url['src'] ),
                'tmp_name'   => $tmp
            );

            $wp_upload_dir = wp_get_upload_dir();
            $local_url = $wp_upload_dir['path'] . '/' . $file_array['name'];

            if( file_exists( $local_url ) ) {
                array_push( $local_images, ($wp_upload_dir['url'] . '/' . $file_array['name']) );
            } else {
                if ( is_wp_error( $tmp ) ) {
                    @unlink( $file_array[ 'tmp_name' ] );
                    return $tmp;
                }

                $GLOBALS['post'] = null;

                $post_id = '0';

                $id = media_handle_sideload( $file_array, $post_id, $desc = null, $post_data = array('post_content' => 'insta_image' ) );

                if ( is_wp_error( $id ) ) {
                    @unlink( $file_array['tmp_name'] );
                    return $id;
                }

                $value = wp_get_attachment_url( $id );

                array_push( $local_images, $value );
            }
        }
        return $local_images;
    }

    /**
     * Getter for the instagram links (local).
     */
    public function get_instagram_local_photos_links() {
        $links = $this->get_local_links();

        if( is_wp_error( $links) ) {
            return new WP_error( 'unknown-error', esc_html__( 'I cannot get the local links of the pictures. Check with the developer.' ) );
        } else {
            return $links;
        }
    }
}

namely the function "get_json":

public function get_json() {
    if ( false === ( $data = get_transient( 'instagram_data' ) ) )  {
        $request = wp_remote_get( $this->build_endpoint() );

        if( is_wp_error( $request ) ) {
            return new WP_Error( 'site-down', esc_html__( 'Instagram may be down. Unable to communicate.', '_s') );
        }

        elseif( wp_remote_retrieve_response_code( $request ) !== 200 ) {
            return new WP_Error( 'invalid-response', esc_html__( 'Got an invalid response.', '_s') );
        }

        else {
            set_transient( 'instagram_data', wp_remote_retrieve_body( $request ), 5 );
        }
    }
    return $data;
}

As stated, this won't work. Here's my widget where I check for an update of $username before doing anything, in the widget() function.

<?php
/**
 * Plugin Name: Instagram Widget
 */


add_action( 'widget_init', 'custom_widget_instagram_pictures');
register_widget( 'custom_widget_instagram_pictures' );

class custom_widget_instagram_pictures extends Wp_Widget {

    /**
     * Setup the Widget
     */
    public function __construct() {
        $widget_ops = array('classname'     => 'custom_widget_instagram_pictures',
                            'description'   => esc_html__('A widget to display instagram photos.', '_s')
                            );

        $control_ops = array('id_base'      => 'custom_widget_instagram_pictures');

        parent::__construct( 'custom_widget_instagram_pictures', __('_s: Instagram Widget', '_s_instagram_widget'), $widget_ops, $control_ops );
    }

    public function widget( $args, $instance ) {
        extract( $args );
        $title              = isset( $instance['title'] ) ? apply_filters('widget_title', $instance['title'] ) : '';
        $username           = isset( $instance['username'] ) ? $instance['username'] : '';
        $profile_link       = isset( $instance['profile_link'] ) ? $instance['profile_link'] : '';
        $columns            = isset( $instance['columns'] ) ? $instance[ 'columns'] : '';
        $save_local         = isset( $instance['save_local'] ) ? $instance['save_local'] : '';
        $number_of_photos   = isset( $instance['number_of_photos'] ) ? $instance['number_of_photos'] : '';

        echo ent2ncr( $before_widget );

        if ( $title ) {
            echo ent2ncr( $before_title . $title . $after_title );
        }
        ?>
        <div class="instagram-widget">
                <?php
                if( $username ) {
                    $instagram_object = new Social_Space\Generate_Instagram_Links( $username, new Social_Space\Scrape_Instagram );

                    if ( !$save_local ) {

                        $instagram_links = $instagram_object->get_instagram_photos_links();

                        if( is_wp_error( $instagram_links ) ) {
                            echo "Ouch. Something's wrong. Did you enter the right username?";
                        } else {

                            $instagram_links_parsed = 0;
                            ?>
                            <ul class="instagram-widget-list">
                            <?php
                                foreach( $instagram_links as $link ) {
                                    if ( $instagram_links_parsed < absint( $number_of_photos ) ) {
                                        $instagram_links_parsed++;
                                        ?>
                                        <li class="instagram-thumb instagram-thumb-<?php echo $columns ?>-col">
                                            <a href="<?php echo $link['real_link']; ?>"><img src="<?php echo $link['src']; ?>"/></a>
                                        </li>
                                        <?php
                                    }
                                    else {
                                        break;
                                    }
                                }
                            ?>
                            </ul>
                            <?php
                        }
                    }

                    elseif ( $save_local ) {

                        $instagram_links = $instagram_object->get_instagram_local_photos_links();

                        if( is_wp_error( $instagram_links ) ) {
                            echo "Ouch. Something's wrong. Did you enter the right username?";
                        } else {
                            $instagram_links_parsed = 0;

                            ?>

                            <ul class="instagram-widget-list">
                            <?php
                                foreach( $instagram_links as $link ) {
                                    if ( $instagram_links_parsed < absint( $number_of_photos ) ) {
                                        $instagram_links_parsed++;
                                        ?>
                                        <li class="instagram-thumb instagram-thumb-<?php echo $columns ?>-col">
                                            <a href="<?php echo $link; ?>"><img src="<?php echo $link; ?>"/></a>
                                        </li>
                                    <?php
                                    }
                                    else {
                                        break;
                                    }
                                }
                            ?>
                            </ul>
                            <?php
                        }
                    }
                } else {
                    echo _s_setup_widget_notification();
                } ?>
            <?php if( 'on' == $profile_link ) : ?>
            <button class="instagram-widget-profile-link"><a href="<?php echo 'https://www.instagram.com/' . $username; ?>"><i class="sf-4"></i><p><?php echo $username ?></p></a></button>
            <?php endif; ?>
        </div>
        <?php
        echo ent2ncr( $after_widget );
    }

    function update( $new_instance, $old_instance ) {
        $instance['title']          = ( isset( $new_instance['title'] ) ) ? strip_tags($new_instance['title'] ) : '';
        $instance['username']       = ( isset( $new_instance['username'] ) ) ? strip_tags( $new_instance['username'] ) : '';
        $instance['profile_link']   = ( isset( $new_instance['profile_link'] ) ) ? strip_tags( $new_instance['profile_link'] ) : '';
        $instance['columns']        = ( isset( $new_instance['columns'] ) ) ? strip_tags( $new_instance['columns'] ) : '';
        $instance['save_local']     = ( isset( $new_instance['save_local'] ) ) ? strip_tags( $new_instance['save_local'] ) : '';
        $instance['number_of_photos'] = ( isset( $new_instance['number_of_photos'] ) ) ? strip_tags( $new_instance['number_of_photos'] ) : '';

        return $instance;
    }

    function form( $instance ) {
        $defaults = array(  'title'             => '',
                            'username'          => '',
                            'profile_link'      => '',
                            'columns'           => '3',
                            'save_local'        => '',
                            'number_of_photos'  => '12',
                        );

        $instance = wp_parse_args( (array) $instance, $defaults ); ?>

        <!-- Custom Message -->
        <div class="customizer-custom-message-1">
            <p>Hey! Just one second. We strongly advise you keep an "even" number of images, 3 columns should have 3,6,9...photos. A 2 columns layout should have 2,4,6 and so on. Looks much better this way! </p>
        </div>
        <!-- Form for Title -->
        <p>
            <label for="<?php echo $this->get_field_id( 'title' ); ?>">Widget Title:<strong>(Leave Blank to Hide)</strong></label>
            <br>
            <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" value="<?php echo $instance['title'];?>" />
        </p>

        <!-- Form for Username -->
        <p>
            <label for="<?php echo $this->get_field_id( 'username' ); ?>">Username:</label>
            <br>
            <input class="widefat" id="<?php echo $this->get_field_id( 'username' ); ?>" name="<?php echo $this->get_field_name( 'username' ); ?>" value="<?php echo $instance['username'];?>" />
        </p>

        <!-- Checkbox for Local -->
        <p>
            <input class="checkbox" type="checkbox" <?php checked( $instance[ 'save_local' ], 'on' ); ?> id="<?php echo $this->get_field_id( 'save_local' ); ?>" name="<?php echo $this->get_field_name( 'save_local' ); ?>" />
            <label for="<?php echo $this->get_field_id( 'save_local' ); ?>">Save Images to Local</label>
        </p>

        <!-- Checkbox for Profile Link -->
        <p>
            <input class="checkbox" type="checkbox" <?php checked( $instance[ 'profile_link' ], 'on' ); ?> id="<?php echo $this->get_field_id( 'profile_link' ); ?>" name="<?php echo $this->get_field_name( 'profile_link' ); ?>" />
            <label for="<?php echo $this->get_field_id( 'profile_link' ); ?>">Show Instagram Button?</label>
        </p>

        <!-- Columns -->
        <p>
            <label for="<?php echo $this->get_field_id( 'columns' ); ?>">How Many Columns to Display?<strong>(Leave Blank to Hide)</strong></label>
            <br>
            <input class="widefat" id="<?php echo $this->get_field_id( 'columns' ); ?>" name="<?php echo $this->get_field_name( 'columns' ); ?>" value="<?php echo $instance['columns'];?>" />
        </p>

        <!-- Number of Photos -->
        <p>
            <label for="<?php echo $this->get_field_id( 'number_of_photos' ); ?>">How Many Pictures?</label>
            <br>
            <input class="widefat" id="<?php echo $this->get_field_id( 'number_of_photos' ); ?>" name="<?php echo $this->get_field_name( 'number_of_photos' ); ?>" value="<?php echo $instance['number_of_photos'];?>" />
        </p>
    <?php
    }
}
2

The simplest thing to do is simply delete the transient when the username is updated. Then the next time get_json() is run it will see that the transient is not set, just as if it had expired, and retrieve the data and save the transient again.

So inside the update() method of your widget class you would do this:

if ( $new_instance['username'] !== $old_instance['username'] ) {
    delete_transient( 'instagram_data' );
}

However one issue with the way you're setting the transient is that there's only one transient, instagram_data, and this will be retrieved no matter what username you're getting data for. So even if you're deleting the transient when updating the widget, if the user adds multiple widgets with different usernames the transient's only ever going to contain data for the first username that went through get_json().

You can address this by creating a transient for each user. This would just involve incorporating the username into the transient name by doing this in your get_json() method:

$transient_name = $this->username . '_instagram_data';

if ( false === ( $data = get_transient( $transient_name ) ) )  {
    // etc.
else {
    set_transient( $transient_name, wp_remote_retrieve_body( $request ), 5 );
}

And the code in your widget update method would become:

if ( $new_instance['username'] !== $old_instance['username'] ) {
        delete_transient( $old_instance['username'] . '_instagram_data' );
}

This isn't required anymore though, because if there's a new username there'll be a new transient, but it will help keep the database free of any unnecessary transients.

  • 1
    By the powers I have been bestowed with, I declare thee the biggest & baddest. I knew there was a delete transient, even occured to me to set 2 transients, but this is very elegant. One question on top of this: my one main issue was I couldn't pinpoint exactly where the "action hook" should be. I was thinking about the "update" function but couldn't quite do it. I can't find anything related to it, any docs or stuff to read upon it? – Jonathan Guerin Feb 16 '18 at 7:52
  • No problem. Not sure what you mean by "where the action hook should be"? For which action? – Jacob Peattie Feb 16 '18 at 7:58
  • Sorry. I meant where can I learn more on the "update" function of the Widget's class? It seems this is connected to some Javascript (AJAX, perhaps?) that passes on what the user's typed. In a way, before I asked this, I was searching on how to hook to this function and to check "if username has been updated". I think this is powerful, because my theme will grow into enriching the customizer and detecting when the user has updated / interacted with the widget / widget's settings is powerful, but I can't find anything on it. – Jonathan Guerin Feb 16 '18 at 8:01
  • Additionally, this works on the front-end, but when I update the username, it doesn't create the right preview and it just shows the error it'd give if it failed the request (empty JSON). – Jonathan Guerin Feb 16 '18 at 8:13
  • Sorry I don't see where a preview's supposed to appear on the widget? – Jacob Peattie Feb 16 '18 at 9:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.