Merge branch 'feature/multi-team-schedule-exporter'

This commit is contained in:
2026-05-18 18:35:36 -05:00
5 changed files with 1680 additions and 189 deletions

View File

@@ -27,10 +27,7 @@ body {
.print-shell {
margin: 0;
width: calc(100% / var(--sheet-scale));
background: #fff;
transform-origin: top left;
transform: scale(var(--sheet-scale));
}
.print-page {
@@ -48,7 +45,10 @@ body {
}
body.print-preview .print-shell {
flex: 0 0 auto;
box-shadow: 0 10px 30px rgba(17, 24, 39, 0.2);
transform-origin: top left;
transform: scale(var(--sheet-scale));
}
body.print-preview .print-shell.letter {
@@ -60,6 +60,104 @@ body {
width: 11in;
min-height: 17in;
}
body.print-preview.month-pages {
display: block;
overflow-x: auto;
}
body.print-preview.month-pages .print-shell,
body.print-preview.month-pages .print-shell.letter,
body.print-preview.month-pages .print-shell.ledger {
width: auto;
min-height: auto;
background: transparent;
box-shadow: none;
transform: none;
}
body.print-preview.month-pages .print-page {
padding: 0;
}
body.print-preview.month-pages .month-page {
box-sizing: border-box;
width: 8.5in;
min-height: 11in;
margin-right: auto;
margin-left: auto;
padding: var(--pc-page-padding);
background: #fff;
box-shadow: 0 10px 30px rgba(17, 24, 39, 0.2);
}
body.print-preview.month-pages.ledger .month-page {
width: 11in;
min-height: 17in;
}
body.print-preview.month-pages .month-page + .month-page {
margin-top: 24px;
}
body.print-preview.month-pages .month-page .header {
margin-bottom: 24px;
}
body.print-preview.month-pages .month-page .footer-meta {
margin-top: 24px;
}
body.print-preview.month-pages .sheet-grid {
display: block;
}
body.print-preview.month-pages .month {
width: 100%;
}
body.month-pages .month-title {
font-size: calc(26px * var(--month-font-scale));
padding: 4px;
}
body.month-pages .dow span {
font-size: calc(12px * var(--month-font-scale));
padding: 4px 2px;
}
body.month-pages .day-num {
font-size: calc(14px * var(--month-font-scale));
}
body.month-pages .event-name {
font-size: calc(13px * var(--month-font-scale));
line-height: 0.98;
}
body.month-pages .matchup-name {
font-size: calc(13px * var(--month-font-scale));
line-height: 0.92;
}
body.month-pages .event-time {
font-size: calc(14px * var(--month-font-scale));
}
body.month-pages .event.matchup .event-time {
font-size: calc(11px * var(--month-font-scale));
}
body.month-pages .event.matchup .event-venue {
font-size: calc(10px * var(--month-font-scale));
}
}
@media screen and (max-width: 900px) {
body.print-preview {
justify-content: flex-start;
overflow-x: auto;
}
}
.header {
@@ -235,7 +333,7 @@ body {
position: absolute;
top: var(--corner-badge-offset);
left: var(--corner-badge-offset);
z-index: 4;
z-index: 20;
width: var(--corner-badge-size);
height: var(--corner-badge-size);
display: flex;
@@ -252,12 +350,46 @@ body {
color: var(--day-num-color, #fff);
}
.day.has-matchups {
background: #fff;
border: 1px solid var(--pc-border);
}
.day.has-matchups .day-num {
position: absolute;
z-index: 20;
background: #fff;
color: var(--team-ink, #111) !important;
text-shadow: none !important;
box-shadow: 0 0 0 1px var(--pc-border);
}
body.month-pages .day .day-num {
top: 1px;
left: 1px;
width: var(--corner-badge-size);
height: var(--corner-badge-size);
border-radius: 0;
background: #fff;
color: var(--team-ink, #111);
text-shadow: none;
}
.events-stack {
position: relative;
z-index: 1;
height: 100%;
display: grid;
grid-template-rows: repeat(var(--event-count), minmax(0, 1fr));
}
.day.has-matchups .events-stack {
box-sizing: border-box;
grid-template-rows: repeat(var(--event-count), minmax(0, 1fr));
gap: 2px;
padding: 2px;
}
.event {
--event-top-band: calc(var(--corner-badge-size, 11px) + var(--corner-badge-offset, 2px));
--event-bottom-band: 26px;
@@ -275,6 +407,17 @@ body {
--event-bg: var(--team-link-color, var(--team-secondary, #8b3f1f));
}
.event.matchup {
display: grid;
grid-template-columns: minmax(0, 1fr);
grid-template-rows: minmax(0, 1fr) auto;
gap: 1px;
min-height: 0;
padding: 2px 3px;
border-radius: 2px;
overflow: hidden;
}
.event-center {
position: absolute;
top: var(--event-top-band);
@@ -293,6 +436,17 @@ body {
text-align: center;
}
.event.matchup .event-center {
position: relative;
top: auto;
left: auto;
right: auto;
bottom: auto;
min-height: 0;
align-items: center;
justify-content: center;
}
.event-center img {
width: auto;
height: var(--event-logo-height);
@@ -315,6 +469,8 @@ body {
hyphens: none;
line-height: 1.05;
font-weight: 700;
font-stretch: condensed;
font-variation-settings: "wdth" 42, "wght" 700;
opacity: 0.85;
}
@@ -322,10 +478,38 @@ body {
font-family: var(--pc-font-display);
font-weight: 700;
font-variation-settings: "wdth" 30, "wght" 700;
letter-spacing: -0.01em;
text-transform: uppercase;
}
.matchup-name {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0;
padding: 0;
font-size: calc(8.5px * var(--month-font-scale));
line-height: 0.98;
font-variation-settings: "wdth" 36, "wght" 800;
opacity: 1;
}
.matchup-vs {
font-size: 0.66em;
font-weight: 900;
opacity: 0.78;
}
.matchup-team {
display: block;
width: 100%;
overflow: hidden;
text-align: center;
text-overflow: clip;
white-space: nowrap;
font-stretch: condensed;
}
.ha-flag {
position: absolute;
top: var(--corner-badge-offset, 2px);
@@ -341,7 +525,7 @@ body {
font-weight: 900;
font-variation-settings: "wdth" 84, "wght" 900;
line-height: 1;
letter-spacing: -0.01em;
letter-spacing: 0;
background: #111;
color: #fff;
}
@@ -366,11 +550,26 @@ body {
overflow: hidden;
}
.event.matchup .event-meta {
position: relative;
left: auto;
right: auto;
bottom: auto;
display: grid;
grid-template-columns: minmax(0, auto) minmax(0, 1fr);
align-items: center;
justify-content: center;
column-gap: 3px;
padding: 0;
}
.event-time {
order: 1;
max-width: 100%;
font-size: calc(12px * var(--month-font-scale));
font-weight: 800;
font-stretch: condensed;
font-variation-settings: "wdth" 56, "wght" 800;
line-height: 1;
text-transform: uppercase;
text-align: center;
@@ -381,11 +580,19 @@ body {
opacity: 0.95;
}
.event.matchup .event-time {
min-width: 0;
font-size: calc(7.5px * var(--month-font-scale));
font-weight: 900;
}
.event-venue {
order: 2;
max-width: 100%;
font-size: calc(8px * var(--month-font-scale));
font-weight: 700;
font-stretch: condensed;
font-variation-settings: "wdth" 50, "wght" 700;
line-height: 1;
text-align: center;
text-transform: uppercase;
@@ -395,6 +602,12 @@ body {
opacity: 0.88;
}
.event.matchup .event-venue {
min-width: 0;
font-size: calc(7px * var(--month-font-scale));
text-align: left;
}
.empty {
padding: 16px;
border: 2px dashed #c8d2de;
@@ -472,6 +685,100 @@ body {
background: #fff;
}
body.black-white .header,
body.black-white .month-title,
body.black-white .dow span,
body.black-white .day,
body.black-white .event,
body.black-white .empty,
body.black-white .footer-meta,
body.black-white .footer-qr-image {
border-color: #2f3337 !important;
}
body.black-white .month-title,
body.black-white .ha-flag {
background: #1f2328 !important;
color: #fff !important;
text-shadow: none !important;
}
body.black-white .dow span,
body.black-white .day,
body.black-white .day.muted,
body.black-white .day.no-events,
body.black-white .day.has-matchups,
body.black-white .empty,
body.black-white .legend-item,
body.black-white .event.a .ha-flag {
background: #f7f7f7 !important;
color: #111 !important;
text-shadow: none !important;
}
body.black-white .dow span {
background: #e4e7eb !important;
}
body.black-white .day,
body.black-white .day.has-matchups,
body.black-white .legend-item {
background: #fff !important;
}
body.black-white .event,
body.black-white .event.h,
body.black-white .event.a,
body.black-white .event.matchup {
background: #e4e7eb !important;
color: #111 !important;
text-shadow: none !important;
}
body.black-white .day.muted,
body.black-white .day.no-events {
background: #f2f3f5 !important;
color: #4b5563 !important;
}
body.black-white .month-title,
body.black-white .dow span,
body.black-white .day,
body.black-white .event {
border: 1px solid #2f3337;
}
body.black-white .day-num,
body.black-white .day.has-events .day-num,
body.black-white .day.has-matchups .day-num,
body.black-white.month-pages .day .day-num {
background: #fff !important;
color: #111 !important;
box-shadow: 0 0 0 1px #2f3337 !important;
text-shadow: none !important;
}
body.black-white .ha-flag,
body.black-white .event.a .ha-flag {
box-shadow: 0 0 0 1px #2f3337;
}
body.black-white .event-name,
body.black-white .event-time,
body.black-white .event-venue,
body.black-white .matchup-vs,
body.black-white .footer-qr-label,
body.black-white .footer-qr-link,
body.black-white .meta {
color: #111 !important;
opacity: 1 !important;
}
body.black-white .meta,
body.black-white .footer-qr-label {
color: #374151 !important;
}
@media print {
body,
body.print-preview {
@@ -487,6 +794,7 @@ body {
width: auto;
min-height: auto;
box-shadow: none;
transform: none;
}
.print-page {
@@ -500,4 +808,27 @@ body {
.title {
font-size: calc(26px * var(--month-font-scale));
}
body.month-pages .sheet-grid {
display: block;
}
body.month-pages .month-page {
break-after: page;
page-break-after: always;
}
body.month-pages .month-page:last-child {
break-after: auto;
page-break-after: auto;
}
body.month-pages .month {
width: 100%;
}
body.month-pages .grid {
break-inside: avoid;
page-break-inside: avoid;
}
}

View File

@@ -62,7 +62,9 @@ function tse_sp_event_export_get_column_definitions() {
'time' => __( 'Time', 'tonys-sportspress-enhancements' ),
'season' => __( 'Season', 'tonys-sportspress-enhancements' ),
'league' => __( 'League', 'tonys-sportspress-enhancements' ),
'title' => __( 'Title', 'tonys-sportspress-enhancements' ),
'team_name' => __( 'Team', 'tonys-sportspress-enhancements' ),
'separator' => __( 'Separator', 'tonys-sportspress-enhancements' ),
'opponent_name' => __( 'Opponent', 'tonys-sportspress-enhancements' ),
'location_flag' => __( 'Home/Away', 'tonys-sportspress-enhancements' ),
'field_name' => __( 'Field Name', 'tonys-sportspress-enhancements' ),
@@ -85,12 +87,33 @@ function tse_sp_event_export_get_column_definitions() {
function tse_sp_event_export_get_default_columns( $format ) {
$defaults = array(
'matchup' => array( 'date', 'time', 'season', 'league', 'away_team', 'home_team', 'field_name' ),
'team' => array( 'label', 'date', 'time', 'season', 'league', 'opponent_name', 'location_flag', 'field_name' ),
'team' => array( 'label', 'date', 'time', 'season', 'league', 'title', 'field_name' ),
);
return isset( $defaults[ $format ] ) ? $defaults[ $format ] : $defaults['matchup'];
}
/**
* Get supported event title formats.
*
* @return string[]
*/
function tse_sp_event_export_get_title_formats() {
return array( 'selected_first', 'matchup' );
}
/**
* Sanitize event title format.
*
* @param string $format Raw title format.
* @return string
*/
function tse_sp_event_export_sanitize_title_format( $format ) {
$format = sanitize_key( (string) $format );
return in_array( $format, tse_sp_event_export_get_title_formats(), true ) ? $format : 'selected_first';
}
/**
* Sanitize an export format.
*
@@ -173,24 +196,26 @@ function tse_sp_event_export_parse_id_list( $value ) {
* @return array
*/
function tse_sp_event_export_normalize_request_args( $source = null ) {
$source = is_array( $source ) ? $source : $_GET;
$format = isset( $source['format'] ) ? tse_sp_event_export_sanitize_format( wp_unslash( $source['format'] ) ) : 'matchup';
$team_ids = isset( $source['team_id'] ) ? tse_sp_event_export_parse_id_list( wp_unslash( $source['team_id'] ) ) : array();
$season_ids = isset( $source['season_id'] ) ? tse_sp_event_export_parse_id_list( wp_unslash( $source['season_id'] ) ) : array();
$league_ids = isset( $source['league_id'] ) ? tse_sp_event_export_parse_id_list( wp_unslash( $source['league_id'] ) ) : array();
$field_ids = isset( $source['field_id'] ) ? tse_sp_event_export_parse_id_list( wp_unslash( $source['field_id'] ) ) : array();
$source = is_array( $source ) ? $source : $_GET;
$format = isset( $source['format'] ) ? tse_sp_event_export_sanitize_format( wp_unslash( $source['format'] ) ) : 'matchup';
$team_ids = isset( $source['team_id'] ) ? tse_sp_event_export_parse_id_list( wp_unslash( $source['team_id'] ) ) : array();
$season_ids = isset( $source['season_id'] ) ? tse_sp_event_export_parse_id_list( wp_unslash( $source['season_id'] ) ) : array();
$league_ids = isset( $source['league_id'] ) ? tse_sp_event_export_parse_id_list( wp_unslash( $source['league_id'] ) ) : array();
$field_ids = isset( $source['field_id'] ) ? tse_sp_event_export_parse_id_list( wp_unslash( $source['field_id'] ) ) : array();
$title_format = isset( $source['title_format'] ) ? tse_sp_event_export_sanitize_title_format( wp_unslash( $source['title_format'] ) ) : 'selected_first';
return array(
'team_id' => isset( $team_ids[0] ) ? $team_ids[0] : 0,
'team_ids' => $team_ids,
'season_id' => isset( $season_ids[0] ) ? $season_ids[0] : 0,
'season_ids'=> $season_ids,
'league_id' => isset( $league_ids[0] ) ? $league_ids[0] : 0,
'league_ids'=> $league_ids,
'field_id' => isset( $field_ids[0] ) ? $field_ids[0] : 0,
'field_ids' => $field_ids,
'format' => $format,
'columns' => isset( $source['columns'] ) ? tse_sp_event_export_sanitize_columns( $format, wp_unslash( $source['columns'] ) ) : tse_sp_event_export_get_default_columns( $format ),
'team_id' => isset( $team_ids[0] ) ? $team_ids[0] : 0,
'team_ids' => $team_ids,
'season_id' => isset( $season_ids[0] ) ? $season_ids[0] : 0,
'season_ids' => $season_ids,
'league_id' => isset( $league_ids[0] ) ? $league_ids[0] : 0,
'league_ids' => $league_ids,
'field_id' => isset( $field_ids[0] ) ? $field_ids[0] : 0,
'field_ids' => $field_ids,
'format' => $format,
'title_format' => $title_format,
'columns' => isset( $source['columns'] ) ? tse_sp_event_export_sanitize_columns( $format, wp_unslash( $source['columns'] ) ) : tse_sp_event_export_get_default_columns( $format ),
);
}
@@ -212,9 +237,6 @@ function tse_sp_event_export_validate_filters( $filters ) {
wp_die( esc_html__( 'Team format requires a team filter.', 'tonys-sportspress-enhancements' ), '', array( 'response' => 400 ) );
}
if ( count( $team_ids ) > 1 ) {
wp_die( esc_html__( 'Team format does not support multiple teams.', 'tonys-sportspress-enhancements' ), '', array( 'response' => 400 ) );
}
}
/**
@@ -294,11 +316,10 @@ function tse_sp_event_export_query_posts( $filters ) {
* @return array
*/
function tse_sp_event_export_get_events( $filters ) {
$team_id = isset( $filters['team_id'] ) ? absint( $filters['team_id'] ) : 0;
$selected_ids = isset( $filters['team_ids'] ) && is_array( $filters['team_ids'] ) ? array_values( array_filter( array_map( 'absint', $filters['team_ids'] ) ) ) : array();
$query_posts = tse_sp_event_export_query_posts( $filters );
$events = array();
$team_name = $team_id > 0 ? get_the_title( $team_id ) : '';
$title_format = tse_sp_event_export_sanitize_title_format( isset( $filters['title_format'] ) ? $filters['title_format'] : 'selected_first' );
$query_posts = tse_sp_event_export_query_posts( $filters );
$events = array();
foreach ( $query_posts as $event ) {
$event_id = $event instanceof WP_Post ? (int) $event->ID : 0;
@@ -314,21 +335,22 @@ function tse_sp_event_export_get_events( $filters ) {
$home_id = isset( $teams[0] ) ? (int) $teams[0] : 0;
$away_id = isset( $teams[1] ) ? (int) $teams[1] : 0;
$venue = tse_sp_event_export_get_primary_field( $event_id );
$context = tse_sp_event_export_get_team_context( $home_id, $away_id, $selected_ids );
if ( $team_id > 0 ) {
$location_flag = $home_id === $team_id ? 'Home' : 'Away';
$opponent_id = $home_id === $team_id ? $away_id : $home_id;
} else {
$location_flag = '';
$opponent_id = 0;
}
$context_team_id = isset( $context['team_id'] ) ? (int) $context['team_id'] : 0;
$opponent_id = isset( $context['opponent_id'] ) ? (int) $context['opponent_id'] : 0;
$separator = isset( $context['separator'] ) ? (string) $context['separator'] : '';
$location_flag = isset( $context['location_flag'] ) ? (string) $context['location_flag'] : '';
$title = tse_sp_event_export_get_event_title_value( $home_id, $away_id, $selected_ids, $title_format );
$events[] = array(
'event_id' => $event_id,
'label' => '',
'date' => get_post_time( 'm/d/Y', false, $event_id, true ),
'time' => strtoupper( (string) ( function_exists( 'sp_get_time' ) ? sp_get_time( $event_id ) : get_post_time( get_option( 'time_format' ), false, $event_id, true ) ) ),
'team_name' => is_string( $team_name ) ? $team_name : '',
'title' => $title,
'team_name' => $context_team_id > 0 ? get_the_title( $context_team_id ) : '',
'separator' => $separator,
'opponent_name' => $opponent_id > 0 ? get_the_title( $opponent_id ) : '',
'location_flag' => $location_flag,
'home_team' => $home_id > 0 ? get_the_title( $home_id ) : '',
@@ -352,6 +374,100 @@ function tse_sp_event_export_get_events( $filters ) {
return $events;
}
/**
* Resolve a display title for a schedule event.
*
* @param int $home_id Home team ID.
* @param int $away_id Away team ID.
* @param int[] $selected_ids Selected team IDs.
* @param string $title_format Title format.
* @return string
*/
function tse_sp_event_export_get_event_title_value( $home_id, $away_id, $selected_ids, $title_format ) {
$home_id = absint( $home_id );
$away_id = absint( $away_id );
$selected_ids = array_values( array_filter( array_map( 'absint', (array) $selected_ids ) ) );
$title_format = tse_sp_event_export_sanitize_title_format( $title_format );
if ( 'matchup' === $title_format ) {
return tse_sp_event_export_join_title_parts( $away_id, 'at', $home_id );
}
$context = tse_sp_event_export_get_team_context( $home_id, $away_id, $selected_ids );
if ( empty( $context['team_id'] ) ) {
return '';
}
if ( 1 === count( $selected_ids ) ) {
$opponent_id = isset( $context['opponent_id'] ) ? (int) $context['opponent_id'] : 0;
return $opponent_id > 0 ? get_the_title( $opponent_id ) : '';
}
return tse_sp_event_export_join_title_parts(
isset( $context['team_id'] ) ? (int) $context['team_id'] : 0,
isset( $context['separator'] ) ? (string) $context['separator'] : '',
isset( $context['opponent_id'] ) ? (int) $context['opponent_id'] : 0
);
}
/**
* Join two team names with a title separator.
*
* @param int $first_id First team ID.
* @param string $separator Separator.
* @param int $second_id Second team ID.
* @return string
*/
function tse_sp_event_export_join_title_parts( $first_id, $separator, $second_id ) {
$first_name = $first_id > 0 ? get_the_title( $first_id ) : '';
$second_name = $second_id > 0 ? get_the_title( $second_id ) : '';
$parts = array_values( array_filter( array( $first_name, trim( $separator ), $second_name ), 'strlen' ) );
return implode( ' ', $parts );
}
/**
* Resolve the team-layout perspective for an event.
*
* The selected team is always first. If both selected teams are in the game,
* use the away team first and the home team second.
*
* @param int $home_id Home team ID.
* @param int $away_id Away team ID.
* @param int[] $selected_ids Selected team IDs.
* @return array
*/
function tse_sp_event_export_get_team_context( $home_id, $away_id, $selected_ids ) {
$home_id = absint( $home_id );
$away_id = absint( $away_id );
$selected_ids = array_values( array_filter( array_map( 'absint', (array) $selected_ids ) ) );
if ( $away_id > 0 && in_array( $away_id, $selected_ids, true ) ) {
return array(
'team_id' => $away_id,
'opponent_id' => $home_id,
'separator' => 'at',
'location_flag' => 'Away',
);
}
if ( $home_id > 0 && in_array( $home_id, $selected_ids, true ) ) {
return array(
'team_id' => $home_id,
'opponent_id' => $away_id,
'separator' => 'vs',
'location_flag' => 'Home',
);
}
return array(
'team_id' => 0,
'opponent_id' => 0,
'separator' => '',
'location_flag' => '',
);
}
/**
* Get event term names as a semicolon-delimited string.
*
@@ -476,25 +592,22 @@ function tse_sp_event_export_fold_ical_line( $line ) {
* @return string
*/
function tse_sp_event_export_get_ical_summary( $event, $filters ) {
$format = tse_sp_event_export_sanitize_format( isset( $filters['format'] ) ? $filters['format'] : 'matchup' );
$team_ids = isset( $filters['team_ids'] ) && is_array( $filters['team_ids'] ) ? array_values( array_filter( array_map( 'absint', $filters['team_ids'] ) ) ) : array();
$team_id = isset( $team_ids[0] ) ? $team_ids[0] : 0;
$format = tse_sp_event_export_sanitize_format( isset( $filters['format'] ) ? $filters['format'] : 'matchup' );
$team_ids = isset( $filters['team_ids'] ) && is_array( $filters['team_ids'] ) ? array_values( array_filter( array_map( 'absint', $filters['team_ids'] ) ) ) : array();
$title_format = tse_sp_event_export_sanitize_title_format( isset( $filters['title_format'] ) ? $filters['title_format'] : 'selected_first' );
$team_id = isset( $team_ids[0] ) ? $team_ids[0] : 0;
if ( 'team' === $format && $team_id > 0 ) {
$teams = array_values( array_unique( array_map( 'intval', get_post_meta( $event->ID, 'sp_team', false ) ) ) );
$home_id = isset( $teams[0] ) ? (int) $teams[0] : 0;
$away_id = isset( $teams[1] ) ? (int) $teams[1] : 0;
$context = tse_sp_event_export_get_team_context( $home_id, $away_id, $team_ids );
if ( in_array( $team_id, $teams, true ) ) {
$is_home = $home_id === $team_id;
$opponent_id = $is_home ? $away_id : $home_id;
$opponent = $opponent_id > 0 ? get_the_title( $opponent_id ) : __( 'TBD', 'tonys-sportspress-enhancements' );
$summary = sprintf(
/* translators: 1: preposition, 2: opponent name. */
__( '%1$s %2$s', 'tonys-sportspress-enhancements' ),
$is_home ? 'vs' : 'at',
$opponent
);
if ( ! empty( $context['team_id'] ) ) {
$summary = tse_sp_event_export_get_event_title_value( $home_id, $away_id, $team_ids, $title_format );
if ( '' === $summary ) {
$summary = $event->post_title;
}
return apply_filters( 'sportspress_ical_feed_summary', $summary, $event );
}
@@ -915,13 +1028,14 @@ function tse_sp_event_export_get_feed_url( $args = array(), $feed_type = 'csv' )
$filters = tse_sp_event_export_normalize_request_args( $args );
$feed = 'ics' === sanitize_key( $feed_type ) ? 'sp-ics' : 'sp-csv';
$query = array(
'feed' => $feed,
'format' => $filters['format'],
'team_id' => ! empty( $filters['team_ids'] ) ? implode( ',', $filters['team_ids'] ) : '',
'season_id' => ! empty( $filters['season_ids'] ) ? implode( ',', $filters['season_ids'] ) : '',
'league_id' => ! empty( $filters['league_ids'] ) ? implode( ',', $filters['league_ids'] ) : '',
'field_id' => ! empty( $filters['field_ids'] ) ? implode( ',', $filters['field_ids'] ) : '',
'columns' => implode( ',', $filters['columns'] ),
'feed' => $feed,
'format' => $filters['format'],
'team_id' => ! empty( $filters['team_ids'] ) ? implode( ',', $filters['team_ids'] ) : '',
'season_id' => ! empty( $filters['season_ids'] ) ? implode( ',', $filters['season_ids'] ) : '',
'league_id' => ! empty( $filters['league_ids'] ) ? implode( ',', $filters['league_ids'] ) : '',
'field_id' => ! empty( $filters['field_ids'] ) ? implode( ',', $filters['field_ids'] ) : '',
'title_format' => isset( $filters['title_format'] ) ? tse_sp_event_export_sanitize_title_format( $filters['title_format'] ) : 'selected_first',
'columns' => implode( ',', $filters['columns'] ),
);
return add_query_arg( array_filter( $query, 'strlen' ), home_url( '/' ) );

File diff suppressed because it is too large Load Diff

View File

@@ -86,11 +86,13 @@ function tse_sp_schedule_exporter_render_shortcode() {
$seasons = tse_sp_schedule_exporter_get_seasons();
$season_id = tse_sp_schedule_exporter_resolve_season_id( $seasons );
$teams = tse_sp_schedule_exporter_get_teams( $league_id, $season_id );
$team_id = tse_sp_schedule_exporter_resolve_team_id( $teams );
$fields = tse_sp_schedule_exporter_get_fields();
$field_id = tse_sp_schedule_exporter_resolve_field_id( $fields );
$export_type = tse_sp_schedule_exporter_resolve_export_type();
$subformat = tse_sp_schedule_exporter_resolve_subformat();
$team_ids = tse_sp_schedule_exporter_resolve_team_ids( $teams );
$fields = tse_sp_schedule_exporter_get_fields();
$field_id = tse_sp_schedule_exporter_resolve_field_id( $fields );
$export_type = tse_sp_schedule_exporter_resolve_export_type();
$subformat = tse_sp_schedule_exporter_resolve_subformat();
$title_format = tse_sp_schedule_exporter_resolve_title_format();
$black_white = tse_sp_schedule_exporter_resolve_black_white();
if ( empty( $teams ) ) {
return '<p>' . esc_html__( 'No SportsPress teams match the selected league and season.', 'tonys-sportspress-enhancements' ) . '</p>';
@@ -115,6 +117,15 @@ function tse_sp_schedule_exporter_render_shortcode() {
<p class="description"><?php esc_html_e( 'CSV builds a feed URL, iCal Link builds a subscription URL, and Printable opens the printable page.', 'tonys-sportspress-enhancements' ); ?></p>
</div>
<div data-title-format-wrap="1" style="margin-bottom:16px;">
<label for="tse-public-title-format"><strong><?php esc_html_e( 'Title Format', 'tonys-sportspress-enhancements' ); ?></strong></label><br />
<select id="tse-public-title-format" name="title_format">
<option value="selected_first" <?php selected( $title_format, 'selected_first' ); ?>><?php esc_html_e( 'Selected Teams First', 'tonys-sportspress-enhancements' ); ?></option>
<option value="matchup" <?php selected( $title_format, 'matchup' ); ?>><?php esc_html_e( 'Matchup', 'tonys-sportspress-enhancements' ); ?></option>
</select>
<p class="description"><?php esc_html_e( 'Selected Teams First shows only the opponent when one team is selected; Matchup always shows away at home.', 'tonys-sportspress-enhancements' ); ?></p>
</div>
<div data-subformat-wrap="1" style="margin-bottom:16px;">
<label for="tse-public-subformat"><strong><?php esc_html_e( 'CSV Layout', 'tonys-sportspress-enhancements' ); ?></strong></label><br />
<select id="tse-public-subformat" name="subformat">
@@ -124,7 +135,7 @@ function tse_sp_schedule_exporter_render_shortcode() {
</option>
<?php endforeach; ?>
</select>
<p class="description"><?php esc_html_e( 'Matchup is away vs home. Team is opponent-based and requires one specific team.', 'tonys-sportspress-enhancements' ); ?></p>
<p class="description"><?php esc_html_e( 'Matchup is away vs home. Team puts a selected team first with vs or at as the separator.', 'tonys-sportspress-enhancements' ); ?></p>
</div>
<div style="margin-bottom:16px;">
@@ -152,14 +163,14 @@ function tse_sp_schedule_exporter_render_shortcode() {
<div style="margin-bottom:16px;">
<label for="tse-public-team"><strong><?php esc_html_e( 'Team', 'tonys-sportspress-enhancements' ); ?></strong></label><br />
<select id="tse-public-team" name="team_id">
<option value="0"><?php esc_html_e( 'All teams', 'tonys-sportspress-enhancements' ); ?></option>
<select id="tse-public-team" name="team_id[]" multiple="multiple" size="<?php echo esc_attr( (string) min( 8, max( 3, count( $teams ) ) ) ); ?>">
<?php foreach ( $teams as $team ) : ?>
<option value="<?php echo esc_attr( (string) $team->ID ); ?>" <?php selected( $team_id, (int) $team->ID ); ?>>
<option value="<?php echo esc_attr( (string) $team->ID ); ?>" <?php selected( in_array( (int) $team->ID, $team_ids, true ) ); ?>>
<?php echo esc_html( $team->post_title ); ?>
</option>
<?php endforeach; ?>
</select>
<p class="description"><?php esc_html_e( 'Select one or more teams. Team layout keeps one selected team first for each game.', 'tonys-sportspress-enhancements' ); ?></p>
</div>
<div style="margin-bottom:16px;">
@@ -174,15 +185,36 @@ function tse_sp_schedule_exporter_render_shortcode() {
</select>
</div>
<div style="margin-bottom:16px;">
<label for="tse-public-month-pages" style="display:inline-flex;align-items:center;gap:6px;">
<input id="tse-public-month-pages" type="checkbox" name="month_pages" value="1" <?php checked( isset( $_GET['month_pages'] ) && '1' === (string) wp_unslash( $_GET['month_pages'] ) ); ?> />
<strong><?php esc_html_e( 'Print each month on its own page', 'tonys-sportspress-enhancements' ); ?></strong>
</label>
</div>
<div style="margin-bottom:16px;">
<label for="tse-public-black-white" style="display:inline-flex;align-items:center;gap:6px;">
<input id="tse-public-black-white" type="checkbox" name="black_white" value="1" <?php checked( $black_white ); ?> />
<strong><?php esc_html_e( 'High-legibility greyscale printable calendar', 'tonys-sportspress-enhancements' ); ?></strong>
</label>
</div>
</form>
<?php tse_sp_schedule_exporter_render_column_picker( 'matchup', 'public', $subformat ); ?>
<?php tse_sp_schedule_exporter_render_column_picker( 'team', 'public', $subformat ); ?>
<?php
$csv_url = tse_sp_event_export_get_feed_url( array( 'team_id' => $team_id, 'season_id' => $season_id, 'league_id' => $league_id, 'field_id' => $field_id, 'format' => $subformat ), 'csv' );
$ics_url = tse_sp_event_export_get_feed_url( array( 'team_id' => $team_id, 'season_id' => $season_id, 'league_id' => $league_id, 'field_id' => $field_id ), 'ics' );
$print_url = tse_sp_schedule_exporter_get_printable_url( $team_id, $season_id, 'letter', $league_id );
$feed_args = array(
'team_id' => $team_ids,
'season_id' => $season_id,
'league_id' => $league_id,
'field_id' => $field_id,
'title_format' => $title_format,
);
$csv_url = tse_sp_event_export_get_feed_url( array_merge( $feed_args, array( 'format' => $subformat ) ), 'csv' );
$ics_url = tse_sp_event_export_get_feed_url( $feed_args, 'ics' );
$print_url = tse_sp_schedule_exporter_get_printable_url( $team_ids, $season_id, 'letter', $league_id, false, $field_id, false, 'name', 'name', $title_format, $black_white );
$current_url = tse_sp_schedule_exporter_get_output_url( $export_type, $csv_url, $ics_url, $print_url );
?>
<div style="display:flex;align-items:center;gap:8px;max-width:100%;margin-top:16px;">
@@ -331,26 +363,34 @@ function tse_sp_schedule_exporter_get_teams( $league_id = 0, $season_id = 0 ) {
}
/**
* Resolve selected team ID.
* Resolve selected team IDs.
*
* @param WP_Post[] $teams Team posts.
* @return int
* @return int[]
*/
function tse_sp_schedule_exporter_resolve_team_id( $teams ) {
$requested = isset( $_GET['team_id'] ) ? absint( wp_unslash( $_GET['team_id'] ) ) : 0;
if ( $requested > 0 && 'sp_team' === get_post_type( $requested ) ) {
foreach ( $teams as $team ) {
if ( $team instanceof WP_Post && (int) $team->ID === $requested ) {
return $requested;
}
function tse_sp_schedule_exporter_resolve_team_ids( $teams ) {
$available = array();
foreach ( $teams as $team ) {
if ( $team instanceof WP_Post ) {
$available[] = (int) $team->ID;
}
}
if ( isset( $teams[0] ) && $teams[0] instanceof WP_Post ) {
return (int) $teams[0]->ID;
$requested = array();
if ( isset( $_GET['team_id'] ) ) {
$requested = tse_sp_event_export_parse_id_list( wp_unslash( $_GET['team_id'] ) );
}
return 0;
$selected = array_values( array_intersect( $requested, $available ) );
if ( ! empty( $selected ) ) {
return $selected;
}
if ( isset( $available[0] ) ) {
return array( (int) $available[0] );
}
return array();
}
/**
@@ -543,6 +583,38 @@ function tse_sp_schedule_exporter_resolve_subformat() {
return tse_sp_event_export_sanitize_format( $requested );
}
/**
* Resolve selected title format.
*
* @return string
*/
function tse_sp_schedule_exporter_resolve_title_format() {
$requested = isset( $_GET['title_format'] ) ? sanitize_key( wp_unslash( $_GET['title_format'] ) ) : 'selected_first';
return tse_sp_schedule_exporter_resolve_title_format_value( $requested );
}
/**
* Normalize a title format value.
*
* @param string $value Raw value.
* @return string
*/
function tse_sp_schedule_exporter_resolve_title_format_value( $value ) {
$value = sanitize_key( (string) $value );
return in_array( $value, tse_sp_event_export_get_title_formats(), true ) ? $value : 'selected_first';
}
/**
* Resolve whether printable URLs should use black-and-white styling.
*
* @return bool
*/
function tse_sp_schedule_exporter_resolve_black_white() {
return isset( $_GET['black_white'] ) && '1' === (string) wp_unslash( $_GET['black_white'] );
}
/**
* Get current output URL for the selected export type.
*
@@ -700,17 +772,29 @@ function tse_sp_schedule_exporter_get_primary_venue( $event_id ) {
* @param int $season_id Season ID.
* @param string $paper Paper size.
* @param int $league_id League ID.
* @param string $team_label_mode Team label mode.
* @param string $field_label_mode Field label mode.
* @param string $title_format Title format.
* @param bool $black_white Whether to use black-and-white printable styling.
* @return string
*/
function tse_sp_schedule_exporter_get_printable_url( $team_id, $season_id, $paper, $league_id = 0, $autoprint = false ) {
function tse_sp_schedule_exporter_get_printable_url( $team_id, $season_id, $paper, $league_id = 0, $autoprint = false, $field_id = 0, $month_pages = false, $team_label_mode = 'name', $field_label_mode = 'name', $title_format = 'selected_first', $black_white = false ) {
$team_ids = is_array( $team_id ) ? array_values( array_filter( array_map( 'absint', $team_id ) ) ) : array( absint( $team_id ) );
return add_query_arg(
array(
Tony_Sportspress_Printable_Calendars::QUERY_FLAG => '1',
'sp_team' => (string) absint( $team_id ),
'sp_team' => implode( ',', $team_ids ),
'sp_season' => $season_id > 0 ? (string) absint( $season_id ) : '',
'sp_league' => $league_id > 0 ? (string) absint( $league_id ) : '',
'sp_field' => $field_id > 0 ? (string) absint( $field_id ) : '',
'team_label' => sanitize_key( (string) $team_label_mode ),
'field_label' => sanitize_key( (string) $field_label_mode ),
'title_format' => tse_sp_schedule_exporter_resolve_title_format_value( $title_format ),
'paper' => $paper,
'autoprint' => $autoprint ? '1' : '',
'month_pages' => $month_pages ? '1' : '',
'black_white' => $black_white ? '1' : '',
),
home_url( '/' )
);
@@ -758,17 +842,28 @@ function tse_sp_schedule_exporter_render_link_sync_script( $echo = false ) {
var league = form.querySelector('[name="league_id"]');
var season = form.querySelector('[name="season_id"]');
var team = form.querySelector('[name="team_id"]');
var team = form.querySelector('[name="team_id[]"], [name="team_id"]');
var exportType = form.querySelector('[name="export_type"]');
var subformat = form.querySelector('[name="subformat"]');
var titleFormat = form.querySelector('[name="title_format"]');
var field = form.querySelector('[name="field_id"]');
var monthPages = form.querySelector('[name="month_pages"]');
var blackWhite = form.querySelector('[name="black_white"]');
var outputUrl = scope.querySelector('.tse-output-url');
var openButton = scope.querySelector('.tse-open-link');
var iosButton = scope.querySelector('.tse-ics-ios-link');
var androidButton = scope.querySelector('.tse-ics-android-link');
var outputNote = scope.querySelector('.tse-output-note');
var copyButton = scope.querySelector('.tse-copy-link');
var teamValue = team ? (team.value || '0') : '0';
var teamValues = team ? Array.prototype.slice.call(team.selectedOptions || []).map(function(option){
return option.value;
}).filter(function(value){
return value && value !== '0';
}) : [];
if (!teamValues.length && team && team.value && team.value !== '0') {
teamValues = [team.value];
}
var teamValue = teamValues.length ? teamValues.join(',') : '0';
var activeSubformat = subformat ? (subformat.value || 'matchup') : 'matchup';
var selectedExportType = exportType ? (exportType.value || 'csv') : 'csv';
@@ -792,6 +887,7 @@ function tse_sp_schedule_exporter_render_link_sync_script( $echo = false ) {
if (season) csvUrl.searchParams.set('season_id', season.value || '0');
if (team) csvUrl.searchParams.set('team_id', teamValue);
if (field) csvUrl.searchParams.set('field_id', field.value || '0');
if (titleFormat) csvUrl.searchParams.set('title_format', titleFormat.value || 'selected_first');
csvUrl.searchParams.set('format', activeSubformat);
var columns = Array.prototype.slice.call(scope.querySelectorAll('[data-columns-format="' + activeSubformat + '"]:checked')).map(function(input){
return input.value;
@@ -808,6 +904,7 @@ function tse_sp_schedule_exporter_render_link_sync_script( $echo = false ) {
if (season) icsUrl.searchParams.set('season_id', season.value || '0');
if (team) icsUrl.searchParams.set('team_id', teamValue);
if (field) icsUrl.searchParams.set('field_id', field.value || '0');
if (titleFormat) icsUrl.searchParams.set('title_format', titleFormat.value || 'selected_first');
icsUrl.searchParams.delete('format');
icsUrl.searchParams.delete('columns');
}
@@ -816,6 +913,18 @@ function tse_sp_schedule_exporter_render_link_sync_script( $echo = false ) {
if (league) printUrl.searchParams.set('sp_league', league.value || '0');
if (season) printUrl.searchParams.set('sp_season', season.value || '0');
if (team) printUrl.searchParams.set('sp_team', teamValue);
if (field) printUrl.searchParams.set('sp_field', field.value || '0');
if (titleFormat) printUrl.searchParams.set('title_format', titleFormat.value || 'selected_first');
if (monthPages && monthPages.checked) {
printUrl.searchParams.set('month_pages', '1');
} else {
printUrl.searchParams.delete('month_pages');
}
if (blackWhite && blackWhite.checked) {
printUrl.searchParams.set('black_white', '1');
} else {
printUrl.searchParams.delete('black_white');
}
printUrl.searchParams.set('paper', 'letter');
}
@@ -833,11 +942,11 @@ function tse_sp_schedule_exporter_render_link_sync_script( $echo = false ) {
resolvedUrl = printUrl.toString();
if (teamValue === '0') {
disabled = true;
note = 'Printable requires a specific team. All teams is not supported.';
note = 'Printable requires at least one selected team.';
}
} else if (selectedExportType === 'csv' && activeSubformat === 'team' && teamValue === '0') {
disabled = true;
note = 'CSV team layout requires a specific team. All teams is not supported.';
note = 'CSV team layout requires at least one selected team.';
}
if (outputUrl) {
@@ -885,10 +994,10 @@ function tse_sp_schedule_exporter_render_link_sync_script( $echo = false ) {
syncLinks(scope);
scope.querySelectorAll('.tse-schedule-exporter-form select').forEach(function(select){
select.addEventListener('change', function(){
if (select.dataset.autoSubmit === '1') {
select.form.submit();
scope.querySelectorAll('.tse-schedule-exporter-form select, .tse-schedule-exporter-form input[type="checkbox"]').forEach(function(input){
input.addEventListener('change', function(){
if (input.dataset.autoSubmit === '1') {
input.form.submit();
return;
}

View File

@@ -0,0 +1,341 @@
<?php
/**
* Tests for schedule exporter and printable schedule helpers.
*
* @package Tonys_Sportspress_Enhancements
*/
/**
* Schedule exporter tests.
*/
class Test_SP_Schedule_Exporter extends WP_UnitTestCase {
/**
* Original request globals.
*
* @var array
*/
private $original_get = array();
/**
* Set up shared test fixtures.
*/
public function set_up() {
parent::set_up();
$this->original_get = $_GET;
foreach ( array( 'sp_venue', 'sp_league', 'sp_season' ) as $taxonomy ) {
if ( ! taxonomy_exists( $taxonomy ) ) {
register_taxonomy( $taxonomy, 'sp_event' );
}
}
}
/**
* Restore request globals.
*/
public function tear_down() {
$_GET = $this->original_get;
parent::tear_down();
}
/**
* Create a SportsPress team post.
*
* @param string $name Team name.
* @return int
*/
private function create_team( $name ) {
return self::factory()->post->create(
array(
'post_type' => 'sp_team',
'post_status' => 'publish',
'post_title' => $name,
)
);
}
/**
* Create a SportsPress event with ordered teams.
*
* @param int $home_id Home team ID.
* @param int $away_id Away team ID.
* @param int[] $venue_ids Venue IDs.
* @return int
*/
private function create_event( $home_id, $away_id, $venue_ids = array() ) {
$event_id = self::factory()->post->create(
array(
'post_type' => 'sp_event',
'post_status' => 'publish',
'post_title' => 'Game',
'post_date' => '2026-05-20 18:00:00',
'post_date_gmt' => '2026-05-20 23:00:00',
)
);
add_post_meta( $event_id, 'sp_team', (string) $home_id );
add_post_meta( $event_id, 'sp_team', (string) $away_id );
if ( ! empty( $venue_ids ) ) {
wp_set_object_terms( $event_id, $venue_ids, 'sp_venue' );
}
return $event_id;
}
/**
* Multiple selected team IDs should be retained in request order.
*/
public function test_resolve_team_ids_accepts_multiple_request_values() {
$team_one = $this->create_team( 'Blue' );
$team_two = $this->create_team( 'Red' );
$teams = array( get_post( $team_one ), get_post( $team_two ) );
$_GET['team_id'] = array( (string) $team_one, (string) $team_two );
$this->assertSame( array( $team_one, $team_two ), tse_sp_schedule_exporter_resolve_team_ids( $teams ) );
}
/**
* Printable URLs should carry multiple team IDs and the selected field.
*/
public function test_printable_url_accepts_multiple_teams_and_field() {
$url = tse_sp_schedule_exporter_get_printable_url( array( 12, 34 ), 56, 'letter', 78, false, 90, true );
$query = array();
wp_parse_str( (string) wp_parse_url( $url, PHP_URL_QUERY ), $query );
$this->assertSame( '12,34', $query['sp_team'] );
$this->assertSame( '90', $query['sp_field'] );
$this->assertSame( 'name', $query['team_label'] );
$this->assertSame( 'name', $query['field_label'] );
$this->assertSame( 'selected_first', $query['title_format'] );
$this->assertSame( '1', $query['month_pages'] );
}
/**
* Printable URLs should carry selected team and field label modes.
*/
public function test_printable_url_accepts_label_modes() {
$url = tse_sp_schedule_exporter_get_printable_url( 12, 56, 'letter', 78, false, 90, false, 'shortname', 'abbreviation' );
$query = array();
wp_parse_str( (string) wp_parse_url( $url, PHP_URL_QUERY ), $query );
$this->assertSame( 'shortname', $query['team_label'] );
$this->assertSame( 'abbreviation', $query['field_label'] );
}
/**
* Printable URLs should carry black-and-white mode when requested.
*/
public function test_printable_url_accepts_black_white_mode() {
$url = tse_sp_schedule_exporter_get_printable_url( 12, 56, 'letter', 78, false, 90, false, 'name', 'name', 'selected_first', true );
$query = array();
wp_parse_str( (string) wp_parse_url( $url, PHP_URL_QUERY ), $query );
$this->assertSame( '1', $query['black_white'] );
}
/**
* Single-team printable entries should keep the existing opponent perspective.
*/
public function test_printable_single_team_entries_keep_opponent_perspective() {
$home_id = $this->create_team( 'Home Team' );
$away_id = $this->create_team( 'Away Team' );
$venue_id = self::factory()->term->create( array( 'taxonomy' => 'sp_venue', 'name' => 'North Field' ) );
$this->create_event( $home_id, $away_id, array( $venue_id ) );
$printable = Tony_Sportspress_Printable_Calendars::instance();
$method = new ReflectionMethod( $printable, 'get_schedule_entries' );
$method->setAccessible( true );
$entries = $method->invoke( $printable, $home_id, 0, 0, array() );
$this->assertCount( 1, $entries );
$this->assertFalse( $entries[0]['is_matchup'] );
$this->assertTrue( $entries[0]['is_home'] );
$this->assertSame( 'Away Team', $entries[0]['opponent_name'] );
}
/**
* Printable entries should honor requested team and field labels.
*/
public function test_printable_entries_honor_label_modes() {
$home_id = $this->create_team( 'Home Team' );
$away_id = $this->create_team( 'Away Team' );
$venue_id = self::factory()->term->create( array( 'taxonomy' => 'sp_venue', 'name' => 'North Field' ) );
update_post_meta( $away_id, 'sp_abbreviation', 'AWY' );
update_term_meta( $venue_id, 'tse_abbreviation', 'NF' );
$this->create_event( $home_id, $away_id, array( $venue_id ) );
$printable = Tony_Sportspress_Printable_Calendars::instance();
$method = new ReflectionMethod( $printable, 'get_schedule_entries' );
$method->setAccessible( true );
$entries = $method->invoke( $printable, $home_id, 0, 0, array(), 'abbreviation', 'abbreviation' );
$this->assertCount( 1, $entries );
$this->assertSame( 'AWY', $entries[0]['opponent_name'] );
$this->assertSame( 'NF', $entries[0]['venue_label'] );
}
/**
* Team CSV layout should support multiple selected teams with away-first context.
*/
public function test_team_export_multiple_selected_teams_uses_away_team_first() {
$home_id = $this->create_team( 'Home Team' );
$away_id = $this->create_team( 'Away Team' );
$this->create_event( $home_id, $away_id );
$events = tse_sp_event_export_get_events(
array(
'team_ids' => array( $home_id, $away_id ),
)
);
$this->assertCount( 1, $events );
$this->assertSame( 'Away Team', $events[0]['team_name'] );
$this->assertSame( 'Away Team at Home Team', $events[0]['title'] );
$this->assertSame( 'at', $events[0]['separator'] );
$this->assertSame( 'Home Team', $events[0]['opponent_name'] );
$this->assertSame( 'Away', $events[0]['location_flag'] );
}
/**
* Team CSV layout should use vs when the selected team is home.
*/
public function test_team_export_home_selected_team_uses_vs_separator() {
$home_id = $this->create_team( 'Home Team' );
$away_id = $this->create_team( 'Away Team' );
$this->create_event( $home_id, $away_id );
$events = tse_sp_event_export_get_events(
array(
'team_ids' => array( $home_id ),
)
);
$this->assertCount( 1, $events );
$this->assertSame( 'Home Team', $events[0]['team_name'] );
$this->assertSame( 'Away Team', $events[0]['title'] );
$this->assertSame( 'vs', $events[0]['separator'] );
$this->assertSame( 'Away Team', $events[0]['opponent_name'] );
$this->assertSame( 'Home', $events[0]['location_flag'] );
}
/**
* Team CSV layout should use at when the selected team is away.
*/
public function test_team_export_away_selected_team_uses_at_separator() {
$home_id = $this->create_team( 'Home Team' );
$away_id = $this->create_team( 'Away Team' );
$this->create_event( $home_id, $away_id );
$events = tse_sp_event_export_get_events(
array(
'team_ids' => array( $away_id ),
)
);
$this->assertCount( 1, $events );
$this->assertSame( 'Away Team', $events[0]['team_name'] );
$this->assertSame( 'Home Team', $events[0]['title'] );
$this->assertSame( 'at', $events[0]['separator'] );
$this->assertSame( 'Home Team', $events[0]['opponent_name'] );
$this->assertSame( 'Away', $events[0]['location_flag'] );
}
/**
* Matchup title format should ignore selected-team perspective.
*/
public function test_team_export_matchup_title_format_uses_away_at_home() {
$home_id = $this->create_team( 'Home Team' );
$away_id = $this->create_team( 'Away Team' );
$this->create_event( $home_id, $away_id );
$events = tse_sp_event_export_get_events(
array(
'team_ids' => array( $home_id ),
'title_format' => 'matchup',
)
);
$this->assertCount( 1, $events );
$this->assertSame( 'Away Team at Home Team', $events[0]['title'] );
}
/**
* Multi-team printable entries should use selected-team-first titles by default.
*/
public function test_printable_multi_team_entries_use_selected_first_titles() {
$home_id = $this->create_team( 'Home Team' );
$away_id = $this->create_team( 'Away Team' );
$venue_id = self::factory()->term->create( array( 'taxonomy' => 'sp_venue', 'name' => 'North Field' ) );
$this->create_event( $home_id, $away_id, array( $venue_id ) );
$printable = Tony_Sportspress_Printable_Calendars::instance();
$method = new ReflectionMethod( $printable, 'get_schedule_entries' );
$method->setAccessible( true );
$entries = $method->invoke( $printable, array( $home_id, $away_id ), 0, 0, array() );
$this->assertCount( 1, $entries );
$this->assertFalse( $entries[0]['is_matchup'] );
$this->assertSame( 'Away Team', $entries[0]['title_team_name'] );
$this->assertSame( 'at', $entries[0]['title_separator'] );
$this->assertSame( 'Home Team', $entries[0]['title_opponent_name'] );
}
/**
* Exactly one selected field should suppress per-event venue labels.
*/
public function test_render_month_grid_can_suppress_event_venue_label() {
$printable = Tony_Sportspress_Printable_Calendars::instance();
$method = new ReflectionMethod( $printable, 'render_month_grid' );
$method->setAccessible( true );
$entries = array(
'2026-05-20' => array(
array(
'day_key' => '2026-05-20',
'month_key' => '2026-05',
'timestamp' => strtotime( '2026-05-20 18:00:00' ),
'is_matchup' => true,
'away_team_name' => 'Away',
'home_team_name' => 'Home',
'event_time' => '6:00 PM',
'venue_label' => 'North',
'venue_name' => 'North Field',
'venue_key' => 'v:1',
),
),
);
ob_start();
$method->invoke(
$printable,
'2026-05',
$entries,
array( 'v:1' => array( 'name' => 'North Field', 'color' => '#1D4ED8' ) ),
array(
'primary' => '#1D4ED8',
'secondary' => '#DC2626',
'accent' => '#1D4ED8',
'ink' => '#111827',
'muted_ink' => '#334155',
),
true,
true
);
$output = ob_get_clean();
$this->assertStringContainsString( 'Away', $output );
$this->assertStringContainsString( 'Home', $output );
$this->assertStringNotContainsString( 'class="event-venue"', $output );
}
}