: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Class SB_Instagram_Feed
* Retrieves data and generates the html for each feed. The
* "display_instagram" function in the if-functions.php file
* is where this class is primarily used.
use TwitterFeed\CtfCache;
use TwitterFeed\CTF_Parse;
use TwitterFeed\V2\CtfOauthConnect;
if ( ! defined( 'ABSPATH' ) ) {
private $regular_feed_transient_name;
private $header_transient_name;
private $backup_feed_transient_name;
private $backup_header_transient_name;
private $should_paginate;
private $image_ids_post_set;
private $should_use_backup;
protected $one_post_found;
* SB_Instagram_Feed constructor.
* @param string $transient_name ID of this feed
* generated in the SB_Instagram_Settings class
public function __construct( $transient_name ) {
$this->regular_feed_transient_name = $transient_name;
$header_transient_name = str_replace( 'ctf_', 'ctf_header_', $transient_name );
$header_transient_name = substr($header_transient_name, 0, 44);
$this->header_transient_name = $header_transient_name;
$this->post_data = array();
$this->next_pages = array();
$this->should_paginate = true;
// this is a count of how many api calls have been made for each feed
// By default the limit is 10
$this->num_api_calls = 0;
$this->max_api_calls = 10;
$this->should_use_backup = false;
// used for errors and the sbi_debug report
$this->resized_images = array();
$this->one_post_found = false;
$this->cache = new CtfCache( $transient_name, 600, 1 );
public function set_cache( $cache_seconds, $settings ) {
$feed_id = $this->regular_feed_transient_name;
$this->cache = new CtfCache( $feed_id, $cache_seconds, $feed_page );
public function get_post_data() {
public function set_post_data( $post_data ) {
$this->post_data = $post_data;
public function set_resized_images( $resized_image_data ) {
$this->resized_images = $resized_image_data;
public function get_next_pages() {
return $this->next_pages;
public function get_resized_images() {
return $this->resized_images;
* Checks the database option related the transient expiration
* to ensure it will be available when the page loads
public function regular_cache_exists() {
//Check whether the cache transient exists in the database and is available for more than one more minute
if ( strpos( $this->regular_feed_transient_name, 'ctf_!' ) !== false ) {
$this->transient_data = $this->cache->get_transient($this->transient_name);
$transient_exists = $this->cache->get_persistent( $this->regular_feed_transient_name );
$transient_exists = $this->cache->get_transient( $this->regular_feed_transient_name );
return $transient_exists;
* Checks the database option related the header transient
* expiration to ensure it will be available when the page loads
public function regular_header_cache_exists() {
$header_transient = $this->cache->get_transient( $this->header_transient_name );
return $header_transient;
public function should_use_backup() {
return $this->should_use_backup || empty( $this->post_data );
* The header is only displayed when the setting is enabled and
* an account has been connected
* Overwritten in the Pro version
* @param array $settings settings specific to this feed
* @param array $feed_types_and_terms organized settings related to feed data
* (ex. 'user' => array( 'smashballoon', 'custominstagramfeed' )
public function need_header( $settings, $feed_types_and_terms ) {
$showheader = ($settings['showheader'] === 'on' || $settings['showheader'] === 'true' || $settings['showheader'] === true);
return ($showheader && isset( $feed_types_and_terms['users'] ));
* Use the transient name to retrieve cached data for header
public function set_header_data_from_cache() {
$header_cache = $this->cache->get_transient( $this->header_transient_name );
$header_cache = json_decode( $header_cache, true );
if ( ! empty( $header_cache ) ) {
$this->header_data = $header_cache;
public function set_header_data( $header_data ) {
$this->header_data = $header_data;
public function get_header_data() {
return $this->header_data;
* Sets the post data, pagination data, shortcode atts used (cron cache),
* and timestamp of last retrieval from transient (cron cache)
* @param array $atts available for cron caching
public function set_post_data_from_cache( $atts = array() ) {
if ( strpos( $this->regular_feed_transient_name, 'ctf_!' ) !== false ) {
$transient_data = $this->cache->get_persistent( $this->regular_feed_transient_name );
$transient_data = $this->cache->get_transient( $this->regular_feed_transient_name );
$transient_data = json_decode( $transient_data, true );
$post_data = isset( $transient_data['data'] ) ? $transient_data['data'] : array();
if ( empty( $post_data ) ) {
$post_data = isset( $transient_data[0]['id_str'] ) ? $transient_data : array();
$this->post_data = $post_data;
$this->next_pages = isset( $transient_data['pagination'] ) ? $transient_data['pagination'] : array();
if ( isset( $transient_data['atts'] ) ) {
$this->transient_atts = $transient_data['atts'];
$this->last_retrieve = $transient_data['last_retrieve'];
* Checks to see if there are enough posts available to create
* the current page of the feed
public function need_posts( $num, $offset = 0 ) {
$num_existing_posts = is_array( $this->post_data ) ? count( $this->post_data ) : 0;
$num_needed_for_page = (int)$num + (int)$offset;
($num_existing_posts < $num_needed_for_page) ? $this->add_report( 'need more posts' ) : $this->add_report( 'have enough posts' );
return ($num_existing_posts < $num_needed_for_page);
* Checks to see if there are additional pages available for any of the
* accounts in the feed and that the max conccurrent api request limit
public function can_get_more_posts() {
$one_type_and_term_has_more_ages = $this->next_pages !== false;
$max_concurrent_api_calls_not_met = $this->num_api_calls < $this->max_api_calls;
$max_concurrent_api_calls_not_met ? $this->add_report( 'max conccurrent requests not met' ) : $this->add_report( 'max concurrent met' );
$one_type_and_term_has_more_ages ? $this->add_report( 'more pages available' ) : $this->add_report( 'no next page' );
return ($one_type_and_term_has_more_ages && $max_concurrent_api_calls_not_met);
* Appends one filtered API request worth of posts for each feed term
* @param array $feed_types_and_terms organized settings related to feed data
* (ex. 'user' => array( 'smashballoon', 'custominstagramfeed' )
* @param array $connected_accounts_for_feed connected account data for the
* @since 2.0/5.1 added logic to make a second attempt at an API connection
* @since 2.0/5.1.2 remote posts only retrieved if API requests are not
* delayed, terms shuffled if there are more than 5
* @since 2.2/5.3 added logic to refresh the access token for basic display
* accounts if needed before using it in an API request
public function add_remote_posts( $settings, $feed_types_and_terms ) {
$new_post_sets = array();
$next_pages = $this->next_pages;
* Number of posts to retrieve in each API call
* @param int Minimum number of posts needed in each API request
* @param array $settings Settings for this feed
$num = apply_filters( 'ctf_num_in_request', $settings['apinum'], $settings );
if ( ! $settings['includereplies'] && ! $settings['selfreplies'] ) {
$num = min( 200, $num * 3 );
$one_successful_connection = false;
$next_page_found = false;
$one_api_request_delayed = false;
foreach ( $feed_types_and_terms as $type => $terms ) {
if ( is_array( $terms ) && count( $terms ) > 5 ) {
foreach ( $terms as $term_and_params ) {
$this->add_report( 'num ' . $num );
$term = $term_and_params['term'];
$params = array_merge( $params, $term_and_params['params'] );
if ( ! empty( $next_pages[ $term . '_' . $type ] ) ) {
$params['count'] = min( $params['count'] + 1, 200 );
$params['max_id'] = $next_pages[ $term . '_' . $type ];
$params['tweet_mode'] = 'extended';
if ( $type === 'usertimeline' ) {
if ( ! empty ( $term ) ) {
$params['screen_name'] = $term;
//if ( $settings['includereplies'] || $settings['selfreplies'] ) {
// $params['exclude_replies'] = 'false';
$params['exclude_replies'] = 'true';
if ( $type === 'hometimeline' ) {
//if ( $settings['includereplies'] || $settings['selfreplies'] ) {
// $params['exclude_replies'] = 'false';
$params['exclude_replies'] = 'true';
if ( $type === 'search' || $type === 'hashtag' ) {
if ( $type === 'lists' ) {
if ( ! empty ( $term ) ) {
$params['list_id'] = $term;
if ( $type === 'userslookup' ) {
if ( ! empty ( $term ) ) {
$params['screen_name'] = $term;
// max_id parameter should only be included for the second set of posts
$api_obj = $this->apiConnectionResponse( $params, $type, $settings );
$raw_tweet_data = json_decode( $api_obj->json , $assoc = true );
if ( isset( $raw_tweet_data['errors'][0] ) ) {
if ( empty( $api_obj ) ) {
$api_obj = new \stdClass();
$api_obj->api_error_no = $raw_tweet_data['errors'][0]['code'];
$api_obj->api_error_message = $raw_tweet_data['errors'][0]['message'];
$this->add_report( 'error ' . $api_obj->api_error_no . ' ' . $api_obj->api_error_message );
$next_pages[ $term . '_' . $type ] = false;
if ( isset( $raw_tweet_data['statuses'] ) ) {
$raw_tweet_data = $raw_tweet_data['statuses'];
if ( ! empty( $params['max_id'] ) ) {
// remove the first tweet as it was returned in the paginated request
array_shift( $raw_tweet_data );
if ( count( $raw_tweet_data ) > 0 ) {
if ( isset( $raw_tweet_data[ count( $raw_tweet_data ) - 1 ] ) ) {
$last_tweet = $raw_tweet_data[ count( $raw_tweet_data ) - 1 ];
$this->add_report( 'normal last tweet' );
$last_tweet = $raw_tweet_data[0];
$this->add_report( 'backup last tweet' );
$working_tweet_set = CTF_Feed::reduceTweetSetData( $raw_tweet_data );
$working_tweet_set = CTF_Feed::filterTweetSet( $working_tweet_set, $settings );
if ( count( $working_tweet_set ) > 1 ) {
$last_tweet = $raw_tweet_data[ count( $working_tweet_set ) - 1 ];
$next_pages[ $term . '_' . $type ] = CTF_Parse_Pro::get_tweet_id( $last_tweet );
$new_post_sets[] = $working_tweet_set;
$this->add_report( 'no raw tweet data' );
$this->add_report( $api_obj->json );
$next_pages[ $term . '_' . $type ] = false;