diff --git a/assets/schedule-exporter-block.js b/assets/schedule-exporter-block.js new file mode 100644 index 0000000..246a26d --- /dev/null +++ b/assets/schedule-exporter-block.js @@ -0,0 +1,27 @@ +( function ( blocks, blockEditor, element, i18n ) { + var el = element.createElement; + var useBlockProps = blockEditor.useBlockProps; + var __ = i18n.__; + + blocks.registerBlockType( 'tse/schedule-exporter', { + edit: function () { + var blockProps = useBlockProps( { + className: 'tse-schedule-exporter-block-placeholder', + } ); + + return el( + 'div', + blockProps, + el( 'strong', null, __( 'Schedule Exporter', 'tonys-sportspress-enhancements' ) ), + el( + 'p', + null, + __( 'This block renders the public schedule exporter on the frontend.', 'tonys-sportspress-enhancements' ) + ) + ); + }, + save: function () { + return null; + }, + } ); +} )( window.wp.blocks, window.wp.blockEditor, window.wp.element, window.wp.i18n ); diff --git a/includes/sp-printable-calendars.php b/includes/sp-printable-calendars.php index cbcb676..db4eecb 100644 --- a/includes/sp-printable-calendars.php +++ b/includes/sp-printable-calendars.php @@ -55,7 +55,7 @@ if ( ! class_exists( 'Tony_Sportspress_Printable_Calendars' ) ) { * * @var string[] */ - private $allowed_paper_sizes = array( 'letter', 'ledger' ); + private $allowed_paper_sizes = array( 'letter', 'ledger', '11x17' ); /** * Allowed refresh intervals. @@ -145,7 +145,9 @@ if ( ! class_exists( 'Tony_Sportspress_Printable_Calendars' ) ) { $vars[] = self::QUERY_FLAG; $vars[] = 'sp_team'; $vars[] = 'sp_season'; + $vars[] = 'sp_league'; $vars[] = 'paper'; + $vars[] = 'autoprint'; return $vars; } @@ -160,7 +162,7 @@ if ( ! class_exists( 'Tony_Sportspress_Printable_Calendars' ) ) { } $season_id = absint( (string) get_option( 'sportspress_season', '0' ) ); - $link = $this->build_url( $team_id, $season_id, 'letter' ); + $link = $this->build_url( $team_id, $season_id, '11x17' ); echo '
'; + echo '

' . esc_html__( 'Schedule Exporter', 'tonys-sportspress-enhancements' ) . '

'; + echo '

' . esc_html__( 'Choose a team and season, then export the schedule as CSV or open the printable schedule in a PDF-ready print view.', 'tonys-sportspress-enhancements' ) . '

'; + + echo '
'; + echo ''; + echo ''; + + echo ''; + echo ''; + echo ''; + echo ''; + + echo ''; + echo ''; + echo ''; + echo ''; + + echo ''; + echo ''; + echo ''; + echo ''; + + echo ''; + echo ''; + echo ''; + echo ''; + + echo ''; + echo '
'; + + if ( empty( $teams ) ) { + echo '

' . esc_html__( 'No SportsPress teams match the selected league and season.', 'tonys-sportspress-enhancements' ) . '

'; + echo '
'; + return; + } + + echo '
'; + echo '

' . esc_html__( 'Exports', 'tonys-sportspress-enhancements' ) . '

'; + + echo ''; + foreach ( array( + array( + 'format' => 'matchup', + 'label' => __( 'Download Matchup CSV', 'tonys-sportspress-enhancements' ), + 'description' => __( 'Date, time, away team, home team, and field name.', 'tonys-sportspress-enhancements' ), + ), + array( + 'format' => 'team', + 'label' => __( 'Download Team CSV', 'tonys-sportspress-enhancements' ), + 'description' => __( 'TeamSnap-compatible layout with game label, opponent, home/away flag, and venue.', 'tonys-sportspress-enhancements' ), + ), + ) as $export_option ) { + $url = wp_nonce_url( + add_query_arg( + array( + 'action' => 'tse_schedule_export', + 'league_id' => $league_id, + 'team_id' => $team_id, + 'season_id' => $season_id, + 'format' => $export_option['format'], + ), + admin_url( 'admin-post.php' ) + ), + 'tse_schedule_export' + ); + + echo ''; + echo ''; + echo ''; + echo ''; + } + + $pdf_url = tse_sp_schedule_exporter_get_pdf_url( $team_id, $season_id, $paper, $league_id ); + echo ''; + echo ''; + echo ''; + echo ''; + echo '
' . esc_html( $export_option['label'] ) . '' . esc_html( $export_option['description'] ) . '
' . esc_html__( 'Open Printable PDF View', 'tonys-sportspress-enhancements' ) . '' . esc_html__( 'Opens the printable schedule and launches the browser print dialog so you can save a PDF.', 'tonys-sportspress-enhancements' ) . '
'; + tse_sp_schedule_exporter_render_link_sync_script( true ); + echo '
'; + echo ''; +} + +/** + * Render the public shortcode. + * + * @return string + */ +function tse_sp_schedule_exporter_render_shortcode() { + $leagues = tse_sp_schedule_exporter_get_leagues(); + $league_id = tse_sp_schedule_exporter_resolve_league_id( $leagues ); + $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 ); + $paper = tse_sp_schedule_exporter_resolve_paper_size(); + + if ( empty( $teams ) ) { + return '

' . esc_html__( 'No SportsPress teams match the selected league and season.', 'tonys-sportspress-enhancements' ) . '

'; + } + + ob_start(); + ?> +
+

+

+ +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+ + + + + + + + + + + + + + + + +
+ +
+ +
+ +
+
+ ' . esc_html__( 'Schedule Exporter', 'tonys-sportspress-enhancements' ) . '

' . esc_html__( 'The schedule exporter renders on the frontend.', 'tonys-sportspress-enhancements' ) . '

'; + } + + return tse_sp_schedule_exporter_render_shortcode(); +} + +/** + * Get teams for the exporter. + * + * @return WP_Post[] + */ +function tse_sp_schedule_exporter_get_leagues() { + $leagues = get_terms( + array( + 'taxonomy' => 'sp_league', + 'hide_empty' => false, + 'orderby' => 'name', + 'order' => 'ASC', + ) + ); + + if ( is_wp_error( $leagues ) || ! is_array( $leagues ) ) { + return array(); + } + + return $leagues; +} + +/** + * Get seasons for the exporter. + * + * @return WP_Term[] + */ +function tse_sp_schedule_exporter_get_seasons() { + $seasons = get_terms( + array( + 'taxonomy' => 'sp_season', + 'hide_empty' => false, + 'orderby' => 'name', + 'order' => 'ASC', + ) + ); + + if ( is_wp_error( $seasons ) || ! is_array( $seasons ) ) { + return array(); + } + + return $seasons; +} + +/** + * Get teams for the exporter. + * + * @param int $league_id League ID. + * @param int $season_id Season ID. + * @return WP_Post[] + */ +function tse_sp_schedule_exporter_get_teams( $league_id = 0, $season_id = 0 ) { + $tax_query = array(); + + if ( $league_id > 0 ) { + $tax_query[] = array( + 'taxonomy' => 'sp_league', + 'field' => 'term_id', + 'terms' => array( $league_id ), + ); + } + + if ( $season_id > 0 ) { + $tax_query[] = array( + 'taxonomy' => 'sp_season', + 'field' => 'term_id', + 'terms' => array( $season_id ), + ); + } + + $args = array( + 'post_type' => 'sp_team', + 'post_status' => 'publish', + 'posts_per_page' => -1, + 'orderby' => 'title', + 'order' => 'ASC', + 'no_found_rows' => true, + 'update_post_meta_cache' => false, + 'update_post_term_cache' => false, + 'cache_results' => false, + ); + + if ( ! empty( $tax_query ) ) { + if ( count( $tax_query ) > 1 ) { + $tax_query['relation'] = 'AND'; + } + + $args['tax_query'] = $tax_query; + } + + $teams = get_posts( $args ); + + return is_array( $teams ) ? $teams : array(); +} + +/** + * Resolve selected team ID. + * + * @param WP_Post[] $teams Team posts. + * @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; + } + } + } + + if ( isset( $teams[0] ) && $teams[0] instanceof WP_Post ) { + return (int) $teams[0]->ID; + } + + return 0; +} + +/** + * Resolve selected league ID. + * + * @param WP_Term[] $leagues League terms. + * @return int + */ +function tse_sp_schedule_exporter_resolve_league_id( $leagues ) { + $requested = isset( $_GET['league_id'] ) ? absint( wp_unslash( $_GET['league_id'] ) ) : 0; + if ( $requested > 0 ) { + return $requested; + } + + foreach ( $leagues as $league ) { + if ( ! $league instanceof WP_Term ) { + continue; + } + + $slug = isset( $league->slug ) ? strtolower( (string) $league->slug ) : ''; + $name = isset( $league->name ) ? strtolower( trim( (string) $league->name ) ) : ''; + + if ( 'cmba' === $slug || 'cmba' === $name ) { + return (int) $league->term_id; + } + } + + if ( isset( $leagues[0] ) && $leagues[0] instanceof WP_Term ) { + return (int) $leagues[0]->term_id; + } + + return 0; +} + +/** + * Resolve selected season ID. + * + * @param WP_Term[] $seasons Season terms. + * @return int + */ +function tse_sp_schedule_exporter_resolve_season_id( $seasons ) { + $requested = isset( $_GET['season_id'] ) ? absint( wp_unslash( $_GET['season_id'] ) ) : 0; + if ( $requested > 0 ) { + return $requested; + } + + $current = absint( (string) get_option( 'sportspress_season', '0' ) ); + if ( $current > 0 ) { + return $current; + } + + if ( isset( $seasons[0] ) && is_object( $seasons[0] ) && isset( $seasons[0]->term_id ) ) { + return (int) $seasons[0]->term_id; + } + + return 0; +} + +/** + * Get supported paper sizes. + * + * @return array + */ +function tse_sp_schedule_exporter_get_paper_sizes() { + return array( + 'letter' => __( 'Letter', 'tonys-sportspress-enhancements' ), + 'ledger' => __( '11x17 / Ledger', 'tonys-sportspress-enhancements' ), + ); +} + +/** + * Resolve selected paper size. + * + * @return string + */ +function tse_sp_schedule_exporter_resolve_paper_size() { + $paper = isset( $_GET['paper'] ) ? sanitize_key( wp_unslash( $_GET['paper'] ) ) : 'letter'; + + return array_key_exists( $paper, tse_sp_schedule_exporter_get_paper_sizes() ) ? $paper : 'letter'; +} + +/** + * Collect team schedule events for export. + * + * @param int $team_id Team ID. + * @param int $season_id Optional season ID. + * @param int $league_id Optional league ID. + * @return array + */ +function tse_sp_schedule_exporter_get_events( $team_id, $season_id = 0, $league_id = 0 ) { + $team_id = absint( $team_id ); + if ( $team_id <= 0 || 'sp_team' !== get_post_type( $team_id ) ) { + return array(); + } + + $args = array( + 'post_type' => 'sp_event', + 'post_status' => array( 'publish', 'future' ), + 'posts_per_page' => -1, + 'orderby' => 'date', + 'order' => 'ASC', + 'no_found_rows' => true, + 'meta_query' => array( + array( + 'key' => 'sp_team', + 'value' => array( (string) $team_id ), + 'compare' => 'IN', + ), + ), + ); + + $tax_query = array(); + + if ( $season_id > 0 ) { + $tax_query[] = array( + 'taxonomy' => 'sp_season', + 'field' => 'term_id', + 'terms' => array( $season_id ), + ); + } + + if ( $league_id > 0 ) { + $tax_query[] = array( + 'taxonomy' => 'sp_league', + 'field' => 'term_id', + 'terms' => array( $league_id ), + ); + } + + if ( ! empty( $tax_query ) ) { + if ( count( $tax_query ) > 1 ) { + $tax_query['relation'] = 'AND'; + } + + $args['tax_query'] = $tax_query; + } + + $query = new WP_Query( $args ); + $events = array(); + $team_name = get_the_title( $team_id ); + + foreach ( $query->posts as $event ) { + $event_id = $event instanceof WP_Post ? (int) $event->ID : 0; + if ( $event_id <= 0 ) { + continue; + } + + $teams = array_values( array_unique( array_map( 'intval', get_post_meta( $event_id, 'sp_team', false ) ) ) ); + if ( ! in_array( $team_id, $teams, true ) ) { + continue; + } + + $home_id = isset( $teams[0] ) ? (int) $teams[0] : 0; + $away_id = isset( $teams[1] ) ? (int) $teams[1] : 0; + + $location_flag = $home_id === $team_id ? 'Home' : 'Away'; + $opponent_id = $home_id === $team_id ? $away_id : $home_id; + $venue = tse_sp_schedule_exporter_get_primary_venue( $event_id ); + + $events[] = array( + 'label' => '', + 'event_id' => $event_id, + '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 : '', + 'opponent_name' => $opponent_id > 0 ? get_the_title( $opponent_id ) : __( 'TBD', 'tonys-sportspress-enhancements' ), + 'location_flag' => $location_flag, + 'home_team' => $home_id > 0 ? get_the_title( $home_id ) : '', + 'away_team' => $away_id > 0 ? get_the_title( $away_id ) : '', + 'venue_name' => isset( $venue['name'] ) ? $venue['name'] : '', + 'venue_abbreviation' => isset( $venue['abbreviation'] ) ? $venue['abbreviation'] : '', + 'venue_short_name' => isset( $venue['short_name'] ) ? $venue['short_name'] : '', + ); + } + + foreach ( $events as $index => $event ) { + $events[ $index ]['label'] = sprintf( 'G#%02d', $index + 1 ); + } + + wp_reset_postdata(); + + return $events; +} + +/** + * Get the primary venue details for an event. + * + * @param int $event_id Event ID. + * @return array + */ +function tse_sp_schedule_exporter_get_primary_venue( $event_id ) { + $venues = get_the_terms( $event_id, 'sp_venue' ); + + if ( ! is_array( $venues ) || ! isset( $venues[0] ) || ! $venues[0] instanceof WP_Term ) { + return array( + 'name' => '', + 'abbreviation' => '', + 'short_name' => '', + ); + } + + $venue = $venues[0]; + + return array( + 'name' => isset( $venue->name ) ? (string) $venue->name : '', + 'abbreviation' => trim( (string) get_term_meta( $venue->term_id, 'tse_abbreviation', true ) ), + 'short_name' => trim( (string) get_term_meta( $venue->term_id, 'tse_short_name', true ) ), + ); +} + +/** + * Build an export download URL. + * + * @param int $team_id Team ID. + * @param int $season_id Season ID. + * @param string $format Export format. + * @param int $league_id League ID. + * @return string + */ +function tse_sp_schedule_exporter_get_export_url( $team_id, $season_id, $format, $league_id = 0 ) { + return wp_nonce_url( + add_query_arg( + array( + 'action' => 'tse_schedule_export', + 'league_id' => absint( $league_id ), + 'team_id' => absint( $team_id ), + 'season_id' => absint( $season_id ), + 'format' => sanitize_key( $format ), + ), + admin_url( 'admin-post.php' ) + ), + 'tse_schedule_export' + ); +} + +/** + * Build the printable PDF URL. + * + * @param int $team_id Team ID. + * @param int $season_id Season ID. + * @param string $paper Paper size. + * @param int $league_id League ID. + * @return string + */ +function tse_sp_schedule_exporter_get_pdf_url( $team_id, $season_id, $paper, $league_id = 0 ) { + return add_query_arg( + array( + Tony_Sportspress_Printable_Calendars::QUERY_FLAG => '1', + 'sp_team' => (string) absint( $team_id ), + 'sp_season' => $season_id > 0 ? (string) absint( $season_id ) : '', + 'sp_league' => $league_id > 0 ? (string) absint( $league_id ) : '', + 'paper' => $paper, + 'autoprint' => '1', + ), + home_url( '/' ) + ); +} + +/** + * Render a small script that keeps export links in sync with current selections. + * + * @param bool $echo Whether to echo immediately. + * @return string + */ +function tse_sp_schedule_exporter_render_link_sync_script( $echo = false ) { + $script = << +(function(){ + function syncLinks(scope){ + var form = scope.querySelector('.tse-schedule-exporter-form'); + if (!form) { + return; + } + + var league = form.querySelector('[name="league_id"]'); + var season = form.querySelector('[name="season_id"]'); + var team = form.querySelector('[name="team_id"]'); + var paper = form.querySelector('[name="paper"]'); + + scope.querySelectorAll('.tse-export-link').forEach(function(link){ + var url = new URL(link.href, window.location.origin); + if (league) url.searchParams.set('league_id', league.value || '0'); + if (season) url.searchParams.set('season_id', season.value || '0'); + if (team) url.searchParams.set('team_id', team.value || '0'); + if (link.dataset.format) url.searchParams.set('format', link.dataset.format); + link.href = url.toString(); + }); + + scope.querySelectorAll('.tse-pdf-link').forEach(function(link){ + var url = new URL(link.href, window.location.origin); + if (league) url.searchParams.set('sp_league', league.value || '0'); + if (season) url.searchParams.set('sp_season', season.value || '0'); + if (team) url.searchParams.set('sp_team', team.value || '0'); + if (paper) url.searchParams.set('paper', paper.value || 'letter'); + link.href = url.toString(); + }); + } + + document.querySelectorAll('.tse-schedule-exporter, .wrap').forEach(function(scope){ + if (!scope.querySelector('.tse-schedule-exporter-form')) { + return; + } + + syncLinks(scope); + + scope.querySelectorAll('.tse-schedule-exporter-form select').forEach(function(select){ + select.addEventListener('change', function(){ + if (select.dataset.autoSubmit === '1') { + select.form.submit(); + return; + } + + syncLinks(scope); + }); + }); + }); +})(); + +HTML; + + if ( $echo ) { + echo $script; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + return ''; + } + + return $script; +} diff --git a/tonys-sportspress-enhancements.php b/tonys-sportspress-enhancements.php index b91a321..86705c7 100644 --- a/tonys-sportspress-enhancements.php +++ b/tonys-sportspress-enhancements.php @@ -37,4 +37,5 @@ require_once plugin_dir_path(__FILE__) . 'includes/sp-event-admin-week-filter.ph require_once plugin_dir_path(__FILE__) . 'includes/sp-event-quick-edit-officials.php'; require_once plugin_dir_path(__FILE__) . 'includes/sp-event-team-ordering.php'; require_once plugin_dir_path(__FILE__) . 'includes/sp-printable-calendars.php'; +require_once plugin_dir_path(__FILE__) . 'includes/sp-schedule-exporter.php'; require_once plugin_dir_path(__FILE__) . 'includes/sp-venue-meta.php';