: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
} elseif ( isset( $tweet['full_text'] ) ) {
$tweet_text = $tweet['full_text'];
} elseif ( isset( $tweet['text'] ) ) {
$tweet_text = $tweet['text'];
$tweet_text = ' ' . preg_replace( '/[,.!?:;"]+/', '', $tweet_text ) . ' '; // spaces added so that we can use strpos instead of regex to find words
$tweet_text = strtolower( preg_replace( '/[\n]+/', ' ', $tweet_text ) );
// don't bother with filtering process if both filters are empty
if ( ! empty( $good_text ) || ! empty( $bad_text ) ) {
if ( $filter_and_or == 'and' && ! empty( $good_text ) && ! empty( $bad_text ) ) {
if ( CTF_Feed::hasGoodText( $good_text, $includewords_any_all, $tweet_text, true ) && CTF_Feed::hasNoBadText( $bad_text, 'any', $tweet_text, true ) ) {
if ( CTF_Feed::hasGoodText( $good_text, $includewords_any_all, $tweet_text, false ) || CTF_Feed::hasNoBadText( $bad_text, $excludewords_any_all, $tweet_text, false ) ) {
$return = apply_filters( 'ctf_filter_out_tweet', $return, $tweet_text, $tweet );
public static function hasGoodText( $good_text, $any_or_all, $tweet_text, $default )
if ( empty( $good_text ) ) { // don't factor in the includewords if there aren't any
$encoded_text = ' ' . str_replace( array( '+', '%0A' ), ' ', urlencode( str_replace( array( '#', '@' ), array( ' HASHTAG', ' MENTION' ), strtolower( $tweet_text ) ) ) ) . ' ';
if ( $any_or_all == 'any' ) {
// as soon as we find any of the includewords, stop searching and return true
foreach ( $good_text as $good ) {
$converted_includeword = trim( str_replace('+', ' ', urlencode( str_replace( array( '#', '@' ), array( ' HASHTAG', ' MENTION' ), strtolower( $good ) ) ) ) );
if ( preg_match('/\b'.$converted_includeword.'\b/i', $encoded_text, $matches ) ) {
// if foreach finishes without finding any matches
// to make sure all of the includewords are present, keep a count of
// how many of the words are detected and compare it to the number that's needed
$number_of_good_text_to_look_for = count( $good_text );
foreach ( $good_text as $good ) {
$converted_includeword = trim( str_replace('+', ' ', urlencode( str_replace( array( '#', '@' ), array( ' HASHTAG', ' MENTION' ), strtolower( $good ) ) ) ) );
if ( preg_match('/\b'.$converted_includeword.'\b/i', $encoded_text, $matches ) ) {
if ( $good_text_matches >= $number_of_good_text_to_look_for ) {
* if a filter is applied to this feed, check and see if this tweet needs to
* or contains the excludewords text
* @param $bad_text array words the tweet cannot have to be included in the feed
* @param $any_or_all enum whether any or all of the bad text words need to be included
* @param $tweet_text string content text of the tweet
* @param $default bool the default return type if nothing is set
* @return bool whether the tweet meets the requirements for having no bad text
public static function hasNoBadText( $bad_text, $any_or_all, $tweet_text, $default )
if ( empty( $bad_text ) ) { // don't factor in the excludewords if there aren't any
$encoded_text = ' ' . str_replace( array( '+', '%0A' ), ' ', urlencode( str_replace( array( '#', '@' ), array( ' HASHTAG', ' MENTION' ), strtolower( $tweet_text ) ) ) ) . ' ';
if ( $any_or_all == 'any' ) {
// as soon as we find any of the excludewords, stop searching and return false
foreach ( $bad_text as $bad ) {
$converted_excludeword = trim( str_replace('+', ' ', urlencode( str_replace( array( '#', '@' ), array( ' HASHTAG', ' MENTION' ), strtolower( $bad ) ) ) ) );
if ( preg_match('/\b'.$converted_excludeword.'\b/i', $encoded_text, $matches ) ) {
// if foreach finishes without finding any matches
// under this circumstance, all excludewords need to be present to remove
// the tweet so a count is kept and compared to the number of words
$number_of_bad_text_to_look_for = count( $bad_text );
foreach ( $bad_text as $bad ) {
$converted_excludeword = trim( str_replace('+', ' ', urlencode( str_replace( '#', 'HASHTAG', strtolower( $bad ) ) ) ) );
if ( preg_match('/\b'.$converted_excludeword.'\b/i', $encoded_text, $matches ) ) {
if ( $bad_text_matches >= $number_of_bad_text_to_look_for ) {
public static function removeStringFromText( $string, $text, $expanded_url = '' ) {
$exceptions = array( '://fb.me/' );
if ( $expanded_url !== '' ) {
foreach ( $exceptions as $exception ) {
if ( strpos( $expanded_url, $exception ) !== false ) {
return str_replace( $string, $expanded_url, $text );
return str_replace( $string, '', $text );
public static function maybeGetTwitterCardData( $url, $id ) {
if ( ! ctf_is_pro_version() ) {
$url_key = str_replace('&','038',$url);
$url_key = preg_replace( '~[^a-zA-Z0-9]+~', '', $url_key );
$tc_data = get_option( 'ctf_twitter_cards', array() );
if ( isset( $tc_data[ $url_key ] ) ) {
$card = $tc_data[ $url_key ];
} elseif ( isset( $tc_data[ $id ] ) ) {
if ( $card && ! isset( $card['local'] ) ) {
$card['local'] = CTF_Twitter_Card_Manager::add_local_image( $card, $id );
public function set_next_pages( $next_pages ) {
$this->next_pages = $next_pages;
private function merge_posts( $post_sets, $settings ) {
$settings['sortby'] = isset( $settings['sortby'] ) ? $settings['sortby'] : 'date';
if ( $settings['sortby'] === 'alternate' ) {
// don't bother merging posts if there is only one post set
if ( isset( $post_sets[1] ) ) {
$min_cycles = max( 1, (int)$settings['num'] );
for( $i = 0; $i <= $min_cycles; $i++ ) {
foreach ( $post_sets as $post_set ) {
if ( isset( $post_set[ $i ] ) && isset( $post_set[ $i ]['id'] ) ) {
$merged_posts[] = $post_set[ $i ];
$merged_posts = isset( $post_sets[0] ) ? $post_sets[0] : array();
} elseif ( $settings['sortby'] === 'api' ) {
if ( isset( $post_sets[0] ) ) {
foreach ( $post_sets as $post_set ) {
$merged_posts = array_merge( $merged_posts, $post_set );
// don't bother merging posts if there is only one post set
if ( isset( $post_sets[1] ) ) {
foreach ( $post_sets as $post_set ) {
$merged_posts = array_merge( $merged_posts, $post_set );
$merged_posts = isset( $post_sets[0] ) ? $post_sets[0] : array();
* Connects to the Instagram API and records returned data
* @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.2/5.3 added logic to append bio data from the related
* connected account if not available in the API response
public function set_remote_header_data( $settings, $feed_types_and_terms ) {
$this->header_data = false;
$endpoint = 'accountlookup';
if ( $settings['type'] === 'usertimeline' ) {
$endpoint = 'userslookup';
// Only can be set in the options page
$request_settings = array(
'consumer_key' => $settings['consumer_key'],
'consumer_secret' => $settings['consumer_secret'],
'access_token' => $settings['access_token'],
'access_token_secret' => $settings['access_token_secret'],
$get_fields = $this->setGetFieldsArray( $endpoint, $settings['screenname'], $settings );
$twitter_connect = new CtfOauthConnect( $request_settings, $endpoint );
$twitter_connect->setUrlBase();
$twitter_connect->setGetFields( $get_fields );
$twitter_connect->setRequestMethod( $settings['request_method'] );
$request_results = $twitter_connect->performRequest();
$header_json = isset( $request_results->json ) ? $request_results->json : false;
$header_data = json_decode( $header_json, true );
$this->header_data = $header_data;
protected function setGetFieldsArray( $end_point, $feed_term, $settings )
$get_fields['tweet_mode'] = 'extended';
if ( $feed_type === 'usertimeline' ) {
if ( ! empty ( $feed_term ) ) {
$get_fields['screen_name'] = $feed_term;
if ( $settings['includereplies'] || $settings['selfreplies'] ) {
$get_fields['exclude_replies'] = 'false';
$get_fields['exclude_replies'] = 'true';
if ( $feed_type === 'hometimeline' ) {
if ( $settings['includereplies'] || $settings['selfreplies'] ) {
$get_fields['exclude_replies'] = 'false';
$get_fields['exclude_replies'] = 'true';
if ( $feed_type === 'search' || $feed_type === 'hashtag' ) {
$get_fields['q'] = $feed_term;
if ( $feed_type === 'lists' ) {
if ( ! empty ( $feed_term ) ) {
$get_fields['list_id'] = $feed_term;
if ( $feed_type === 'userslookup' ) {
if ( ! empty ( $feed_term ) ) {
$get_fields['screen_name'] = $feed_term;
* Stores feed data in a transient for a specified time
* @param bool $save_backup
* @since 2.0/5.1 duplicate posts removed
public function cache_feed_data( $cache_time, $save_backup = true ) {
if ( ! empty( $this->post_data ) || ! empty( $this->next_pages ) ) {
$this->remove_duplicate_posts();
$this->trim_posts_to_max();
'data' => $this->post_data,
'pagination' => $this->next_pages
$this->cache->set_transient( $this->regular_feed_transient_name, wp_json_encode( $to_cache ), $cache_time );
update_option( $this->backup_feed_transient_name, wp_json_encode( $to_cache ), false );
$this->add_report( 'no data not caching' );
* Stores header data for a specified time as a transient
* @param bool $save_backup
public function cache_header_data( $cache_time, $save_backup = true ) {
if ( $this->header_data ) {
$this->cache->set_transient( $this->header_transient_name, wp_json_encode( $this->header_data ), $cache_time );
update_option( $this->backup_header_transient_name, wp_json_encode( $this->header_data ), false );
* Determines if pagination can and should be used based on settings and available feed data
public function should_use_pagination( $settings, $offset = 0 ) {
if ( $settings['minnum'] < 1 ) {
$this->add_report( 'minnum too small' );
$posts_available = count( $this->post_data ) - ($offset + $settings['minnum']);
$show_loadmore_button_by_settings = ($settings['showbutton'] == 'on' || $settings['showbutton'] == 'true' || $settings['showbutton'] == true ) && $settings['showbutton'] !== 'false';
if ( $show_loadmore_button_by_settings ) {
if ( $posts_available > 0 ) {
$this->add_report( 'do pagination, posts available' );
$pages = $this->next_pages;
if ( $pages && ! $this->should_use_backup() ) {
foreach ( $pages as $page ) {
if ( ! empty( $page ) ) {
$this->add_report( 'no pagination, no posts available' );
* Overwritten in the Pro version
* @param $feed_types_and_terms
public function get_first_user( $feed_types_and_terms ) {
if ( isset( $feed_types_and_terms['users'][0] ) ) {
return $feed_types_and_terms['users'][0][1];
* Adds recorded strings to an array
public function add_report( $to_add ) {
$this->report[] = $to_add;
public function get_report() {
* Used for filtering a single API request worth of posts
* Overwritten in the Pro version
* @param array $post_set a single set of post data from the api
protected function filter_posts( $post_set, $settings = array() ) {
// array_unique( $post_set, SORT_REGULAR);
protected function handle_no_posts_found( $settings = array(), $feed_types_and_terms = array() ) {
protected function remove_duplicate_posts() {
$posts = $this->post_data;
$non_duplicate_posts = array();
foreach ( $posts as $post ) {
$post_id = CTF_Parse::get_post_id( $post );
if ( ! in_array( $post_id, $ids_in_feed, true ) ) {
$ids_in_feed[] = $post_id;
$non_duplicate_posts[] = $post;
$this->add_report( 'removed duplicates: ' . implode(', ', $removed ) );
$this->set_post_data( $non_duplicate_posts );
public static function parse_api_card_data( $data ) {
'twitter:description' => '',
if ( ! empty( $data['name'] ) ) {
$return['twitter:card'] = $data['name'];
if ( ! empty( $data['url'] ) ) {
$return['twitter:site'] = $data['url'];
if ( ! empty( $data['binding_values']['title']['string_value'] ) ) {
$return['twitter:title'] = $data['binding_values']['title']['string_value'];
if ( ! empty( $data['binding_values']['description']['string_value'] ) ) {
$return['twitter:description'] = $data['binding_values']['description']['string_value'];
if ( ! empty( $data['binding_values']['thumbnail_image_large']['image_value']['url'] ) ) {
$return['twitter:image'] = $data['binding_values']['thumbnail_image_large']['image_value']['url'];
} elseif ( ! empty( $data['binding_values']['thumbnail_image']['image_value']['url'] ) ) {
$return['twitter:image'] = $data['binding_values']['thumbnail_image']['image_value']['url'];
} elseif ( ! empty( $data['binding_values']['thumbnail_image_small']['image_value']['url'] ) ) {
$return['twitter:image'] = $data['binding_values']['thumbnail_image_small']['image_value']['url'];