????
Current Path : /home/darkwebsol/acrepairkwt.com/wp-content/plugins/feeds-for-youtube/inc/ |
Current File : /home/darkwebsol/acrepairkwt.com/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 ) { 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 ) ) { $api_connect_playlist_items = $this->make_workaround_connection( $connected_account_for_term, $type, $params ); $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 ); } }