public function maybe_render_episode(){ if(!$this->is_episode_request()){ return; } global $wpdb; $show = sanitize_title(get_query_var('pp_tv_show')); $s = (int) get_query_var('pp_tv_s'); $e = (int) get_query_var('pp_tv_e'); $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->tbl_eps} WHERE show_slug=%s AND season_number=%d AND episode_number=%d LIMIT 1", $show, $s, $e ), ARRAY_A ); if(!$row){ status_header(404); nocache_headers(); get_header(); echo '

Episode not found

'; get_footer(); exit; } $title = ($row['show_title'] ?: ucfirst($row['show_slug'])) . ' - ' . ($row['title'] ?: 'Episode') . sprintf(' (S%dE%d)', $s, $e); $this->current_doc_title = $title; $credits = $wpdb->get_results( $wpdb->prepare("SELECT * FROM {$this->tbl_credits} WHERE show_slug=%s AND season_number=%d AND episode_number=%d ORDER BY credit_type ASC, COALESCE(order_num,9999) ASC, person_name ASC",$show,$s,$e), ARRAY_A ); $person_ids = array(); foreach($credits as $c){ $pid=(int)$c['tmdb_person_id']; if($pid>0) $person_ids[$pid]=true; } $people = array(); if($person_ids){ $ids = implode(',', array_map('intval', array_keys($person_ids))); $people = $wpdb->get_results("SELECT tmdb_person_id, name, person_slug, photo_id, photo_tmdb_path FROM {$this->tbl_people} WHERE tmdb_person_id IN ($ids)", OBJECT_K); } get_header(); echo '
'; echo '← Back to show'; echo '

'.esc_html($title).'

'; $hero_url = ''; $hero_is_local = false; if(!empty($row['still_attachment_id'])){ $hero_url = wp_get_attachment_image_url((int)$row['still_attachment_id'],'large'); $hero_is_local = true; } if(!$hero_url && !empty($row['still_path'])){ $hero_url = 'https://image.tmdb.org/t/p/w780'.$row['still_path']; } $hero_alt = $this->episode_alt_text($row['show_title'], (int)$row['season_number'], (int)$row['episode_number'], $row['title']); if($hero_url){ echo ''.esc_attr($hero_alt).''; if(!empty($row['overview'])){ echo '

'.esc_html($row['overview']).'

'; } if(!$hero_is_local){ echo '

Image temporarily served from TMDB until local copy is saved.

'; } } elseif(!empty($row['overview'])){ echo '

'.esc_html($row['overview']).'

'; } echo ''; $this->row_kv('Runtime (min)', isset($row['runtime_minutes']) ? $row['runtime_minutes'] : ''); $this->row_kv('TMDB Rating', isset($row['vote_average']) ? $row['vote_average'] : ''); $this->row_kv('Air Date', isset($row['air_date']) ? $row['air_date'] : ''); echo '
'; $yt_raw = $this->val($row, array('youtube_video_id','youtube_id','yt_id','youtube','trailer_youtube_id')); $yt = $this->extract_youtube_id($yt_raw); if($yt){ echo '
'; } if ( function_exists('pp_tv_render_episode_sections') ) { echo pp_tv_render_episode_sections( $row ); } elseif ( function_exists('pp_tv_render_episode_ai_sections') ) { echo pp_tv_render_episode_ai_sections( $row ); } // Credits render $main_cast=[]; $writing=[]; $directing=[]; $buckets=[]; foreach($credits as $c){ $dept = $c['department'] ? $c['department'] : ($c['credit_type']==='cast' ? 'Acting' : ''); $job = $c['job'] ? $c['job'] : ($c['credit_type']==='cast' ? 'Cast' : ''); $pid = (int)$c['tmdb_person_id']; $person = isset($people[$pid]) ? $people[$pid] : null; // PERSON PHOTO POLICY: // Only use LOCAL photo_id; never hotlink TMDB and never queue downloads. $photo = ''; if($person && !empty($person->photo_id)){ $photo = wp_get_attachment_image_url((int)$person->photo_id, 'medium'); } $pname = $person ? $person->name : $c['person_name']; $item = array( 'name'=>$pname, 'photo'=>$photo, 'character'=> isset($c['character_name']) ? $c['character_name'] : '', 'job'=>$job, 'dept'=>$dept, 'order'=> isset($c['order_num']) && $c['order_num']!==null ? (int)$c['order_num'] : 9999, ); if($c['credit_type']==='cast'){ $main_cast[] = $item; } elseif(strtolower($dept)==='writing' || stripos($job,'writer')!==false || stripos($job,'script')!==false){ $writing[] = $item; } elseif(strtolower($dept)==='directing' || stripos($job,'director')!==false){ $directing[] = $item; } else { $key = $dept ? $dept : 'Other'; if(!isset($buckets[$key])) $buckets[$key] = array(); $buckets[$key][] = $item; } } usort($main_cast, function($a,$b){ return $a['order'] - $b['order']; }); usort($writing, function($a,$b){ return strcmp($a['name'],$b['name']); }); usort($directing, function($a,$b){ return strcmp($a['name'],$b['name']); }); ksort($buckets); if($main_cast){ echo '

Cast

'; $max = min(5, count($main_cast)); for($i=0; $i<$max; $i++){ $this->render_person_card($main_cast[$i], true); } echo '
'; if(count($main_cast)>5){ echo '
Show '.(count($main_cast)-5).' more cast
'; for($i=5; $irender_person_card($main_cast[$i], true); } echo '
'; } } echo '
'; echo '

Writing

'; if($writing){ echo '
    '; foreach($writing as $w){ echo '
  • '.$this->crew_line($w).'
  • '; } echo '
'; } else { echo '
'; } echo '
'; echo '

Directing

'; if($directing){ echo '
    '; foreach($directing as $d){ echo '
  • '.$this->crew_line($d).'
  • '; } echo '
'; } else { echo '
'; } echo '
'; echo '
'; echo $this->render_season_browser($row['show_slug'], (int)$row['season_number']); echo '

This product uses the TMDB API but is not endorsed or certified by TMDB.

'; echo '
'; get_footer(); exit; } /* ==================== SEO ==================== */ public function maybe_disable_seo_plugins(){ if(!$this->is_episode_request()) return; if(has_action('wpseo_head')) remove_all_actions('wpseo_head'); // Yoast add_filter('rank_math/frontend/disable', '__return_true', 99); add_filter('aioseo_disable', '__return_true', 99); } public function print_episode_seo_head(){ if(!$this->is_episode_request()) return; global $wpdb; $show = sanitize_title(get_query_var('pp_tv_show')); $s = (int) get_query_var('pp_tv_s'); $e = (int) get_query_var('pp_tv_e'); $row = $wpdb->get_row( $wpdb->prepare( "SELECT show_slug, show_title, season_number, episode_number, episode_slug, title, overview, air_date, still_attachment_id, still_path FROM {$this->tbl_eps} WHERE show_slug=%s AND season_number=%d AND episode_number=%d LIMIT 1", $show, $s, $e ), ARRAY_A ); if(!$row) return; $title = ($row['show_title'] ?: ucfirst($row['show_slug'])) . ' - ' . ($row['title'] ?: 'Episode') . sprintf(' (S%dE%d)', $s, $e); $desc = trim(wp_strip_all_tags((string)$row['overview'])); if($desc==='') $desc = $title; $img = ''; if(!empty($row['still_attachment_id'])){ $img = wp_get_attachment_image_url((int)$row['still_attachment_id'], 'large'); } elseif(!empty($row['still_path'])) { $img = 'https://image.tmdb.org/t/p/w780'.$row['still_path']; } echo "\n".''."\n"; echo ''.esc_html($title)."\n"; echo ''."\n"; echo ''."\n"; echo ''."\n"; if($img) echo ''."\n"; echo ''."\n"; echo ''."\n"; echo ''."\n"; if($img) echo ''."\n"; $ld = array( '@context'=>'https://schema.org', '@type'=>'TVEpisode', 'name'=> (string)$row['title'], 'description'=> $desc, 'episodeNumber'=>(int)$row['episode_number'], 'datePublished'=> (string)$row['air_date'], 'partOfSeason'=> array('@type'=>'TVSeason','seasonNumber'=>(int)$row['season_number'],'name'=>'Season '.(int)$row['season_number']), 'partOfSeries'=> array('@type'=>'TVSeries','name'=>(string)$row['show_title']), ); if($img) $ld['image']=$img; echo '\n"; echo ''."\n"; } /* ==================== Helpers ==================== */ private function row_kv($k,$v){ if($v==='' || $v===null){ $v='—'; } echo ''.esc_html($k).''.esc_html($v).''; } private function crew_line($it){ $edit = current_user_can('manage_options') ? ' (edit)' : ''; $right = $it['job'] ? $it['job'] : $it['character']; return ''.esc_html($it['name']).''.$edit.' — '.esc_html($right ? $right : 'Crew').''; } private function render_person_card($p, $showRole){ $edit = current_user_can('manage_options') ? '(edit)' : ''; echo '
'; // Only show local photo if present; otherwise placeholder. Never hotlink TMDB. if(!empty($p['photo'])){ echo ''.esc_attr($p['name']).''; } else { echo '
👤
'; } echo '
'.esc_html($p['name']).' '.$edit.'
'; if($showRole){ $r = !empty($p['character']) ? $p['character'] : $p['job']; if($r) echo '
'.esc_html($r).'
'; } echo '
'; } private function extract_youtube_id($in){ $in = trim((string)$in); if($in==='') return ''; if(preg_match('~^[A-Za-z0-9_-]{6,}$~', $in)) return $in; $parts = wp_parse_url($in); if(!$parts) return ''; $host = isset($parts['host']) ? $parts['host'] : ''; $path = isset($parts['path']) ? trim($parts['path'], '/') : ''; if(strpos($host,'youtu.be') !== false && $path) return $path; if(strpos($host,'youtube.com') !== false){ if(!empty($parts['query'])){ parse_str($parts['query'], $q); if(!empty($q['v'])) return $q['v']; } if(strpos($path,'embed/')===0) return substr($path,6); } return ''; } private function val($row, $candidates){ foreach((array)$candidates as $c){ if(isset($row[$c]) && $row[$c]!=='') return $row[$c]; } return ''; } /* ==================== Admin UI ==================== */ public function admin_menu(){ add_menu_page('TV Library','TV Library','manage_options','pp-tv-library',[$this,'page_library'],'dashicons-format-video',56); add_submenu_page('pp-tv-library','Settings','Settings','manage_options','pp-tv-settings',[$this,'page_settings']); add_submenu_page(null, 'Edit Episode', 'Edit Episode', 'manage_options', 'pp-tv-edit-episode', [$this,'page_edit_episode']); } adhd | Parenting Patch
Girl deals with ADHD sitting in classroom inattentive

adhd