Skip to content

Instantly share code, notes, and snippets.

@jkereako
Created August 2, 2013 17:04
Show Gist options
  • Save jkereako/5db9f7fd36493f67208a to your computer and use it in GitHub Desktop.
Save jkereako/5db9f7fd36493f67208a to your computer and use it in GitHub Desktop.
Handles the TV Tune-In and the TV Schedule.
<?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'] ) ) ) . '">&laquo; ' . $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">&nbsp;</td>';
$calendar_output .= "\n\t\t" . '<td class="spacer">&nbsp;</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'] ) ) . ' &raquo;</a></td>';
}
else {
$calendar_output .= "\n\t\t".'<td colspan="3" id="next" class="spacer">&nbsp;</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">&nbsp;</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 ) .'">&nbsp;</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