Created
August 2, 2013 17:04
-
-
Save jkereako/5db9f7fd36493f67208a to your computer and use it in GitHub Desktop.
Handles the TV Tune-In and the TV Schedule.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* NESN PROGRAMMING | |
* | |
* Handles the TV Tune-In and the TV Schedule | |
*/ | |
class NESN_Programming { | |
/** | |
* Class variables | |
*/ | |
private $_tv_schedule_slug = 'tv-schedule'; | |
public $permalink = ''; | |
public $current_month = null; | |
public $previous_month = null; | |
public $next_month = null; | |
// Array of required CSV columns. This is used to validate incoming CSV data | |
private $_required_csv_columns = array( | |
'tv-tune-in' => array( | |
'pub_date', | |
'pub_time', | |
'tune_in_text_1', | |
'tune_in_link_1', | |
'tune_in_text_2', | |
'tune_in_link_2', | |
'tune_in_text_3', | |
'tune_in_link_3' | |
), | |
'tv-schedule' => array( | |
'Date', | |
'Time', | |
'Program Name', | |
'Title Name', | |
'Airing Status' | |
) | |
); | |
/** | |
* Register actions and filter | |
* | |
* @uses add_action, add_filter | |
* @return null | |
*/ | |
public function __construct() { | |
global $wp_rewrite; | |
// Register assets | |
//add_action( 'init', array( $this, 'action_init' ) ); | |
// For TV schedule use only | |
add_action( 'generate_rewrite_rules', array( $this, 'action_generate_rewrite_rules' ) ); | |
add_filter( 'query_vars', array( $this, 'filter_query_vars' ) ); | |
//add_action( 'wp_enqueue_scripts', array( $this, 'action_load_assets' ) ); | |
//add_filter( 'nesn_core_js_data', array( $this, 'filter_nesn_core_js_data' ) ); | |
// Make this available throughout, that way, we can ensure consistency | |
$this->permalink = trailingslashit( get_site_url() ) . $this->_tv_schedule_slug . $wp_rewrite->get_day_permastruct(); | |
$this->current_month = array( | |
'day' => (int) date ( 'j', current_time( 'timestamp', 0 ) ), | |
'month_num' => (int) date ( 'n', current_time( 'timestamp', 0 ) ), | |
'year' => (int) date ( 'Y', current_time( 'timestamp', 0 ) ) | |
); | |
$this->previous_month = array( | |
'month_num' => (int) date( 'n', strtotime ( date( 'Y-m-d', strtotime ( $this->current_month['year'] . '-' . $this->current_month['month_num'] . '-01' ) ) . ' - 1 month' ) ), | |
'year' => (int) date( 'Y', strtotime ( date( 'Y-m-d', strtotime ( $this->current_month['year'] . '-' . $this->current_month['month_num'] . '-01' ) ) . ' - 1 month' ) ) | |
); | |
$this->next_month = array( | |
'month_num' => (int) date( 'n', strtotime ( date( 'Y-m-d', strtotime ( $this->current_month['year'] . '-' . $this->current_month['month_num'] . '-01' ) ) . ' + 1 month' ) ), | |
'year' => (int) date( 'Y', strtotime ( date( 'Y-m-d', strtotime ( $this->current_month['year'] . '-' . $this->current_month['month_num'] . '-01' ) ) . ' + 1 month' ) ) | |
); | |
} | |
/** | |
* Register assets | |
* | |
* @return array | |
*/ | |
public function action_init() { | |
//wp_register_script( 'moment-js', wpcom_vip_theme_url( 'js/moment.min.js' ), array(), '2.0.0', true ); | |
} | |
/** | |
* Load assets | |
* | |
* @return array | |
*/ | |
public function action_load_assets(){ | |
// For now, only include on the TV Schedule page | |
if ( get_query_var( 'pagename' ) !== $this->_tv_schedule_slug ) { | |
return false; | |
} | |
global $wp_scripts; | |
//wp_enqueue_script ( 'moment-js', wpcom_vip_theme_url( 'js/moment.min.js' ), array (), '2.0.0', true ); | |
} | |
/** | |
* Check to see if we have next month's TV schedule processed and ready to go | |
* | |
* @param $schedules array, $timestamp int | |
* @uses array_key_exists, is_array, json_decode, json_last_error | |
* @return bool | |
*/ | |
public function has_next_months_tv_schedule( $schedules, $timestamp ) { | |
if ( ! is_array( $schedules ) || empty( $timestamp ) ) { | |
return false; | |
} | |
// If the current timestamp already is the next month, return false. | |
if ( (int) date( 'n', $timestamp ) === (int) $this->next_month['month_num'] && (int) date( 'Y', $timestamp ) === (int) $this->next_month['year'] ){ | |
return false; | |
} | |
// Else, check if we have two schedules | |
else if ( array_key_exists( 1, $schedules ) ) { | |
// If we do, decode the JSON of the second schedule, if necessary | |
if ( ! is_array( $schedules[1] ) ) { | |
$schedule = json_decode( $schedules[1], true ); | |
} | |
else { | |
$schedule = $schedules[1]; | |
} | |
if ( json_last_error() !== JSON_ERROR_NONE ) { | |
return false; | |
} | |
// Check if the second schedule matches the month number of next month | |
else if ( (int) $schedule['meta']['scheduleMonth'] === (int) $this->next_month['month_num'] && (int) $schedule['meta']['scheduleYear'] === (int) $this->next_month['year'] ) { | |
unset( $schedule ); | |
return true; | |
} | |
} | |
// It doesn't match, so, return false | |
return false; | |
} | |
/** | |
* Print the schedule data to the nesnData object for use with the client | |
* | |
* @param array $data | |
* @return array | |
*/ | |
public function filter_nesn_core_js_data( $data ) { | |
if ( get_query_var( 'pagename' ) !== $this->_tv_schedule_slug ) { | |
return false; | |
} | |
$schedules = nesn_singleton( 'NESN_Content_Options' )->get_option('TV Schedule'); | |
if ( empty( $schedules ) ) { | |
return $data; | |
} | |
// Attempt to build the timestamp from the querystring | |
$timestamp = strtotime( get_query_var( 'nesn_tv_schedule_year' ) . '-' . get_query_var( 'nesn_tv_schedule_monthnum' ) . '-' . get_query_var( 'nesn_tv_schedule_day' ) . ' 00:00:00'); | |
if ( ! $timestamp ) { | |
$timestamp = current_time( 'timestamp', 0 ); | |
} | |
// Decode the JSON for easy handling | |
foreach ( $schedules as $idx => $val ) { | |
if ( count( $schedules[ $idx ] ) ) { | |
$schedules[ $idx ] = json_decode( $schedules[ $idx ], true ); | |
} | |
} | |
$schedule = null; | |
// Simple find schedule | |
foreach ( $schedules as $idx => $val ) { | |
if ( (int) date( 'n', $timestamp ) === (int) $schedules[ $idx ]['meta']['scheduleMonth'] && (int) date( 'Y', $timestamp ) === (int) $schedules[ $idx ]['meta']['scheduleYear'] ){ | |
$schedule = $schedules[ $idx ]; | |
} | |
} | |
// So we know whether to show or hide the link to the next month's schedule | |
$schedule['meta']['hasNextMonthsSchedule'] = (int) $this->has_next_months_tv_schedule( $schedules, $timestamp ); | |
$data['tvSchedule'] = $schedule; | |
return $data; | |
} | |
/** | |
* Add query variables for TV schedule URLs | |
* | |
* @param $vars array | |
* @return array | |
*/ | |
public function filter_query_vars( $vars ) { | |
$vars[] = 'nesn_tv_schedule_year'; | |
$vars[] = 'nesn_tv_schedule_monthnum'; | |
$vars[] = 'nesn_tv_schedule_day'; | |
return $vars; | |
} | |
/** | |
* Add rewrite rules for the TV Schedule page | |
* | |
* @uses str_replace, trailingslashit | |
* @return array | |
*/ | |
public function action_generate_rewrite_rules( &$wp_rewrite ){ | |
$regex = ''; | |
$regex = $wp_rewrite->get_day_permastruct(); | |
// The regular expressions below were tailored to be specific to ensure they are chosen over default URL rewrites. | |
// Only allow 4-digit numbers | |
$regex = str_replace ( '%year%', '(\d{4})', $regex ); | |
// Allow at least 1 decimal number | |
$regex = str_replace ( '%monthnum%', '(\d{1,})', $regex ); | |
// Allow at least 1 decimal number | |
$regex = str_replace ( '%day%', '?(\d{1,})?', $regex ); | |
// Allows for an optional feed argument that can be added after the month argument | |
// or the day argument. | |
// $regex .= '/?(json)?/?$'; | |
$regex = trailingslashit ( $this->_tv_schedule_slug ) . trim ( $regex, '/' ); | |
$wp_rewrite->rules = array_merge( | |
array( | |
$regex => 'index.php?pagename=' . $this->_tv_schedule_slug . '&nesn_tv_schedule_year='. $wp_rewrite->preg_index(1) . '&nesn_tv_schedule_monthnum='. $wp_rewrite->preg_index(2) . '&nesn_tv_schedule_day=' . $wp_rewrite->preg_index(3) //. '&feed='. $wp_rewrite->preg_index(5), | |
), | |
$wp_rewrite->rules | |
); | |
} | |
/** | |
* Validate the query string arguments and return a timestamp | |
* | |
* @uses Exception::getMessage, File_CSV_DataSource::getHeaders, File_CSV_DataSource::isSymmetric, File_CSV_DataSource::load | |
* @return bool or WP_Error | |
*/ | |
public function find_tv_schedule_timestamp() { | |
$timestamp = false; | |
// If the user passed arguments in the query string... | |
if ( get_query_var( 'nesn_tv_schedule_year' ) && get_query_var( 'nesn_tv_schedule_monthnum' ) && get_query_var( 'nesn_tv_schedule_day' ) ) { | |
// First, check if the user entered a valid date. | |
if ( ! checkdate( (int) get_query_var( 'nesn_tv_schedule_monthnum' ), (int) get_query_var( 'nesn_tv_schedule_day' ), (int) get_query_var( 'nesn_tv_schedule_year' ) ) ) { | |
return false; | |
} | |
// If they check out okay, then attempt to build the timestamp from the querystring | |
$timestamp = strtotime( get_query_var( 'nesn_tv_schedule_year' ) . '-' . get_query_var( 'nesn_tv_schedule_monthnum' ) . '-' . get_query_var( 'nesn_tv_schedule_day' ) . ' 00:00:00'); | |
} | |
// If we failed (and we shouldn't, kept her for legacy), then use the current timestamp | |
if ( ! $timestamp ) { | |
$timestamp = current_time( 'timestamp', 0 ); | |
} | |
// However, if we succeeded, make sure the user either chose the current month or next month | |
else { | |
// If the user entered in bad dates, then return an error message | |
if ( | |
( (int) date( 'n', $timestamp ) !== (int) $this->current_month['month_num'] || (int) date( 'Y', $timestamp ) !== (int) $this->current_month['year'] ) | |
&& | |
( (int) date( 'n', $timestamp ) !== (int) $this->next_month['month_num'] || (int) date( 'Y', $timestamp ) !== (int) $this->next_month['year'] ) | |
) { | |
unset( $next ); | |
return false; | |
} | |
} | |
return $timestamp; | |
} | |
/** | |
* Validate the CSV file and return helpful error messages to the user | |
* | |
* @uses Exception::getMessage, File_CSV_DataSource::getHeaders, File_CSV_DataSource::isSymmetric, File_CSV_DataSource::load | |
* @return bool or WP_Error | |
*/ | |
private function _validate ( $headers = null, $data = null ) { | |
// Before we do anything, check to make sure the CSV data exists | |
if ( empty( $headers ) ) { | |
throw new Exception ( 'Array of required columns is missing.', 100 ); | |
} | |
// Before we do anything, check to make sure the CSV data exists | |
else if ( empty( $data ) ) { | |
throw new Exception ( 'No CSV data exists.', 101 ); | |
} | |
$csv_datasource_object = null; | |
$csv_datasource_object = new File_CSV_DataSource(); | |
try { | |
$out = ''; | |
// Can we load the data? | |
if ( ! $csv_datasource_object->loadData( $data ) ) { | |
throw new Exception ( 'Unable to load the CSV data', 101 ); | |
} | |
// Does the CSV have misspelled column names or missing columns? | |
else if ( count ( $missing_headers = array_diff ( $headers, $csv_datasource_object->getHeaders() ) ) > 0 ) { | |
// If so, display the expected headers, the actual headers, and the difference between both data sets | |
$out .= '<p class="code" id="expected-headers">Expected: '. implode(', ', $headers ) . '</p>'; | |
$out .= '<p class="code" id="actual-headers">Actual: '. implode(', ', $csv_datasource_object->getHeaders() ) . '</p>'; | |
$out .= '<p class="code" id="difference">Difference: '. implode(', ', $missing_headers ) . '</p>'; | |
throw new Exception ( 'The CSV data has missing columns, has misspelled column names, or has both. Below is a list of the expected column names and the actual column names' . $out, 300 ); | |
} | |
// Is the CSV asymmetric? | |
else if ( ! $csv_datasource_object->isSymmetric() ) { | |
// If so, then display the problem rows to the user | |
$problem_rows = $csv_datasource_object->getAsymmetricRows(); | |
$out .= '<p class="code" id="headers">'. implode(', ', $csv_datasource_object->getHeaders() ) . '</p>'; | |
$out .= '<ul class="code" id="asymmetric-rows">'; | |
foreach ( $problem_rows as $idx => $row ) { | |
$out .= '<li>'. implode(', ', $row ) . '</li>'; | |
} | |
$out .= '</ul>'; | |
throw new Exception ( 'The CSV data has ' . count( $problem_rows ) . ' asymmetric rows (too many or too few commas). Below is a list of the asymmetric rows. Fix the issues and reupload.' . $out, 301 ); | |
} | |
} | |
catch ( Exception $e ) { | |
//error_log ( 'Caught exception: ' . $e->getMessage() . "\n", 0 ); | |
return new WP_Error( $e->getCode(), $e->getMessage() ); | |
} | |
return $csv_datasource_object->export(); | |
} | |
/** | |
* Helper method to validate incoming CSV data for TV Tune-In | |
* | |
* @uses is_wp_error, intval | |
* @return bool or WP_Error | |
*/ | |
public function validate_data_for_tv_tune_in( $data, $params = array( 'process_data' => false, 'json_encode' => false ) ) { | |
$ret_val = $this->_validate( $this->_required_csv_columns['tv-tune-in'], $data ); | |
if ( 1 === intval( $params['process_data'] ) && ! is_wp_error( $ret_val ) ) { | |
$ret_val = $this->_process_tv_tune_in_data( $data, $params ); | |
} | |
return $ret_val; | |
} | |
/** | |
* Helper method to validate incoming CSV data for TV Schedule | |
* | |
* @uses is_wp_error, intval | |
* @return bool or WP_Error | |
*/ | |
public function validate_data_for_tv_schedule( $data, $params = array( 'process_data' => false, 'json_encode' => false ) ) { | |
$ret_val = $this->_validate( $this->_required_csv_columns['tv-schedule'], $data ); | |
if ( 1 === intval( $params['process_data'] ) && ! is_wp_error( $ret_val ) ) { | |
$ret_val = $this->_process_tv_schedule_data( $data, $params ); | |
} | |
return $ret_val; | |
} | |
/** | |
* Finds the TV Tune-in data | |
* @param string $theme | |
* @return array or WP_Error | |
*/ | |
public function find_tv_tune_ins_in_tv_tune_in_data( $theme ) { | |
// The value of $data could be a string or an array. | |
$data = nesn_get_content_option( 'TV Tune-In' ); | |
// Is there any data available? | |
if ( empty( $data ) ) { | |
unset( $data ); | |
return new WP_Error( -1, 'TV Tune-in data does not exist.' ); | |
} | |
// There is data. Is it an array? | |
else if ( is_array( $data ) ) { | |
// Has the desktop theme been requested and is there tune-in data available for it? | |
if ( 'Desktop' === $theme && array_key_exists( 0, $data ) ) { | |
$data = $data[0]; | |
} | |
// Or, has the mobile theme been requested and is there tune-in data available for it? | |
else if ( 'Mobile' === $theme && array_key_exists( 1, $data ) ) { | |
$data = $data[1]; | |
} | |
// Data mismatch: The mobile theme was requested, but only desktop tune-in data exists. | |
// or, the desktop theme was requested, but only the mobile tune-in data exists | |
else { | |
unset( $data ); | |
return new WP_Error( -1, 'Data mismatch: the mobile theme was requested, but only desktop tune-in data exists, or, the desktop theme was requested, but only the mobile tune-in data exists' ); | |
} | |
} | |
$arr = null; | |
// Added one more sanity check | |
$arr = ( is_string( $data ) ) ? json_decode( $data, true ) : $data; | |
unset( $data ); | |
// There was something wrong with the data | |
if ( ! is_array( $arr ) || empty( $arr ) || json_last_error() !== JSON_ERROR_NONE ) { | |
return new WP_Error( -1, 'There is something wrong with the data. Too difficult to tell what the problem is.' ); | |
} | |
$current_timestamp = 0; | |
$look_behind = array(); | |
$current_timestamp = current_time( 'timestamp', 0 ); | |
foreach ( $arr as $timestamp => $tune_ins ) { | |
// We've already passed the publish time, so, continue on to the next tune-in | |
if ( $current_timestamp > (int) $timestamp ) { | |
$look_behind = $tune_ins; | |
continue; | |
} | |
// We've yet to pass the publish time, so the tune-in that we want is stored in $look_behind | |
else if ( $current_timestamp < (int) $timestamp ) { break; } | |
} | |
if ( empty( $look_behind ) ) { | |
return new WP_Error( -1, 'No current tune-in messages were found. The data exists, but it\'s out of date.' ); | |
} | |
return $look_behind; | |
} | |
/** | |
* Builds an array of tune-in messages using today's TV schedule. Only select programs are promoted in the TV tune-in. | |
* Credit to Karen Belmonte for the inspiration. | |
* | |
* @uses array_intersect_key, array_key_exists, current_time, date, in_array, nesn_on_air_time_format, strtotime, unset, usort | |
* @param string $theme | |
* @return bool or WP_Error | |
*/ | |
public function find_tv_tune_ins_in_tv_schedule_data ( $theme ) { | |
// Define function options here. | |
$options = array( | |
'display' => array( | |
'count' => 3, // Number of tune-in messages to show | |
'text_today' => 'TODAY', | |
'text_tonight' => 'TONIGHT', | |
'text_next' => 'NEXT', | |
'text_now' => 'NOW', | |
// The default tune-in message | |
'default' => array( | |
'text' => 'Today on NESN', | |
'url' => 'http://nesn.com/tv-schedule' | |
) | |
), | |
// Defines thresholds of time and duration | |
'thresholds' => array( | |
'nighttime' => 17, // The hour at which tune-in messages will begin to read "TONIGHT" | |
'coming_up' => 1800 // The duration in seconds at which tune-in messages will read "NEXT" | |
) | |
); | |
$schedules = nesn_get_content_option( 'TV Schedule' ); | |
// If we cannot find valid schedule data... | |
if ( empty( $schedules ) || ! is_array( $schedules ) ) { | |
unset( $schedules ); | |
return new WP_Error( -1, 'No TV schedules exist.' ); | |
} | |
$schedule = null; | |
// Decode the JSON for easy handling | |
foreach ( $schedules as $idx => $val ) { | |
if ( count( $schedules[ $idx ] ) ) { | |
$schedules[ $idx ] = json_decode( $schedules[ $idx ], true ); | |
} | |
} | |
// Find the current schedule | |
foreach ( $schedules as $idx => $val ) { | |
if ( (int) date( 'n' ) === (int) $schedules[ $idx ]['meta']['scheduleMonth'] && (int) date( 'Y' ) === (int) $schedules[ $idx ]['meta']['scheduleYear'] ) { | |
$schedule = $schedules[ $idx ]; | |
} | |
} | |
unset( $schedules ); | |
// If the current month's schedule data is stored in the next month's schedule field, $schedule will be empty | |
if ( empty( $schedule ) ) { | |
return new WP_Error( -1, 'No TV schedules exist.' ); | |
} | |
// The function current_time( 'timestamp' ) returns the local timestamp | |
// See: http://vip.wordpress.com/documentation/use-current_time-not-date_default_timezone_set/ | |
$current_timestamp = current_time( 'timestamp' ); | |
// $promotable_programs is a case-insensitive, prioritized list | |
// of programs which will be promoted as tune-in messages. | |
// | |
// When adding to the list, write in the title of program only. | |
// Do not include "live", "presented by", "(:15)" or any other | |
// extraneous information. | |
$promotable_programs = array( | |
/*** Pre-Game Shows ***/ | |
0 => array( | |
'Red Sox GameDay' => 'http://nesn.com/boston-red-sox', | |
'Red Sox First Pitch' => 'http://nesn.com/boston-red-sox', | |
'Bruins Face-Off' => 'http://nesn.com/boston-bruins' | |
), | |
/*** Bruins and Sox Games ***/ | |
// This is for live coverage only. This is not for | |
// Bruins in 2 or Sox in 2 | |
1 => array( | |
'Boston Red Sox Baseball' => 'http://nesn.com/boston-red-sox', | |
'Boston Bruins Hockey' => 'http://nesn.com/boston-bruins', | |
), | |
/*** Live Programming and Post-Game Shows ***/ | |
// These are weighted the same and will be promoted in | |
// chronological order | |
2 => array( | |
// Post-game shows | |
'W.B. Mason Extra Innings' => 'http://nesn.com/boston-red-sox', | |
'Red Sox Final' => 'http://nesn.com/boston-red-sox', | |
// Live programming | |
'Dennis and Callahan Morning Show' => 'http://nesn.com/dennis-and-callahan' | |
), | |
/*** In-Studio Shows ***/ | |
3 => array( | |
'Sports Today' => 'http://nesn.com/nesn-sports-today', | |
'The Ultimate Red Sox Show' => 'http://nesn.com/boston-red-sox' | |
), | |
/*** Last Priority ***/ | |
// This is a catch-all for shows which need to be promoted, | |
// but should not appear before anything else. | |
4 => array( | |
'Dirty Water TV' => 'http://nesn.com/dirty-water-tv', | |
'Charlie Moore Outdoors' => 'http://nesn.com/charlie-moore-outdoors' | |
) | |
); | |
// Find the current day | |
$todays_schedule = $schedule['data'][ date( 'j', $current_timestamp ) - 1 ]; | |
$stack = array(); | |
$programs_copy = array(); | |
$cleaned_title = ''; | |
unset( $schedule ); | |
// Iterate over each program for the day and normalize the title | |
foreach ( $todays_schedule as $idx => $program ) { | |
// Convert the show title to lowercase to begin processing | |
$cleaned_title = strtolower( $program['title'] ); | |
// Remove "live" from the title because, sometimes, it's not | |
// included in the TV schedule. | |
$cleaned_title = str_replace( 'live', '', $cleaned_title ); | |
// Remove "(:15)" from the title. You will only see this | |
// with the program NESN Sports Today | |
$cleaned_title = str_replace( '(:15)', '', $cleaned_title ); | |
// Strip out "presented by" and the sponsor's name that | |
// follows, if it exists | |
if ( false !== strpos( $cleaned_title, 'presented by' ) ) { | |
$cleaned_title = substr( $cleaned_title, 0, strpos( $cleaned_title, 'presented by' ) ); | |
} | |
// Change WB Mason to W.B. Mason. Sometimes the periods are | |
// included, sometimes they aren't. I'm not sure if there's | |
// a better way to handle this. | |
$cleaned_title = str_replace( 'wb mason', 'w.b. mason', $cleaned_title ); | |
// This must stay here! Eliminate residual whitespace | |
// AFTER string manipulation | |
$cleaned_title = trim( $cleaned_title ); | |
// Find promotable programs | |
foreach ( $promotable_programs as $priority => $programs ) { | |
// Normalize the keys of $promotable_programs to | |
// lowercase to match against $cleaned_title | |
$programs_copy = array_change_key_case( $programs, CASE_LOWER ); | |
// Use a case-insensitive method to find promotable | |
// programs | |
if ( array_key_exists( $cleaned_title, $programs_copy ) ) { | |
$program['url'] = $programs_copy[ $cleaned_title ]; | |
$program['priority'] = $priority; | |
$program['duration'] = 0; | |
$program['status'] = ''; // airing, has aired, will air | |
// Use the episode field if it's a Bruins or a | |
// Sox game | |
if ( strtolower( 'Boston Red Sox Baseball' ) === $cleaned_title || strtolower( 'Boston Bruins Hockey' ) === $cleaned_title ) { | |
$program['title'] = $program['episode']; | |
} | |
// Is there another program to air after $program? | |
if ( array_key_exists( $idx + 1, $todays_schedule ) ) { | |
// Find the duration of $program | |
$program['duration'] = $todays_schedule[ $idx + 1 ]['startTS'] - $program['startTS']; | |
} | |
// $program must be running until midnight. | |
else { | |
// Find 11:59:59pm of the current day and then | |
// add 1 second to find the program duration | |
$program['duration'] = ( strtotime( '23:59:59', $program['startTS'] ) - $program['startTS'] ) + 1; | |
} | |
// Has the program already aired? | |
if ( $current_timestamp > $program['startTS'] + $program['duration'] ) { | |
$program['status'] = 'has aired'; | |
} | |
// Or, is it airing now? | |
else if ( $current_timestamp > $program['startTS'] && $current_timestamp < $program['startTS'] + $program['duration'] ) { | |
$program['status'] = 'airing'; | |
} | |
// If none of the above, then it must be airing in | |
// the future. | |
else { | |
$program['status'] = 'will air'; | |
} | |
$stack[] = $program; | |
break; | |
} | |
} | |
} | |
unset( $idx, $programs, $programs_copy, $todays_schedule ); | |
// Sort the promotable programs by priority. If priorities are equal, sort by air time | |
usort( $stack, function( $a, $b ) use ( &$promotable_programs ) { | |
if ( $a['priority'] === $b['priority'] ) { | |
return $a['startTS'] - $b['startTS']; | |
} | |
// Here we are sorting based on the index of the element in the array $promotable_programs | |
return $a['priority'] - $b['priority']; | |
}); | |
$tune_ins = array(); | |
// Build the tune-ins | |
foreach ( $stack as $program ) { | |
// Do we already have tune-ins ready to go? | |
if ( count( $tune_ins ) === $options['display']['count'] ) { | |
break; | |
} | |
// Have any programs within this block aired already? If so, break out of the foreach loop and continue to the next pattern | |
else if ( 'has aired' === $program['status'] ) { | |
continue; | |
} | |
// We have a valid block | |
else { | |
// Is the program already airing? | |
if ( 'airing' === $program['status'] ) { | |
$tune_ins[] = array( | |
'text' => $options['display']['text_now'] . ': ' . $program['title'], | |
'url' => 'http://nesn.com/tv-schedule' | |
); | |
} | |
// Or will it air later on today? | |
else if ( 'will air' === $program['status'] ) { | |
// Will the program air in the next 30 minutes? | |
if ( $program['startTS'] - $current_timestamp <= $options['thresholds']['coming_up'] ) { | |
$tune_ins[] = array( | |
'text' => sprintf( "%s : %s", $options['display']['text_next'],$program['title'] ), | |
'url' => esc_url( $program['url'] ) | |
); | |
} | |
// Is the program airing "tonight" or "today"? | |
else if ( (int) date( 'G', $program['startTS'] ) >= $options['thresholds']['nighttime'] ) { | |
$tune_ins[] = array( | |
'text' => sprintf( "%s %s : %s", $options['display']['text_tonight'], nesn_on_air_time_format( $program['startTS'] ), $program['title'] ), | |
'url' => esc_url( $program['url'] ) | |
); | |
} | |
else { | |
$tune_ins[] = array( | |
'text' => $options['display']['text_today'] . ' ' . nesn_on_air_time_format( $program['startTS'] ) . ': ' . $program['title'], | |
'url' => esc_url( $program['url'] ) | |
); | |
} | |
} | |
// If we got here, which we never should, then there was a programming syntax error | |
else { | |
return new WP_Error( -1, 'The airing status for the program was either not set or there is a coding syntax error.' ); | |
} | |
} | |
} | |
unset( $program, $stack ); | |
// Are we short a tune-in or two? If so, add the default text | |
if ( count( $tune_ins ) !== $options['display']['count'] ) { | |
$tune_ins[] = $options['display']['default']; | |
} | |
return $tune_ins; | |
} | |
/** | |
* Convert the CSV data to JSON | |
* | |
* @uses json_encode | |
* @param string $data | |
* @return string or WP_Error | |
*/ | |
private function _process_tv_tune_in_data ( $data, $params ) { | |
$i = 0; | |
$limit = 4; // 3 is the number of tune-ins, so, we have 4 to fight the off-by-one error | |
$csv = new File_CSV_DataSource(); | |
$stack = array(); | |
$output_array = array(); | |
$out = ''; | |
$csv->loadData( $data ); | |
$arr = $csv->toArray(); | |
unset( $csv ); | |
foreach ( $arr as $idx => $val ) { | |
// $i indicates tune_in_text_1, tune_in_text_2 and so on | |
for ( $i = 1; $i !== $limit; ++ $i ) { | |
$stack[ $i ]['text'] = $val[ 'tune_in_text_' . $i ]; | |
$stack[ $i ]['url'] = $val[ 'tune_in_link_' . $i ]; | |
} | |
// Convert both columns into one timestamp | |
$timestamp = strtotime ( $val[ 'pub_date' ] . ' ' . $val[ 'pub_time' ] ); | |
$output_array[ $timestamp ] = $stack; | |
$stack = array(); | |
} | |
unset ( $arr, $val, $stack ); | |
if ( $params['json_encode'] ) { | |
$out = json_encode ( $output_array ); | |
unset ( $output_array ); | |
// In the unlikely event that there is a JSON encoding error... | |
if ( json_last_error() !== JSON_ERROR_NONE ) { | |
return new WP_Error( json_last_error(), 'The schedule could not be converted from a CSV to JSON. This is a tricky one to debug.' ); | |
} | |
return $out; | |
} | |
return $output_array; | |
} | |
/** | |
* Convert the CSV data to JSON | |
* | |
* @uses json_encode | |
* @param string $data | |
* @return string or WP_Error | |
*/ | |
private function _process_tv_schedule_data ( $data, $params ) { | |
// Rendering options. Edit these as necessary | |
$options = array( | |
'display' => array( | |
'consolidate_consecutive_programs' => 1, // Consolidate consecutive programs into one block | |
'show_program_end_time' => 0, // Show the end time? | |
'remove_recorded_label' => 1, // Remove the recorded label | |
'recorded_label' => '(R)', // The recorded label | |
'abbreviate_team_names' => 0, // Use BOS instead of Boston Red Sox | |
'guess_program_links' => 0 // Guess the links associated with each show | |
), | |
); | |
$i = 0; | |
$j = 0; | |
$limit = 0; | |
$row = null; | |
$look_ahead = null; | |
$program_start_timestamp = 0; | |
$program_end_timestamp = 0; | |
$last_processed_day = 0; | |
$out = ''; | |
$csv = new File_CSV_DataSource(); | |
$stack = array(); | |
$output_array = array(); | |
$csv->loadData( $data ); | |
$arr = $csv->toArray(); | |
unset( $csv ); | |
$limit = count( $arr ); | |
// A for loop is required because we need to look ahead | |
for ( $i = 0; $i !== $limit ; ++ $i ) { | |
// Make it easier to read | |
$row = $arr[ $i ]; | |
// Find the look-ahead row | |
$look_ahead = ( $limit - 1 !== $i ) ? $arr[ $i + 1 ] : null; | |
// Find the timestamp of the current row | |
$program_start_timestamp = strtotime( $row['Date'] . ' ' . $row['Time'] ); | |
// Find the timestamp for the look-ahead row | |
if ( ! empty( $look_ahead ) ) { | |
$program_end_timestamp = strtotime( $look_ahead['Date'] . ' ' . $look_ahead['Time'] ); | |
} | |
else { | |
$program_end_timestamp = $program_start_timestamp; | |
} | |
/* INITIAL PASS */ | |
if ( 0 === $i ) { | |
$last_processed_day = date ( 'z', $program_start_timestamp ); | |
} | |
/* NEW DAY */ | |
if ( date( 'z', $program_start_timestamp ) !== $last_processed_day ) { | |
$output_array['data'][] = $stack; | |
// Reset the stack | |
$stack = array(); | |
$j = 0; | |
$last_processed_day = date( 'z', $program_start_timestamp ); | |
} | |
// OPTION: Program Consolidation | |
if ( 1 === (int) $options['display']['consolidate_consecutive_programs'] ) { | |
if ( $j > 0 && count( $stack ) >= 1 ) { | |
// Is the title and the episode equal? | |
if ( $stack[ $j - 1 ]['title'] == trim ( $row['Program Name'] ) && $stack[ $j - 1 ]['episode'] == trim ( $row['Title Name'] ) ) { | |
// OPTION: Show program end time | |
if ( 1 === (int) $options['display']['show_program_end_time'] ){ | |
$stack[ $j - 1 ]['end_ts'] = $program_end_timestamp; | |
} | |
// Process the next record | |
continue; | |
} | |
} | |
} | |
$stack[ $j ]['startTS'] = $program_start_timestamp; | |
// OPTION: Show program end time | |
if ( 1 === (int) $options['display']['show_program_end_time'] ) { | |
$stack[ $j ]['endTS'] = $program_end_timestamp; | |
} | |
// Store the basic show information | |
$stack[ $j ]['title'] = trim ( $row[ 'Program Name'] ); | |
$stack[ $j ]['episode'] = trim ( $row[ 'Title Name'] ); | |
$stack[ $j ]['airing'] = trim ( $row[ 'Airing Status'] ); | |
// OPTION: Remove label "recorded" | |
if ( 1 === (int) $options['display']['remove_recorded_label'] ) { | |
// Find the label "recorded" and remove it | |
if ( strstr( $stack[ $j ]['airing'], $options['display']['recorded_label'] ) ) { | |
$stack[ $j ]['airing'] = ''; | |
} | |
} | |
// Increment the stack counter. | |
++ $j; | |
} | |
/* FINAL PASS */ | |
$output_array['data'][] = $stack; | |
unset ( $row, $stack ); | |
// Creation date of the schedule. This was added in case other NESN employees reference the schedule, they will know how fresh the data is | |
$output_array['meta']['modified'] = $output_array['meta']['created'] = current_time( 'timestamp', 0 ); | |
// Month number of the schedule | |
$output_array['meta']['scheduleMonth'] = (int) date ( 'n', $program_start_timestamp ); | |
// Year of the schedule | |
$output_array['meta']['scheduleYear'] = (int) date ( 'Y', $program_start_timestamp ); | |
//The permalink structure. This has been added so that we can pass this structure to the client, removing guess work when building URLs with JavaScript | |
$output_array['meta']['permalink'] = $this->permalink; | |
// Whether to encode the data as JSON now or later. | |
if ( $params['json_encode'] ) { | |
$out = json_encode ( $output_array ); | |
unset ( $output_array ); | |
// In the unlikely event that there is a JSON encoding error... | |
if ( json_last_error() !== JSON_ERROR_NONE ) { | |
return new WP_Error( json_last_error(), 'The schedule could not be converted from a CSV to JSON. This is a tricky one to debug.' ); | |
} | |
return $out; | |
} | |
unset ( $out ); | |
return $output_array; | |
} | |
/** | |
* Display the calendar for the TV Schedule | |
* | |
* The calendar is cached, which will be retrieved, if it exists. | |
* | |
* @uses md5, wp_cache_get, wp_cache_set | |
* @param int $timestamp Required | |
*/ | |
public function render_tv_schedule_calendar ( $timestamp ) { | |
$cache_key = md5( __CLASS__ . __FUNCTION__ . date( 'Ym', $timestamp ) ); | |
$cache = ''; | |
// Check if the user made a request to flush the caches | |
if ( current_user_can( 'manage_options' ) && isset( $_GET['nesn_cache_clear'] ) ) { | |
wp_cache_delete( $cache_key, $GLOBALS[ 'nesn_cache_group' ] ); | |
} | |
// Otherwise, let's see if we have an existing cache already | |
// Empty strings are okay, false means no cache | |
else if ( false !== $cache = wp_cache_get( $cache_key, $GLOBALS[ 'nesn_cache_group' ] ) ) { | |
return $cache; | |
} | |
// Rendering options | |
$options = array( | |
'show_previous_month' => false, | |
'show_next_month' => false | |
); | |
global $wp_locale; | |
$permalink = ''; | |
$previous = nesn_singleton( 'NESN_Programming' )->previous_month; | |
$current = nesn_singleton( 'NESN_Programming' )->current_month; | |
$next = nesn_singleton( 'NESN_Programming' )->next_month; | |
$schedules = nesn_singleton( 'NESN_Content_Options' )->get_option('TV Schedule'); | |
// Should we show the link to next month's schedule? | |
if ( ( (int) date( 'n', $timestamp ) === (int) $current['month_num'] && (int) date( 'Y', $timestamp ) === (int) $current['year'] ) && $this->has_next_months_tv_schedule( $schedules, $timestamp ) ) { | |
$options['show_next_month'] = true; | |
} | |
// Or should we show the link to the previous month's schedule? | |
else if ( (int) date( 'n', $timestamp ) === (int) $next['month_num'] && (int) date( 'Y', $timestamp ) === (int) $next['year'] ) { | |
$options['show_previous_month'] = true; | |
} | |
// week_begins = 0 stands for Sunday | |
$week_begins = (int) get_option ( 'start_of_week' ); | |
$unix_month = mktime ( 0, 0 , 0, (int) date( 'n', $timestamp ), 1, (int) date( 'Y', $timestamp ) ); | |
$last_day = date( 't', $unix_month ); | |
/* translators: Calendar caption: 1: month name, 2: 4-digit year */ | |
$calendar_caption = _x('%1$s %2$s', 'calendar caption'); | |
$calendar_output = '<table id="program-calendar"> | |
<caption>' . sprintf( $calendar_caption, $wp_locale->get_month ( date( 'n', $timestamp ) ), date ( 'Y', $unix_month ) ) . '</caption> | |
<thead> | |
<tr>'; | |
$myweek = array(); | |
for ( $weekday_count = 0; 7 !== $weekday_count; ++ $weekday_count ) { | |
$myweek[] = $wp_locale->get_weekday ( ( $weekday_count + $week_begins ) % 7 ); | |
} | |
foreach ( $myweek as $wd ) { | |
$day_name = $wp_locale->get_weekday_initial( $wd ); | |
$wd = esc_attr ( $wd ); | |
$calendar_output .= "\n\t\t<th class=\"day-of-week\" scope=\"col\" title=\"$wd\">$day_name</th>"; | |
} | |
$calendar_output .= ' | |
</tr> | |
</thead> | |
<tfoot> | |
<tr>'; | |
if ( 1 === (int) $options['show_previous_month'] ) { | |
$permalink = $this->permalink; | |
$permalink = str_replace ( '%year%', $current['year'], $permalink ); | |
$permalink = str_replace ( '%monthnum%', $current['month_num'], $permalink ); | |
// Link to the last day of the previous month | |
$permalink = str_replace ( '%day%', date( 't', strtotime( $current['year'] . '-'. $current['month_num'] . '-'. $current['day'] ) ), $permalink ); | |
$calendar_output .= "\n\t\t".'<td colspan="2" id="next"><a id="month-previous" href="' . $permalink .'" title="' . esc_attr( sprintf(__('View TV Schedule for %1$s'), $wp_locale->get_month ( $current['month_num'] ) ) ) . '">« ' . $wp_locale->get_month_abbrev ( $wp_locale->get_month ( $current['month_num'] ) ) . '</a></td>'; | |
} | |
$calendar_output .= "\n\t\t" . '<td colspan="3" id="prev" class="pad"> </td>'; | |
$calendar_output .= "\n\t\t" . '<td class="spacer"> </td>'; | |
if ( 1 === (int) $options['show_next_month'] ) { | |
$permalink = $this->permalink; | |
$permalink = str_replace ( '%year%', $next['year'], $permalink ); | |
$permalink = str_replace ( '%monthnum%', $next['month_num'], $permalink ); | |
$permalink = str_replace ( '%day%', 1, $permalink ); | |
$calendar_output .= "\n\t\t".'<td colspan="3" id="next"><a id="month-next" href="' . $permalink . '" title="' . esc_attr( sprintf(__('View TV Schedule for %1$s'), $wp_locale->get_month ( $next['month_num'] ) ) ) . '">' . $wp_locale->get_month_abbrev ( $wp_locale->get_month ( $next['month_num'] ) ) . ' »</a></td>'; | |
} | |
else { | |
$calendar_output .= "\n\t\t".'<td colspan="3" id="next" class="spacer"> </td>'; | |
} | |
$calendar_output .= ' | |
</tr> | |
</tfoot> | |
<tbody> | |
<tr>'; | |
// See how much we should pad in the beginning | |
$pad = calendar_week_mod( date ( 'w', $unix_month ) - $week_begins ); | |
if ( 0 !== $pad ) { | |
$calendar_output .= "\n\t\t".'<td colspan="'. esc_attr ( $pad ) .'" class="pad"> </td>'; | |
} | |
$days_in_month = (int) date( 't', $unix_month ); | |
for ( $day = 1; $day <= $days_in_month; ++ $day ) { | |
if ( isset( $new_row ) && $new_row ) { | |
$calendar_output .= "\n\t</tr>\n\t<tr>\n\t\t"; | |
} | |
$new_row = false; | |
$calendar_output .= '<td id="day-' . $day . '"'; | |
if ( (int) $day === (int) $current['day'] && (int) date( 'n', $timestamp ) === (int) $current['month_num'] && (int) date( 'Y', $timestamp ) === (int) $current['year'] ) { | |
$calendar_output .= ' class="today">'; | |
} | |
/* | |
// TODO: Figure out a reliable way to handle the "selected" day | |
else if ( (int) $day == (int) date( 'j', $timestamp ) ) { | |
$calendar_output .= ' class="selected">'; | |
} | |
*/ | |
else { | |
$calendar_output .= '>'; | |
} | |
$permalink = $this->permalink; | |
$permalink = str_replace ( '%year%', date( 'Y', $timestamp ), $permalink ); | |
$permalink = str_replace ( '%monthnum%', date( 'n', $timestamp ), $permalink ); | |
$permalink = str_replace ( '%day%', $day, $permalink ); | |
$calendar_output .= '<a href="' . $permalink . '">' . $day . '</a>'; | |
$calendar_output .= '</td>'; | |
if ( 6 == calendar_week_mod ( date ( 'w', mktime ( 0, 0, 0, date( 'n', $unix_month ), $day, date( 'Y', $unix_month ) ) ) - $week_begins ) ) { | |
$new_row = true; | |
} | |
} | |
$pad = 7 - calendar_week_mod( date( 'w', mktime ( 0, 0 , 0, date( 'n', $unix_month ), $day, date( 'Y', $unix_month ) ) ) - $week_begins ); | |
if ( $pad > 0 && $pad < 7 ) { | |
$calendar_output .= "\n\t\t".'<td class="pad" colspan="'. esc_attr ( $pad ) .'"> </td>'; | |
} | |
$calendar_output .= "\n\t</tr>\n\t</tbody>\n\t</table>"; | |
// Cache the results for 1 hour. This will ensure that the calendar is changed on the first of the month | |
wp_cache_set( $cache_key, $calendar_output, $GLOBALS[ 'nesn_cache_group' ], 60 * 60 * 1 ); | |
return $calendar_output; | |
} | |
} | |
nesn_singleton( 'NESN_Programming' ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment