/* ═══════════════════════════════════════════════════════════════ OUT-OF-CATALOG ARRIVALS (the /new/ surfaces only) OOC titles are auto-ingested and not yet hand-reviewed, so they stay out of fv_build_index() and off every browse page — that index is what /all/, the archives and the homepage rows all read, and every pick there is curator-vouched. "New on" is a different claim: it says a title landed on a service, not that we vouch for it. So the /new/ pages and the homepage's New shelf pull OOC arrivals from here and merge them in, and nothing else sees them. Entries are shaped like fv_build_index() entries so the callers need no special case beyond the merge. Cached for an hour; bump the key to force a rebuild. ═══════════════════════════════════════════════════════════════ */ function agmtw_ooc_arrivals( $geo = 'US' ) { static $memo = []; $map = [ 'US' => 'usa', 'CA' => 'canada', 'GB' => 'uk' ]; if ( ! isset( $map[ $geo ] ) ) $geo = 'US'; if ( isset( $memo[ $geo ] ) ) return $memo[ $geo ]; $key = 'agmtw_ooc_arrivals_v1_' . strtolower( $geo ); $cached = get_transient( $key ); if ( is_array( $cached ) ) return $memo[ $geo ] = $cached; global $wpdb; $prefix = $map[ $geo ]; $cutoff = time() - ( 90 * DAY_IN_SECONDS ); // 1) Arrival stamps first: only OOC titles that actually landed somewhere // recently are worth loading at all. $rows = $wpdb->get_results( $wpdb->prepare( "SELECT m.post_id, m.meta_key, m.meta_value FROM {$wpdb->postmeta} m JOIN {$wpdb->posts} p ON p.ID = m.post_id WHERE p.post_status = 'outofcatalog' AND p.post_type IN ('post','shows') AND m.meta_key LIKE %s AND m.meta_value REGEXP '^[0-9]+$' AND CAST( m.meta_value AS UNSIGNED ) >= %d", $prefix . '-%-date', $cutoff ) ); if ( ! $rows ) { set_transient( $key, [], HOUR_IN_SECONDS ); return $memo[ $geo ] = []; } $plen = strlen( $prefix . '-' ); $slen = strlen( '-date' ); $arrivals = []; foreach ( $rows as $r ) { $streamer = substr( $r->meta_key, $plen, -$slen ); if ( $streamer === '' || $streamer === 'arrival' ) continue; $pid = (int) $r->post_id; $ts = (int) $r->meta_value; if ( ! isset( $arrivals[ $pid ][ $streamer ] ) || $arrivals[ $pid ][ $streamer ] < $ts ) { $arrivals[ $pid ][ $streamer ] = $ts; } } $ids = array_keys( $arrivals ); $ids_csv = implode( ',', array_map( 'intval', $ids ) ); // 2) Titles + type, 3) the taxonomies the /new/ table prints, 4) the // streamer tags an arrival has to be backed by. $meta_rows = $wpdb->get_results( "SELECT ID, post_type, post_title FROM {$wpdb->posts} WHERE ID IN ($ids_csv)" ); $tax_rows = $wpdb->get_results( " SELECT tr.object_id, tt.taxonomy, t.slug FROM {$wpdb->term_relationships} tr JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id JOIN {$wpdb->terms} t ON tt.term_id = t.term_id WHERE tr.object_id IN ($ids_csv) AND tt.taxonomy IN ('category','language','released','post_tag') " ); $sr_rows = $wpdb->get_results( "SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE post_id IN ($ids_csv) AND meta_key = 'staffrating'" ); $genres = $langs = $years = $streamers = []; foreach ( $tax_rows as $t ) { $pid = (int) $t->object_id; if ( $t->taxonomy === 'category' ) $genres[ $pid ][] = $t->slug; elseif ( $t->taxonomy === 'language' ) $langs[ $pid ][] = $t->slug; elseif ( $t->taxonomy === 'released' ) $years[ $pid ][] = $t->slug; elseif ( strpos( $t->slug, $prefix . '-' ) === 0 ) { $streamers[ $pid ][ substr( $t->slug, $plen ) ] = true; } } $sr = []; foreach ( $sr_rows as $r ) $sr[ (int) $r->post_id ] = (int) $r->meta_value; $out = []; foreach ( $meta_rows as $p ) { $pid = (int) $p->ID; // An arrival with no matching tag means the title has since dropped // off that service; same guard the main index applies. $arr = []; foreach ( ( $arrivals[ $pid ] ?? [] ) as $s => $ts ) { if ( isset( $streamers[ $pid ][ $s ] ) ) $arr[ $s ] = $ts; } if ( ! $arr ) continue; $out[] = [ 'id' => $pid, 'type' => $p->post_type, 'title' => html_entity_decode( $p->post_title, ENT_QUOTES, 'UTF-8' ), 'staffrating' => $sr[ $pid ] ?? 0, 'isPro' => false, 'isOOC' => true, 'streamers' => array_keys( $streamers[ $pid ] ?? [] ), 'genres' => $genres[ $pid ] ?? [], 'languages' => $langs[ $pid ] ?? [], 'released' => $years[ $pid ] ?? [], 'moods' => [], 'countries' => [], 'arrivals' => $arr, 'latestArrival' => max( $arr ), ]; } set_transient( $key, $out, HOUR_IN_SECONDS ); return $memo[ $geo ] = $out; }
Updated July 29, 2026 • By Isabella Endrinal
7-day free trial available
Cord-cutters only interested in Spanish-language content will find much to like in Fubo Latino. Instead of the usual lineup, the Latino plan carries more than 65 channels in Spanish, including Unimas, Univision, BeinSports, Nat Geo Mundo, Sony Cine, and Discovery en Español. And since Fubo started as a soccer streaming service, it also has live sporting events featuring leagues like the UEFA Champions League, Bundesliga, and MLS.
At only $14.99/month, it’s Fubo’s most affordable plan yet, but it still has base features like unlimited cloud DVR and two simultaneous streams. If you already have a Fubo base plan, Fubo also offers these channels as an add-on for $20/month.
Fubo Latino carries 52 channels ( total), including 10 cheapest, Bein Sports, beIN SPORTS. beIN SPORTS XTRA, Canela Cinema, Canela Clasiocos, Canela Deportes, and more.
How much does Fubo Latino cost?
Fubo Latino starts at $14.99/month.
Does Fubo Latino have a free trial?
Yes. Fubo Latino currently offers a 7-day free trial for new subscribers.
What channels does Fubo Latino include?
Fubo Latino carries 52 channels, including 10 cheapest, Bein Sports, beIN SPORTS. beIN SPORTS XTRA, Canela Cinema, Canela Clasiocos, and more.
What devices support Fubo Latino?
Fubo Latino is available on Amazon Fire TV, Android, Android TV, Apple TV, Chromecast, iOS, Roku.
Can I cancel Fubo Latino anytime?
Fubo Latino is a month-to-month subscription with no long-term contract. You can cancel at any time without penalty.
Is Fubo Latino worth it?
Cord-cutters only interested in Spanish-language content will find much to like in Fubo Latino. Instead of the usual lineup, the Latino plan carries more than 65 channels in Spanish, including Unimas, Univision, BeinSports, Nat Geo Mundo, Sony Cine, and Discovery en Español. And since Fubo started as a soccer streaming service, it also has live sporting events featuring leagues like the UEFA Champions League, Bundesliga, and MLS. At only $33/month, it's Fubo's most affordable plan yet, but it still has base features like 250 hours worth of cloud DVR and two simultaneous streams. If you already have a Fubo base plan, Fubo also offers these channels as an add-on for $20/month.