File: /home/adltc/public_html/wp-content/plugins/feeds-for-youtube/inc/SBY_Feed.php
<?php
namespace SmashBalloon\YouTubeFeed;
use Smashballoon\Customizer\Feed_Builder;
use SmashBalloon\YouTubeFeed\Services\AdminAjaxService;
class SBY_Feed
{
/**
* @var string
*/
protected $regular_feed_transient_name;
/**
* @var string
*/
private $header_transient_name;
/**
* @var string
*/
private $backup_feed_transient_name;
/**
* @var string
*/
private $backup_header_transient_name;
protected $channels_data;
/**
* @var array
*/
private $post_data;
/**
* @var
*/
private $header_data;
/**
* @var array
*/
protected $next_pages;
/**
* @var array
*/
private $transient_atts;
/**
* @var int
*/
private $last_retrieve;
/**
* @var bool
*/
private $should_paginate;
/**
* @var int
*/
private $num_api_calls;
/**
* @var int
*/
private $max_api_calls;
/**
* @var array
*/
protected $image_ids_post_set;
/**
* @var array
*/
public $post_ids_with_no_details;
/**
* @var bool
*/
private $should_use_backup;
/**
* @var array
*/
private $report;
private $successful_video_api_request_made;
private $do_page_cache_all;
protected $channel_id_avatars;
/**
* SBY_Feed constructor.
*
* @param string $transient_name ID of this feed
* generated in the SBY_Settings class
*/
public function __construct( $transient_name ) {
$this->regular_feed_transient_name = $transient_name;
$this->backup_feed_transient_name = SBY_BACKUP_PREFIX . $transient_name;
$sby_header_transient_name = str_replace( 'sby_', 'sby_header_', $transient_name );
$sby_header_transient_name = substr($sby_header_transient_name, 0, 44);
$this->header_transient_name = $sby_header_transient_name;
$this->backup_header_transient_name = SBY_BACKUP_PREFIX . $sby_header_transient_name;
$this->channels_data = array();
$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
// type and term.
// By default the limit is 10
$this->num_api_calls = 0;
$this->max_api_calls = apply_filters( 'sby_max_concurrent_api_calls', 10 );
$this->should_use_backup = false;
// used for errors and the sby_debug report
$this->report = array();
$this->successful_video_api_request_made = false;
$this->post_ids_with_no_details = array();
$this->do_page_cache_all = false;
}
/**
* @return string
*
* @since 2.0
*/
public function get_feed_id() {
return str_replace( '*', '', $this->regular_feed_transient_name );
}
/**
* @return array
*
* @since 1.0
*/
public function get_post_data() {
return $this->post_data;
}
/**
* @return array
*
* @since 1.0
*/
public function set_post_data( $post_data ) {
$this->post_data = $post_data;
}
public function get_misc_data( $feed_id, $posts ) {
return array();
}
/**
* @return array
*
* @since 1.0
*/
public function get_next_pages() {
return $this->next_pages;
}
public function are_posts_with_no_details() {
return (! empty( $this->post_ids_with_no_details ));
}
/**
* Uses the settings to determine if avatars are going to be used.
* Can make feed creation faster if not.
*
* @param $settings
*
* @return bool
*
* @since 2.0
*/
public function need_avatars( $settings ) {
if ( isset( $settings['type'] ) && $settings['type'] === 'hashtag' ) {
return false;
} elseif ( isset( $settings['disablelightbox'] ) && ($settings['disablelightbox'] === 'true' || $settings['disablelightbox'] === 'on') ) {
return false;
} else {
return true;
}
}
public function maybe_add_live_html( $post ) {
if ( ! isset( $post['iframe'] ) ) {
return;
}
echo '<iframe id="sby_live_player'. esc_attr( $post['iframe'] ) .'" width="640" height="360" data-origwidth="640" data-origheight="360" data-relstop="1" src="https://www.youtube.com/embed/live_stream?enablejsapi=1&channel='. esc_attr( $post['iframe'] ) .'&rel=0&modestbranding=1&autoplay=0&cc_load_policy=0&iv_load_policy=1&loop=0&fs=1&playsinline=0&autohide=2&theme=dark&color=red&controls=1&" class="sby_live_player" title="YouTube player" allow="autoplay; encrypted-media" allowfullscreen data-no-lazy="1" data-skipgform_ajax_framebjll=""></iframe>';
}
/**
* Available avatars are added to the feed as an attribute so they can be used in the lightbox
*
* @param $connected_accounts_in_feed
* @param $feed_types_and_terms
*
* @since 2.0
*/
public function set_up_feed_avatars( $connected_accounts_in_feed, $feed_types_and_terms ) {
foreach ( $feed_types_and_terms as $type => $terms ) {
foreach ( $terms as $term_and_params ) {
$existing_channel_cache = $this->get_channel_cache( $term_and_params['term'], true );
$avatar = SBY_Parse::get_avatar( $existing_channel_cache );
$channel_id = SBY_Parse::get_channel_id( $existing_channel_cache );
$this->set_avatar( $channel_id, $avatar );
}
}
}
/**
* Creates a key value pair of the username and the url of
* the avatar image
*
* @param $name
* @param $url
*
* @since 2.0
*/
public function set_avatar( $channel_id, $url ) {
$this->channel_id_avatars[ $channel_id ] = $url;
}
/**
* @return array
*/
public function get_channel_id_avatars() {
return $this->channel_id_avatars;
}
/**
* Checks the database option related the transient expiration
* to ensure it will be available when the page loads
*
* @return bool
*
* @since 2.0/4.0
*/
public function regular_cache_exists() {
//Check whether the cache transient exists in the database and is available for more than one more minute
$transient_exists = 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
*
* @return bool
*
* @since 1.0
*/
public function regular_header_cache_exists() {
$header_transient = get_transient( $this->header_transient_name );
return $header_transient;
}
public function get_channel_cache( $channel, $force_get_cache = false ) {
if ( $this->is_pageable() && ! $force_get_cache ) {
return false;
}
$maybe_cache = get_option( SBY_CHANNEL_CACHE_PREFIX . $channel );
if ( $maybe_cache !== false ) {
$maybe_cache = json_decode( $maybe_cache, true );
}
return $maybe_cache;
}
public function set_channel_cache( $channel, $cache ) {
if ( is_array( $cache ) ) {
$cache = wp_json_encode( $cache );
}
update_option( SBY_CHANNEL_CACHE_PREFIX . $channel, $cache, false );
}
/**
* @return bool
*
* @since 1.0
*/
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', 'customyoutubefeed' )
*
* @return bool
*
* @since 1.0
*/
public function need_header( $settings, $feed_types_and_terms ) {
if ( ! empty( $settings['headerchannel'] ) || isset( $feed_types_and_terms['channels'] ) ) {
return true;
}
return false;
}
/**
* Use the transient name to retrieve cached data for header
*
* @since 1.0
*/
public function set_header_data_from_cache() {
$header_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;
}
/**
* @since 1.0
*/
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
*
* @since 1.0
*/
public function set_post_data_from_cache( $atts = array() ) {
$transient_data = get_transient( $this->regular_feed_transient_name );
$transient_data = json_decode( $transient_data, true );
if ( $transient_data ) {
$post_data = isset( $transient_data['data'] ) ? $transient_data['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'];
}
}
}
/**
* Sets post data from a permanent database backup of feed
* if it was created
*
* @since 1.0
*/
public function maybe_set_post_data_from_backup() {
$args = array(
'post_type' => SBY_CPT,
'post_status' => array( 'publish', 'pending', 'draft' ),
'orderby' => 'date',
'order' => 'DESC',
'posts_per_page' => 50,
'meta_query' => array(
array(
'value' => sby_strip_after_hash( $this->regular_feed_transient_name ),
'key' => 'sby_feed_id'
)
)
);
$feed_videos = new \WP_Query( $args );
if ( $feed_videos->have_posts() ) {
$posts = array();
while ( $feed_videos->have_posts() ) {
$feed_videos->the_post();
$json = get_post_meta( get_the_ID(), 'sby_json', true );
if ( $json ) {
$posts[] = json_decode( $json, true );
}
}
wp_reset_postdata();
$this->post_data = $posts;
return true;
} else {
$this->add_report( 'no backup post data found' );
wp_reset_postdata();
return false;
}
}
/**
* Sets header data from a permanent database backup of feed
* if it was created
*
* @since 1.0
*/
public function maybe_set_header_data_from_backup() {
$backup_header_data = get_option( $this->backup_header_transient_name, false );
if ( ! empty( $backup_header_data ) ) {
$backup_header_data = json_decode( $backup_header_data, true );
$this->header_data = $backup_header_data;
return true;
} else {
$this->add_report( 'no backup header data found' );
return false;
}
}
/**
* Returns recorded image IDs for this post set
* for use with image resizing
*
* @return array
*
* @since 1.0
*/
public function get_image_ids_post_set() {
return $this->image_ids_post_set;
}
/**
* Cron caching needs additional data saved in the transient
* to work properly. This function checks to make sure it's present
*
* @return bool
*
* @since 1.0
*/
public function need_to_start_cron_job() {
return (( ! empty( $this->post_data ) && ! isset( $this->transient_atts )) || empty( $this->post_data ));
}
/**
* Checks to see if there are enough posts available to create
* the current page of the feed
*
* @param int $num
* @param int $offset
*
* @return bool
*
* @since 1.0
*/
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
* has not been reached
*
* @return bool
*
* @since 1.0
*/
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);
}
public function get_play_list_for_term( $type, $term, $connected_account_for_term, $params ) {
if ( $type === 'search' || $type === 'live' ) {
return false;
}
$existing_channel_cache = $this->get_channel_cache( $term );
if ( $existing_channel_cache ) {
$this->channels_data[ $term ] = $existing_channel_cache;
}
if ( empty( $this->channels_data[ $term ] ) ) {
if ( $connected_account_for_term['expires'] < time() + 5 ) {
$error_message = '<p><b>' . __( 'Reconnect to YouTube to show this feed.', 'feeds-for-youtube' ) . '</b></p>';
$error_message .= '<p>' . __( 'To create a new feed, first connect to YouTube using the "Connect to YouTube to Create a Feed" button on the settings page and connect any account.', 'feeds-for-youtube' ) . '</p>';
if ( current_user_can( 'manage_youtube_feed_options' ) ) {
$error_message .= '<a href="' . admin_url( 'admin.php?page=youtube-feed-settings' ) . '" target="blank" rel="noopener nofollow">' . __( 'Reconnect in the YouTube Feeds Settings Area', 'feeds-for-youtube' ) . '</a>';
}
global $sby_posts_manager;
$sby_posts_manager->add_frontend_error( 'accesstoken', $error_message );
$sby_posts_manager->add_error( 'accesstoken', array( 'Trying to connect a new account', $error_message ) );
return false;
}
$channel_data = array();
$api_connect_channels = $this->make_api_connection( $connected_account_for_term, 'channels', $params );
$this->add_report( 'channel api call made for ' . $term . ' - ' . $type );
$api_connect_channels->connect();
if ( ! $api_connect_channels->is_wp_error() && ! $api_connect_channels->is_youtube_error() ) {
$channel_data = $api_connect_channels->get_data();
$channel_id = SBY_Parse::get_channel_id( $channel_data );
$this->set_channel_cache( $channel_id, $channel_data );
if ( isset( $params['channel_name'] ) ) {
sby_set_channel_id_from_channel_name( $params['channel_name'], $channel_id );
$this->set_channel_cache( $params['channel_name'], $channel_data );
}
$params = array( 'channel_id' => $channel_id );
$this->channels_data[ $channel_id ] = $channel_data;
$this->channels_data[ $term ] = $channel_data;
} else {
if ( ! $api_connect_channels->is_wp_error() ) {
$return = SBY_API_Connect::handle_youtube_error( $api_connect_channels->get_data(), $connected_account_for_term );
if ( $return && isset( $return['access_token'] ) ) {
$connected_account_for_term['access_token'] = $return['access_token'];
$connected_accounts_for_feed[ $term ]['access_token'] = $return['access_token'];
$connected_account_for_term['expires'] = $return['expires_in'] + time();
$connected_accounts_for_feed[ $term ]['expires'] = $return['expires_in'] + time();
sby_update_or_connect_account( $connected_account_for_term );
$this->add_report( 'refreshing access token for ' . $connected_account_for_term['channel_id'] );
$sby_api_connect_channel = $this->make_api_connection( $connected_account_for_term, 'channels', $params );
$sby_api_connect_channel->connect();
if ( ! $sby_api_connect_channel->is_youtube_error() ) {
$channel_data = $sby_api_connect_channel->get_data();
$channel_id = SBY_Parse::get_channel_id( $channel_data );
$this->set_channel_cache( $channel_id, $channel_data );
if ( isset( $params['channel_name'] ) ) {
sby_set_channel_id_from_channel_name( $params['channel_name'], $channel_id );
$this->set_channel_cache( $params['channel_name'], $channel_data );
}
$this->channels_data[ $channel_id ] = $channel_data;
$this->channels_data[ $term ] = $channel_data;
}
} else {
$this->add_report( 'error connecting to channel' );
}
} else {
$api_connect_channels->handle_wp_remote_get_error( $api_connect_channels->get_data() );
}
}
}
$playlist = isset( $this->channels_data[ $term ]['items'][0]['contentDetails']['relatedPlaylists']['uploads'] ) ? $this->channels_data[ $term ]['items'][0]['contentDetails']['relatedPlaylists']['uploads'] : false;
return $playlist;
}
public function maybe_refresh_token( $term, $connected_account_for_term ) {
return $connected_account_for_term;
}
public function is_efficient_type( $type ) {
return in_array( $type, array( 'playlist', 'channel' ), true );
}
public function requires_workaround_connection( $type ) {
return false;
}
public function make_workaround_connection( $connected_account_for_term, $type, $params, $feed_id = '' ) {
return $this->make_api_connection( $connected_account_for_term, $type, $params );
}
/**
* Appends one filtered API request worth of posts for each feed term
*
* @param $settings
* @param array $feed_types_and_terms organized settings related to feed data
* (ex. 'user' => array( 'smashballoon', 'customyoutubefeed' )
* @param array $connected_accounts_for_feed connected account data for the
* feed types and terms
*
* @since 1.0
*/
public function add_remote_posts( $settings, $feed_types_and_terms, $connected_accounts_for_feed ) {
$new_post_sets = array();
$next_pages = $this->next_pages;
global $sby_posts_manager;
$api_requests_delayed = $sby_posts_manager->are_current_api_request_delays();
/**
* 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
*
* @since 1.0
*/
$num = apply_filters( 'sby_num_in_request', (int)$settings['num'], $settings );
$params = array(
'num' => $num
);
$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 ) {
shuffle( $terms );
}
foreach ( $terms as $term_and_params ) {
$term = $term_and_params['term'];
$params = array_merge( $params, $term_and_params['params'] );
$connected_accounts_for_feed[ $term ] = $this->maybe_refresh_token( $term, $connected_accounts_for_feed[ $term ] );
$connected_account_for_term = $connected_accounts_for_feed[ $term ];
$play_list = $this->get_play_list_for_term( $type, $term, $connected_account_for_term, $params );
if ( ! empty( $next_pages[ $term . '_' . $type ] ) ) {
$params['nextPageToken'] = $next_pages[ $term . '_' . $type ];
}
if ( $this->is_pageable() ) {
$this->add_remote_pageable_posts( $settings, $feed_types_and_terms, $connected_accounts_for_feed );
} else {
$this->add_remote_non_pageable( $settings, $feed_types_and_terms, $connected_accounts_for_feed );
}
if ( ! $this->is_efficient_type( $type ) && $this->is_pageable() ) {
if ( $this->requires_workaround_connection( $type ) ) {
$feed_id = !empty($settings['feed']) ? $settings['feed'] : '';
$api_connect_playlist_items = $this->make_workaround_connection( $connected_account_for_term, $type, $params, $feed_id );
$this->add_report( 'Workaround API call made for ' . $term );
} else {
if ( $play_list ) {
$params['playlist_id'] = $play_list;
$api_connect_playlist_items = $this->make_api_connection( $connected_account_for_term, 'playlistItems', $params );
} else {
$api_connect_playlist_items = $this->make_api_connection( $connected_account_for_term, $type, $params );
}
$api_connect_playlist_items->connect();
$this->add_report( 'API call made for ' . $term );
}
if ( ! $api_connect_playlist_items->is_wp_error() && ! $api_connect_playlist_items->is_youtube_error() ) {
$one_successful_connection = true;
$data = $api_connect_playlist_items->get_data();
if ( isset( $data['items'][0] ) ) {
// clear object cache after successful API connection
sby_clear_object_cache();
$post_set = $this->filter_posts( $data['items'], $settings );
$this->successful_video_api_request_made = true;
$new_post_sets[] = $post_set;
}
$next_page = $api_connect_playlist_items->get_next_page( $params );
$report = is_array( $next_page ) ? implode( ',', $next_page ) : $next_page;
$this->add_report( 'Next Page ' . $report );
if ( ! empty( $next_page ) ) {
$next_pages[ $term . '_' . $type ] = $next_page;
$next_page_found = true;
} else {
$next_pages[ $term . '_' . $type ] = false;
}
}
$this->num_api_calls++;
} else {
if ( ! $this->is_pageable() && (int)self::get_channel_status( $term ) !== 1 ) {
self::update_channel_status( $term, 1 );
$this->do_page_cache_all = true;
$play_list = $this->get_play_list_for_term( $type, $term, $connected_account_for_term, $params );
$channel_id = isset( $this->channels_data[ $term ] ) ? SBY_Parse::get_channel_id( $this->channels_data[ $term ] ) : '';
$params['channel_id'] = $channel_id;
$params['num'] = 50;
if ( $play_list ) {
$params['playlist_id'] = $play_list;
if ( ! empty( $next_pages[ $term . '_' . $type ] ) && $next_pages[ $term . '_' . $type ] !== 'rss' ) {
$params['nextPageToken'] = $next_pages[ $term . '_' . $type ];
}
self::update_channel_status( $term, 1 );
$this->do_page_cache_all = true;
$this->add_report( 'using API request to get first 50 videos' );
$api_connect_playlist_items = $this->make_api_connection( $connected_account_for_term, 'playlistItems', $params );
$api_connect_playlist_items->connect();
if ( ! $api_connect_playlist_items->is_wp_error() && ! $api_connect_playlist_items->is_youtube_error() ) {
$one_successful_connection = true;
$data = $api_connect_playlist_items->get_data();
if ( isset( $data['items'][0] ) ) {
$post_set = $this->filter_posts( $data['items'], $settings );
$this->successful_video_api_request_made = true;
$new_post_sets[] = $post_set;
}
$next_pages[ $term . '_' . $type ] = false;
}
} else {
$this->add_report( 'no first playlist' );
}
} elseif ( ! $this->is_pageable()
&& isset( $params['channel_id'] )
&& (! isset( $next_pages[ $term . '_' . $type ] ) || $next_pages[ $term . '_' . $type ] !== 'rss') ) {
$rss_connect_playlist_items = new SBY_RSS_Connect( 'playlistItems', $params );
$this->add_report( 'RSS call made for ' . $term );
$rss_connect_playlist_items->connect();
$one_successful_connection = true;
$data = $rss_connect_playlist_items->get_data();
if ( isset( $data[0] ) ) {
$data = array(
'items' => $data
);
$post_set = $this->filter_posts( $data['items'], $settings );
$this->successful_video_api_request_made = true;
if ( count( $post_set ) > 14 ) {
if ( (int)self::get_channel_status( $term ) !== 1 ) {
$next_pages[ $term . '_' . $type ] = 'rss';
$next_page_found = true;
} else {
$this->add_report( 'RSS update only for ' . $term );
$post_set = $this->merge_cached_posts( $post_set, $term );
$next_pages[ $term . '_' . $type ] = false;
}
} else {
$next_pages[ $term . '_' . $type ] = false;
}
$new_post_sets[] = $post_set;
}
} elseif ( isset( $connected_account_for_term['rss_only'] ) ) {
$rss_connect_playlist_items = new SBY_RSS_Connect( 'playlistItems', $params );
$this->add_report( 'RSS Only call made for ' . $term );
$rss_connect_playlist_items->connect();
$one_successful_connection = true;
$data = $rss_connect_playlist_items->get_data();
$next_pages[ $term . '_' . $type ] = false;
if ( isset( $data[0] ) ) {
$data = array(
'items' => $data
);
$post_set = $this->filter_posts( $data['items'], $settings );
$this->successful_video_api_request_made = true;
$new_post_sets[] = $post_set;
}
} else {
if ( ! $api_requests_delayed
&& (! isset( $next_pages[ $term . '_' . $type ] ) || $next_pages[ $term . '_' . $type ] !== false) ) {
$play_list = $this->get_play_list_for_term( $type, $term, $connected_account_for_term, $params );
$channel_id = isset( $this->channels_data[ $term ] ) ? SBY_Parse::get_channel_id( $this->channels_data[ $term ] ) : '';
$params['channel_id'] = $channel_id;
if ( $play_list ) {
$params['playlist_id'] = $play_list;
if ( ! empty( $next_pages[ $term . '_' . $type ] ) && $next_pages[ $term . '_' . $type ] !== 'rss' ) {
$params['nextPageToken'] = $next_pages[ $term . '_' . $type ];
}
if ( isset( $next_pages[ $term . '_' . $type ] ) && $next_pages[ $term . '_' . $type ] === 'rss' ) {
self::update_channel_status( $term, 1 );
$this->do_page_cache_all = true;
$this->add_report( 'using API request to get first 50 videos' );
} else {
$this->add_report( 'using API request to get more videos' );
}
$api_connect_playlist_items = $this->make_api_connection( $connected_account_for_term, 'playlistItems', $params );
$api_connect_playlist_items->connect();
if ( ! $api_connect_playlist_items->is_wp_error() && ! $api_connect_playlist_items->is_youtube_error() ) {
$one_successful_connection = true;
$data = $api_connect_playlist_items->get_data();
if ( isset( $data['items'][0] ) ) {
$post_set = $this->filter_posts( $data['items'], $settings );
$this->successful_video_api_request_made = true;
$new_post_sets[] = $post_set;
}
$next_page = $this->is_pageable() ? $api_connect_playlist_items->get_next_page() : false;
if ( ! empty( $next_page ) ) {
$next_pages[ $term . '_' . $type ] = $next_page;
$next_page_found = true;
} else {
$next_pages[ $term . '_' . $type ] = false;
}
}
} else {
$this->add_report( 'no first playlist' );
}
if ( ! $this->is_pageable()
&& empty( $next_pages[ $term . '_' . $type ] )
&& ! empty( $params['channel_id'] ) ) {
$this->add_report( 'using RSS to get first 15' );
$rss_connect_playlist_items = new SBY_RSS_Connect( 'playlistItems', $params );
$rss_connect_playlist_items->connect();
$one_successful_connection = true;
$data = $rss_connect_playlist_items->get_data();
if ( isset( $data[0] ) ) {
$data = array(
'items' => $data
);
$post_set = $this->filter_posts( $data['items'], $settings );
$this->successful_video_api_request_made = true;
if ( count( $post_set ) > 14 ) {
if ( (int)self::get_channel_status( $term ) !== 1 ) {
$next_pages[ $term . '_' . $type ] = 'rss';
$next_page_found = true;
} else {
$this->add_report( 'RSS Only' . $term );
$post_set = $this->merge_cached_posts( $post_set, $term );
$next_pages[ $term . '_' . $type ] = false;
}
} else {
$next_pages[ $term . '_' . $type ] = false;
}
$new_post_sets[] = $post_set;
}
} else {
$this->num_api_calls++;
}
}
}
}
}
if ( sby_is_pro() ) {
// Make another API request to add video duration to existing videos
$new_post_sets = $this->add_video_duration( $type, $new_post_sets, $connected_account_for_term, $params );
}
}
if ( ! $one_successful_connection || ($one_api_request_delayed && empty( $new_post_sets )) ) {
$this->should_use_backup = true;
}
$posts = $this->merge_posts( $new_post_sets, $settings );
$posts = $this->sort_posts( $posts, $settings );
if ( ! empty( $this->post_data ) && is_array( $this->post_data ) ) {
$posts = array_merge( $this->post_data, $posts );
}
$this->post_data = $posts;
if ( isset( $next_page_found ) && $next_page_found ) {
$this->next_pages = $next_pages;
} else {
$this->next_pages = false;
}
}
/**
* Add video durations to each videos
*
* @since 2.1
*/
public function add_video_duration( $type, $posts, $connected_account_for_term, $params ) {
if ( count( $posts ) < 1 ) {
return $posts;
}
$videos_id = array();
if ( $type == 'single' ) {
return $posts;
}
if ( $type == 'channels' || $type == 'playlist' ) {
foreach( $posts[0] as $post ) {
$videos_id[] = $post['snippet']['resourceId']['videoId'];
}
}
if ( $type == 'search' ) {
foreach( $posts[0] as $post ) {
$videos_id[] = $post['id']['videoId'];
}
}
$params['ids'] = implode( '&id=', $videos_id );
$connection = $this->make_api_connection( $connected_account_for_term, 'videosDuration', $params );
$connection->connect();
$posts_with_duration = $connection->get_data();
$posts[0] = $this->marge_duration_data_to_original_posts( $posts[0], $posts_with_duration['items'], $type );
return $posts;
}
/**
* Merge duration data to orignal posts
*
* @since 2.1
*/
public function marge_duration_data_to_original_posts( $original_posts, $new_posts, $type ) {
// map over the origional posts and add video duration to contentDetails element
$updated_posts = array_map(function( $original_post ) use ($new_posts, $type) {
if ( $type == 'search' ) {
$original_post_video_id = isset( $original_post['id']['videoId'] ) ? $original_post['id']['videoId'] : null;
} else {
$original_post_video_id = isset( $original_post['contentDetails']['videoId'] ) ? $original_post['contentDetails']['videoId'] : null;
}
if ( $original_post_video_id ) {
foreach( $new_posts as $video ) {
if ( $video['id'] == $original_post_video_id ) {
$video_duration = isset( $video['contentDetails']['duration'] ) ? $video['contentDetails']['duration'] : null;
$original_post['snippet']['videoDuration'] = $video_duration;
}
}
}
return $original_post;
}, $original_posts );
return $updated_posts;
}
public function add_remote_pageable_posts() {
}
public function add_remote_non_pageable() {
}
private function is_pageable() {
global $sby_settings;
return ! empty( $sby_settings['api_key'] );
}
public function merge_cached_posts( $current_posts, $channel_id ) {
$args = array(
'post_type' => SBY_CPT,
'post_status' => array( 'publish', 'pending', 'draft' ),
'orderby' => 'date',
'order' => 'DESC',
'posts_per_page' => 80,
'meta_query' => array(
array(
'value' => $channel_id,
'key' => 'sby_channel_id'
)
)
);
$feed_videos = new \WP_Query( $args );
if ( $feed_videos->have_posts() ) {
$posts = array();
while ( $feed_videos->have_posts() ) {
$feed_videos->the_post();
$json = get_post_meta( get_the_ID(), 'sby_json', true );
if ( $json ) {
$posts[] = json_decode( $json, true );
}
}
wp_reset_postdata();
$this->add_report( 'merging cached posts' );
$posts = array_merge( $current_posts, $posts );
return $posts;
} else {
$this->add_report( 'no cached posts found' );
wp_reset_postdata();
return $current_posts;
}
}
/**
* Connects to the YouTube API and records returned data. Will use channel data if already
* set by the regular feed
*
* @param $settings
* @param array $feed_types_and_terms organized settings related to feed data
* (ex. 'user' => array( 'smashballoon', 'customyoutubefeed' )
* @param array $connected_accounts_for_feed connected account data for the
* feed types and terms
*
* @since 1.0
*/
public function set_remote_header_data( $settings, $feed_types_and_terms, $connected_accounts_for_feed ) {
if ( is_array( $feed_types_and_terms['channels'] ) && count($feed_types_and_terms['channels']) > 1 ) {
$this->header_data = $this->process_multi_channel_header_data( $settings, $feed_types_and_terms, $connected_accounts_for_feed );
return;
}
$first_user = $this->get_first_user( $feed_types_and_terms, $settings );
$this->header_data = false;
$existing_channel_cache = $this->get_channel_cache( $first_user );
if ( $existing_channel_cache ) {
$this->channels_data[ $first_user ] = $existing_channel_cache;
$this->add_report( 'header data for ' . $first_user . ' exists in cache' );
}
if ( isset( $this->channels_data[ $first_user ] ) && ! $this->is_pageable() ) {
$this->header_data = $this->channels_data[ $first_user ];
} elseif ( ! empty( $first_user ) ) {
$connected_account_for_term = sby_get_first_connected_account();
if ( $connected_account_for_term['expires'] < time() + 5 ) {
$error_message = '<p><b>' . __( 'Reconnect to YouTube to show this feed.', 'feeds-for-youtube' ) . '</b></p>';
$error_message .= '<p>' . __( 'To create a new feed, first connect to YouTube using the "Connect to YouTube to Create a Feed" button on the settings page and connect any account.', 'feeds-for-youtube' ) . '</p>';
if ( current_user_can( 'manage_youtube_feed_options' ) ) {
$error_message .= '<a href="' . admin_url( 'admin.php?page=youtube-feed-settings' ) . '" target="blank" rel="noopener nofollow">' . __( 'Reconnect in the YouTube Feeds Settings Area', 'feeds-for-youtube' ) . '</a>';
}
global $sby_posts_manager;
$sby_posts_manager->add_frontend_error( 'accesstoken', $error_message );
$sby_posts_manager->add_error( 'accesstoken', array( 'Trying to connect a new account', $error_message ) );
} else {
$channel_params_type = strpos( $first_user, 'UC' ) !== 0 ? 'channel_name' : 'channel_id';
$params[ $channel_params_type ] = $first_user;
$connection = $this->make_api_connection( $connected_account_for_term, 'channels', $params );
$connection->connect();
$this->add_report( 'api call made for header - ' . $first_user );
if ( ! $connection->is_wp_error() && ! $connection->is_youtube_error() ) {
$this->header_data = $connection->get_data();
$channel_id = SBY_Parse::get_channel_id( $this->header_data );
$this->set_channel_cache( $channel_id, $this->header_data );
$this->channels_data[ $channel_id ] = $this->header_data;
$this->channels_data[ $first_user ] = $this->header_data;
if ( isset( $connected_accounts_for_feed[ $first_user ]['local_avatar'] ) && $connected_accounts_for_feed[ $first_user ]['local_avatar'] ) {
$upload = wp_upload_dir();
$resized_url = trailingslashit( $upload['baseurl'] ) . trailingslashit( SBY_UPLOADS_NAME );
$full_file_name = $resized_url . $this->header_data['username'] . '.jpg';
$this->header_data['local_avatar'] = $full_file_name;
}
} else {
if ( $connection->is_wp_error() ) {
SBY_API_Connect::handle_wp_remote_get_error( $connection->get_wp_error() );
} else {
SBY_API_Connect::handle_youtube_error( $connection->get_data(), $connected_accounts_for_feed[ $first_user ], 'header' );
}
}
}
}
}
/**
* Process header data when a feed has multiple channel sources
*
* @since 2.0.8
*/
public function process_multi_channel_header_data( $settings, $feed_types_and_terms, $connected_accounts_for_feed ) {
$multiple_header_data = array();
foreach( $feed_types_and_terms['channels'] as $channel ) {
if ( isset( $channel['term'] ) ) {
$channel_term = $channel['term'];
$existing_channel_cache = $this->get_channel_cache( $channel_term );
if ( $existing_channel_cache ) {
$this->channels_data[ $channel_term ] = $existing_channel_cache;
$this->add_report( 'header data for ' . $channel_term . ' exists in cache' );
}
if ( isset( $this->channels_data[ $channel_term ] ) && ! $this->is_pageable() ) {
$multiple_header_data[$channel_term] = $this->channels_data[ $channel_term ];
} elseif ( ! empty( $channel_term ) ) {
$connected_account_for_term = sby_get_first_connected_account();
if ( $connected_account_for_term['expires'] < time() + 5 ) {
$error_message = '<p><b>' . __( 'Reconnect to YouTube to show this feed.', 'feeds-for-youtube' ) . '</b></p>';
$error_message .= '<p>' . __( 'To create a new feed, first connect to YouTube using the "Connect to YouTube to Create a Feed" button on the settings page and connect any account.', 'feeds-for-youtube' ) . '</p>';
if ( current_user_can( 'manage_youtube_feed_options' ) ) {
$error_message .= '<a href="' . admin_url( 'admin.php?page=youtube-feed-settings' ) . '" target="blank" rel="noopener nofollow">' . __( 'Reconnect in the YouTube Feeds Settings Area', 'feeds-for-youtube' ) . '</a>';
}
global $sby_posts_manager;
$sby_posts_manager->add_frontend_error( 'accesstoken', $error_message );
$sby_posts_manager->add_error( 'accesstoken', array( 'Trying to connect a new account', $error_message ) );
} else {
$channel_params_type = strpos( $channel_term, 'UC' ) !== 0 ? 'channel_name' : 'channel_id';
$params[ $channel_params_type ] = $channel_term;
$connection = $this->make_api_connection( $connected_account_for_term, 'channels', $params );
$connection->connect();
$this->add_report( 'api call made for header - ' . $channel_term );
if ( ! $connection->is_wp_error() && ! $connection->is_youtube_error() ) {
$multiple_header_data[$channel_term] = $connection->get_data();
$channel_id = SBY_Parse::get_channel_id( $this->header_data );
$this->set_channel_cache( $channel_id, $this->header_data );
$this->channels_data[ $channel_id ] = $this->header_data;
$this->channels_data[ $channel_term ] = $this->header_data;
if ( isset( $connected_accounts_for_feed[ $channel_term ]['local_avatar'] ) && $connected_accounts_for_feed[ $channel_term ]['local_avatar'] ) {
$upload = wp_upload_dir();
$resized_url = trailingslashit( $upload['baseurl'] ) . trailingslashit( SBY_UPLOADS_NAME );
$full_file_name = $resized_url . $this->header_data['username'] . '.jpg';
$this->header_data['local_avatar'] = $full_file_name;
}
} else {
if ( $connection->is_wp_error() ) {
SBY_API_Connect::handle_wp_remote_get_error( $connection->get_wp_error() );
} else {
SBY_API_Connect::handle_youtube_error( $connection->get_data(), $connected_accounts_for_feed[ $channel_term ], 'header' );
}
}
}
}
}
}
return $multiple_header_data;
}
/**
* Stores feed data in a transient for a specified time
*
* @param int $cache_time
*
* @since 1.0
*/
public function cache_feed_data( $cache_time ) {
if ( ! empty( $this->post_data ) || ! empty( $this->next_pages ) ) {
$this->remove_duplicate_posts();
$this->trim_posts_to_max();
$post_data = $this->post_data;
if (! isset( $post_data[0]['iframe'] )) {
$to_cache = array(
'data' => $this->post_data,
'pagination' => $this->next_pages
);
set_transient( $this->regular_feed_transient_name, wp_json_encode( $to_cache ), $cache_time );
} else {
$this->add_report( 'iframe not caching' );
}
} else {
$this->add_report( 'no data not caching' );
}
}
/**
* Stores feed data with additional data specifically for cron caching
*
* @param array $to_cache feed data with additional things like the shortcode
* settings, when the cache was last requested, when new posts were last retrieved
* @param int $cache_time how long the cache will last
*
* @since 1.0
*/
public function set_cron_cache( $to_cache, $cache_time ) {
if ( ! empty( $this->post_data )
|| ! empty( $this->next_pages )
|| ! empty( $to_cache['data'] ) ) {
$this->remove_duplicate_posts();
$this->trim_posts_to_max();
$to_cache['data'] = isset( $to_cache['data'] ) ? $to_cache['data'] : $this->post_data;
$to_cache['pagination'] = isset( $to_cache['next_pages'] ) ? $to_cache['next_pages'] : $this->next_pages;
$to_cache['atts'] = isset( $to_cache['atts'] ) ? $to_cache['atts'] : $this->transient_atts;
$to_cache['last_requested'] = isset( $to_cache['last_requested'] ) ? $to_cache['last_requested'] : time();
$to_cache['last_retrieve'] = isset( $to_cache['last_retrieve'] ) ? $to_cache['last_retrieve'] : $this->last_retrieve;
set_transient( $this->regular_feed_transient_name, wp_json_encode( $to_cache ), $cache_time );
} else {
$this->add_report( 'no data not caching' );
}
}
/**
* Stores header data for a specified time as a transient
*
* @param int $cache_time
* @param bool $save_backup
*
* @since 1.0
*/
public function cache_header_data( $cache_time, $save_backup = true ) {
if ( $this->header_data ) {
set_transient( $this->header_transient_name, wp_json_encode( $this->header_data ), $cache_time );
if ( $save_backup ) {
update_option( $this->backup_header_transient_name, wp_json_encode( $this->header_data ), false );
}
}
}
/**
* Used to randomly trigger an updating of the last requested data for cron caching
*
* @return bool
*
* @since 1.0
*/
public function should_update_last_requested() {
return (rand( 1, 20 ) === 20);
}
/**
* Determines if pagination can and should be used based on settings and available feed data
*
* @param array $settings
* @param int $offset
*
* @return bool
*
* @since 1.0
*/
public function should_use_pagination( $settings, $offset = 0 ) {
$posts_available = count( $this->post_data ) - ($offset + $settings['num']);
$show_loadmore_button_by_settings = ($settings['showbutton'] == 'on' || $settings['showbutton'] == 'true' || $settings['showbutton'] == true ) && $settings['showbutton'] !== 'false';
if ( $show_loadmore_button_by_settings ) {
// used for permanent and whitelist feeds
if ( $this->feed_is_complete( $settings, $offset ) ) {
$this->add_report( 'no pagination, feed complete' );
return false;
}
if ( $posts_available > 0 ) {
$this->add_report( 'do pagination, posts available' );
return true;
}
$pages = $this->next_pages;
if ( $pages && ! $this->should_use_backup() ) {
foreach ( $pages as $page ) {
if ( ! empty( $page ) ) {
return true;
}
}
}
}
$this->add_report( 'no pagination, no posts available' );
return false;
}
public static function get_channel_status( $channel ) {
$channel_setting = get_option( 'sby_channel_status', array() );
if ( isset( $channel_setting[ $channel ] ) ) {
return $channel_setting[ $channel ];
}
return 0;
}
public static function update_channel_status( $channel, $status ) {
$channel_setting = get_option( 'sby_channel_status', array() );
$channel_setting[ $channel ] = $status;
update_option( 'sby_channel_status', $channel_setting, false );
}
/**
* Generates the HTML for the feed if post data is available. Although it seems
* some of the variables ar not used they are set here to hide where they
* come from when used in the feed templates.
*
* @param array $settings
* @param array $atts
* @param array $feed_types_and_terms organized settings related to feed data
* (ex. 'user' => array( 'smashballoon', 'customyoutubefeed' )
* @param array $connected_accounts_for_feed connected account data for the
* feed types and terms
*
* @return false|string
*
* @since 1.0
*/
public function get_the_feed_html( $settings, $atts, $feed_types_and_terms, $connected_accounts_for_feed ) {
global $sby_posts_manager;
if ( empty( $this->post_data ) && ! empty( $connected_accounts_for_feed ) ) {
$error_template = "<p><b>%s</b><p>%s</p>";
$error_title = __( 'Error: No videos found.', 'feeds-for-youtube' );
$error_description = __( 'Make sure this is a valid channel ID and that the channel has videos available on youtube.com.', 'feeds-for-youtube' );
if ( ! $this->feed_exists( $settings['feed'] ) ) {
$error_title = sprintf( __( 'Error: No feed found with the ID %s.', 'feeds-for-youtube' ),
$settings['feed'] );
$error_description = __( 'Go to the All Feeds page and select an ID from an existing feed.', 'feeds-for-youtube' );
}
$sby_posts_manager->add_frontend_error( 'noposts', sprintf($error_template, $error_title, $error_description) );
}
$posts = array_slice( $this->post_data, 0, $settings['num'] );
$header_data = ! empty( $this->header_data ) ? $this->header_data : false;
$first_username = false;
if ( $header_data ) {
$first_username = SBY_Parse::get_channel_id( $header_data );
} elseif ( isset( $this->post_data[0] ) ) { // in case no connected account for feed
$first_username = SBY_Parse::get_channel_id( $this->post_data[0] );
}
$use_pagination = $this->should_use_pagination( $settings, 0 );
$feed_id = $this->regular_feed_transient_name;
$shortcode_atts = ! empty( $atts ) ? wp_json_encode( $atts ) : '{}';
$settings['header_outside'] = false;
$settings['header_inside'] = false;
if ( $header_data && $settings['showheader'] ) {
$settings['header_inside'] = true;
}
$other_atts = '';
// The plugin settings did not mention heightunit but instead accept px or % in the height option directly. This is a check for this to make sure heightunit is assigned properly.
$settings['heightunit'] = ( strpos( $settings['height'], 'px' ) === false && strpos( $settings['height'], '%' ) === false ) ? $settings['heightunit'] : (strpos( $settings['height'], 'px' ) !== false ? 'px' : '%' );
$classes = array();
if ( empty( $settings['widthresp'] ) || $settings['widthresp'] == 'on' || $settings['widthresp'] == 'true' || $settings['widthresp'] === true ) {
if ( $settings['widthresp'] !== 'false' ) {
$classes[] = 'sby_width_resp';
}
}
if ( ! empty( $settings['class'] ) ) {
$classes[] = esc_attr( $settings['class'] );
}
if ( ! empty( $settings['height'] )
&& (((int)$settings['height'] < 100 && $settings['heightunit'] === '%') || $settings['heightunit'] === 'px') ) {
$classes[] = 'sby_fixed_height';
}
if ( ! empty( $settings['disablemobile'] )
&& ($settings['disablemobile'] == 'on' || $settings['disablemobile'] == 'true' || $settings['disablemobile'] == true) ) {
if ( $settings['disablemobile'] !== 'false' ) {
$classes[] = 'sby_disable_mobile';
}
}
if ( isset( $atts['classname'] ) ) {
$classes[] = sanitize_text_field( $atts['classname'] );
}
$additional_classes = '';
if ( ! empty( $classes ) ) {
$additional_classes = ' ' . esc_attr( implode( ' ', $classes ) );
}
$other_atts = $this->add_other_atts( $other_atts, $settings );
$flags = array();
if ( $this->successful_video_api_request_made && ! empty( $posts ) ) {
if ( $settings['storage_process'] === 'page' ) {
$this_posts = $posts;
if ( $this->do_page_cache_all ) {
$this_posts = $this->post_data;
}
$this->add_report( 'Adding videos to wp_posts ' . count( $this_posts ) );
AdminAjaxService::sby_process_post_set_caching( $this_posts, $feed_id );
} elseif ( $settings['storage_process'] === 'background' ) {
$flags[] = 'checkWPPosts';
if ( $this->do_page_cache_all ) {
$this->add_report( 'Flagging videos to wp_posts ' . count( $this->post_data ) );
$flags[] = 'cacheAll';
}
}
}
if ( $settings['disable_resize'] ) {
$flags[] = 'resizeDisable';
} elseif ( $settings['favor_local'] ) {
$flags[] = 'favorLocal';
}
if ( $settings['global_settings']['disable_js_image_loading'] ) {
$flags[] = 'imageLoadDisable';
}
if ( $settings['ajax_post_load'] ) {
$flags[] = 'ajaxPostLoad';
}
if ( $settings['playerratio'] === '3:4' ) {
$flags[] = 'narrowPlayer';
}
if ( SBY_GDPR_Integrations::doing_gdpr( $settings ) ) {
$flags[] = 'gdpr';
}
if ( ! is_admin()
&& Feed_Locator::should_do_ajax_locating( $this->regular_feed_transient_name, get_the_ID() ) ) {
$flags[] = 'locator';
}
if ( $settings['global_settings']['disablecdn'] ) {
$flags[] = 'disablecdn';
}
if ( isset( $_GET['sb_debug'] ) ) {
$flags[] = 'debug';
}
if ( ! empty( $settings['allowcookies'] )
&& ( $settings['allowcookies'] == 'on' || $settings['allowcookies'] == 'true' || $settings['allowcookies'] == true) ) {
$flags[] = 'allowcookies';
}
if ( isset( $atts['allowcookies'] ) && $atts['allowcookies'] == true ) {
$flags[] = 'allowcookies';
}
if ( ! empty( $flags ) ) {
if ( sby_doing_customizer( $settings ) ) {
$other_atts .= ' :data-sby-flags="$parent.getFlagsAttr()"';
} else {
$other_atts .= ' data-sby-flags="' . esc_attr( implode(',', $flags ) ) . '"';
}
}
$other_atts .= ' data-postid="' . esc_attr( get_the_ID() ) . '"';
if ( $settings['layout'] === 'grid' || $settings['layout'] === 'carousel' ) {
$other_atts .= ' data-sby-supports-lightbox="1"';
}
$icon_type = $settings['font_method'];
ob_start();
include sby_get_feed_template_part( 'feed', $settings );
$html = ob_get_contents();
ob_get_clean();
if ( $settings['ajaxtheme'] ) {
$html .= $this->get_ajax_page_load_html($settings);
}
return $html;
}
/**
* Generates HTML for individual sby_item elements
*
* @param array $settings
* @param int $offset
* @param array $feed_types_and_terms organized settings related to feed data
* (ex. 'user' => array( 'smashballoon', 'customyoutubefeed' )
* @param array $connected_accounts_for_feed connected account data for the
* feed types and terms
*
* @return false|string
*
* @since 1.0
*/
public function get_the_items_html( $settings, $offset, $feed_types_and_terms = array(), $connected_accounts_for_feed = array() ) {
if ( empty( $this->post_data ) ) {
ob_start();
$html = ob_get_contents();
ob_get_clean(); ?>
<p><?php _e( 'No posts found.', 'feeds-for-youtube' ); ?></p>
<?php
$html = ob_get_contents();
ob_get_clean();
return $html;
}
$posts = array_slice( $this->post_data, $offset, $settings['num'] );
ob_start();
$this->posts_loop( $posts, $settings, $offset );
$html = ob_get_contents();
ob_get_clean();
return $html;
}
/**
* Overwritten in the Pro version
*
* @return object
*/
public function make_api_connection( $connected_account_or_page, $type = NULL, $params = NULL ) {
return new SBY_API_Connect( $connected_account_or_page, $type, $params );
}
/**
* When the feed is loaded with AJAX, the JavaScript for the plugin
* needs to be triggered again. This function is a workaround that adds
* the file and settings to the page whenever the feed is generated.
*
* @return string
*
* @since 1.0
*/
public function get_ajax_page_load_html($settings) {
$js_options = array(
'adminAjaxUrl' => admin_url( 'admin-ajax.php' ),
'placeholder' => trailingslashit( SBY_PLUGIN_URL ) . 'img/placeholder.png',
'placeholderNarrow' => trailingslashit( SBY_PLUGIN_URL ) . 'img/placeholder-narrow.png',
'lightboxPlaceholder' => trailingslashit( SBY_PLUGIN_URL ) . 'img/lightbox-placeholder.png',
'lightboxPlaceholderNarrow' => trailingslashit( SBY_PLUGIN_URL ) . 'img/lightbox-placeholder-narrow.png',
'autoplay' => $settings['playvideo'] === 'automatically',
'semiEagerload' => $settings['eagerload'],
'eagerload' => $settings['eagerload']
);
$encoded_options = wp_json_encode( $js_options );
$js_option_html = '<script type="text/javascript">if (typeof sbyOptions === "undefined") var sbyOptions = ' . $encoded_options . ';</script>';
$js_option_html .= "<script type='text/javascript' src='" . trailingslashit( SBY_PLUGIN_URL ) . 'js/sb-youtube.min.js?ver=' . SBYVER . "'></script>";
return $js_option_html;
}
/**
* Overwritten in the Pro version
*
* @param $feed_types_and_terms
*
* @return string
*
* @since 2.1/5.2
*/
public function get_first_user( $feed_types_and_terms, $settings = array() ) {
if ( isset( $feed_types_and_terms['channels'][0] ) ) {
return $feed_types_and_terms['channels'][0]['term'];
} else {
return '';
}
}
public function do_page_cache_all() {
return $this->do_page_cache_all;
}
public function successful_video_api_request_made() {
return $this->successful_video_api_request_made;
}
/**
* Adds recorded strings to an array
*
* @param $to_add
*
* @since 1.0
*/
public function add_report( $to_add ) {
$this->report[] = $to_add;
}
/**
* @return array
*
* @since 1.0
*/
public function get_report() {
return $this->report;
}
/**
* Additional options/settings added to the main div
* for the feed
*
* Overwritten in the Pro version
*
* @param $other_atts
* @param $settings
*
* @return string
*/
protected function add_other_atts( $other_atts, $settings ) {
return '';
}
/**
* 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
*
* @return mixed|array
*
* @since 1.0
*/
protected function filter_posts( $post_set, $settings = array() ) {
// array_unique( $post_set, SORT_REGULAR);
return $post_set;
}
protected function remove_duplicate_posts() {
$posts = $this->post_data;
$ids_in_feed = array();
$non_duplicate_posts = array();
$removed = array();
foreach ( $posts as $post ) {
$post_id = SBY_Parse::get_video_id( $post );
if ( ! in_array( $post_id, $ids_in_feed, true ) ) {
$ids_in_feed[] = $post_id;
$non_duplicate_posts[] = $post;
} else {
$removed[] = $post_id;
}
}
$this->add_report( 'removed duplicates: ' . implode(', ', $removed ) );
$this->set_post_data( $non_duplicate_posts );
}
/**
* Used for limiting the cache size
*
* @since 2.0/5.1.1
*/
protected function trim_posts_to_max() {
if ( ! is_array( $this->post_data ) ) {
return;
}
$max = apply_filters( 'sby_max_cache_size', 500 );
$this->set_post_data( array_slice( $this->post_data , 0, $max ) );
}
/**
* Used for permanent feeds or white list feeds to
* stop pagination if all posts are already added
*
* Overwritten in the Pro version
*
* @param array $settings
* @param int $offset
*
* @return bool
*
* @since 1.0
*/
protected function feed_is_complete( $settings, $offset = 0 ) {
return false;
}
/**
* Iterates through post data and tracks the index of the current post.
* The actual post ids of the posts are stored in an array so the plugin
* can search for local images that may be available.
*
* @param array $posts final filtered post data for the feed
* @param array $settings
* @param int $offset
*
* @since 1.0
*/
protected function posts_loop( $posts, $settings, $offset = 0 ) {
$header_data = $this->get_header_data();
$image_ids = array();
$post_index = $offset;
if ( ! isset( $settings['feed_id'] ) ) {
$settings['feed_id'] = $this->regular_feed_transient_name;
}
$misc_data = $this->get_misc_data( $settings['feed_id'], $posts );
$icon_type = $settings['font_method'];
foreach ( $posts as $post ) {
$image_ids[] = SBY_Parse::get_post_id( $post );
include sby_get_feed_template_part( 'item', $settings );
$post_index++;
}
$this->image_ids_post_set = $image_ids;
}
/**
* Uses array of API request results and merges them based on how
* the feed should be sorted. Mixed feeds are always sorted alternating
* since there is no post date for hashtag feeds.
*
* @param array $post_sets an array of single API request worth
* of posts
* @param array $settings
*
* @return array
*
* @since 1.0
*/
private function merge_posts( $post_sets, $settings ) {
$merged_posts = array();
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 ];
}
}
}
} else {
$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 );
}
}
} else {
// don't bother merging posts if there is only one post set
if ( isset( $post_sets[1] ) ) {
foreach ( $post_sets as $post_set ) {
if ( isset( $post_set[0]['id'] ) ) {
$merged_posts = array_merge( $merged_posts, $post_set );
}
}
} else {
$merged_posts = isset( $post_sets[0] ) ? $post_sets[0] : array();
}
}
return $merged_posts;
}
/**
* Sorts a post set based on sorting settings. Sorting by "alternate"
* is done when merging posts for efficiency's sake so the post set is
* just returned as it is.
*
* @param array $post_set
* @param array $settings
*
* @return mixed|array
*
* @since 1.0
*/
protected function sort_posts( $post_set, $settings ) {
if ( empty( $post_set ) ) {
return $post_set;
}
// sorting done with "merge_posts" to be more efficient
if ( $settings['sortby'] === 'alternate' || $settings['sortby'] === 'api' ) {
$return_post_set = $post_set;
} elseif ( $settings['sortby'] === 'random' ) {
/*
* randomly selects posts in a random order. Cache saves posts
* in this random order so paginating does not cause some posts to show up
* twice or not at all
*/
usort($post_set, 'sby_rand_sort' );
$return_post_set = $post_set;
} else {
// compares posted on dates of posts
usort($post_set, 'sby_date_sort' );
$return_post_set = $post_set;
}
/**
* Apply a custom sorting of posts
*
* @param array $return_post_set Ordered set of filtered posts
* @param array $settings Settings for this feed
*
* @since 1.0
*/
return apply_filters( 'sby_sorted_posts', $return_post_set, $settings );
}
/**
* Can trigger a second attempt at getting posts from the API
*
* Overwritten in the Pro version
*
* @param string $type
* @param array $connected_account_with_error
* @param int $attempts
*
* @return bool
*
* @since 1.0
*/
protected function can_try_another_request( $type, $connected_account_with_error, $attempts = 0 ) {
return false;
}
/**
* returns a second connected account if it exists
*
* Overwritten in the Pro version
*
* @param string $type
* @param array $attempted_connected_accounts
*
* @return bool
*
* @since 1.0
*/
protected function get_different_connected_account( $type, $attempted_connected_accounts ) {
return false;
}
private function feed_exists( $id ) {
$builder = \Smashballoon\Customizer\Container::getInstance()->get( Feed_Builder::class );
$feeds_list = $builder->get_feed_list( [ "id" => $id ], true );
return ! empty( $feeds_list );
}
}