Improve SportsPress Open Graph images

This commit is contained in:
2026-04-27 12:43:26 -05:00
parent 1df307dfbe
commit 2afe98bc99
6 changed files with 1868 additions and 344 deletions

View File

@@ -0,0 +1,93 @@
Copyright © 2010 by Dharma Type.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

View File

@@ -1,206 +1,843 @@
<?php <?php
/* /**
Plugin Name: SP Event Image Generator * Dynamic SportsPress event matchup image endpoint.
Description: Auto-generates featured images for SP Events by combining team colors and logos. *
Version: 1.0 * @package Tonys_Sportspress_Enhancements
Author: Your Name
*/ */
function generate_bisected_image($color1, $color2, $logo1_path, $logo2_path) { if ( ! defined( 'ASC_SP_EVENT_IMAGE_OPTION_KEY' ) ) {
$width = 1200; define( 'ASC_SP_EVENT_IMAGE_OPTION_KEY', 'asc_sp_event_image_settings' );
$height = 628; }
$x_margin = 0.1 * ($width / 2); // 10% of half the width
$y_margin = 0.1 * $height; // 10% of the height if ( ! defined( 'ASC_SP_EVENT_IMAGE_OPTION_GROUP' ) ) {
define( 'ASC_SP_EVENT_IMAGE_OPTION_GROUP', 'asc_sp_event_image_settings' );
}
if ( ! defined( 'ASC_SP_EVENT_IMAGE_SETTINGS_TAB' ) ) {
define( 'ASC_SP_EVENT_IMAGE_SETTINGS_TAB', 'open-graph-images' );
}
if ( ! defined( 'ASC_SP_EVENT_IMAGE_CACHE_VERSION' ) ) {
define( 'ASC_SP_EVENT_IMAGE_CACHE_VERSION', '7' );
}
/**
* Default image generator settings.
*
* @return array<string,string>
*/
function asc_sp_event_image_default_settings() {
return array(
'fallback_left_background' => '#4B5563',
'fallback_right_background' => '#4B5563',
'fallback_text_color' => '#F9FAFB',
'fallback_shadow_color' => '#1F2937',
);
}
/**
* Get image generator settings.
*
* @return array<string,string>
*/
function asc_sp_event_image_get_settings() {
return wp_parse_args( get_option( ASC_SP_EVENT_IMAGE_OPTION_KEY, array() ), asc_sp_event_image_default_settings() );
}
/**
* Sanitize and validate a hex color.
*
* @param string $color Color value.
* @param string $fallback Fallback color.
* @return string
*/
function asc_sp_event_image_color( $color, $fallback = '#4B5563' ) {
return is_string( $color ) && preg_match( '/^#[a-fA-F0-9]{6}$/', $color ) ? strtoupper( $color ) : strtoupper( $fallback );
}
/**
* Sanitize image generator settings.
*
* @param mixed $input Raw settings.
* @return array<string,string>
*/
function asc_sp_event_image_sanitize_settings( $input ) {
$defaults = asc_sp_event_image_default_settings();
$input = is_array( $input ) ? $input : array();
return array(
'fallback_left_background' => asc_sp_event_image_color( isset( $input['fallback_left_background'] ) ? $input['fallback_left_background'] : '', $defaults['fallback_left_background'] ),
'fallback_right_background' => asc_sp_event_image_color( isset( $input['fallback_right_background'] ) ? $input['fallback_right_background'] : '', $defaults['fallback_right_background'] ),
'fallback_text_color' => asc_sp_event_image_color( isset( $input['fallback_text_color'] ) ? $input['fallback_text_color'] : '', $defaults['fallback_text_color'] ),
'fallback_shadow_color' => asc_sp_event_image_color( isset( $input['fallback_shadow_color'] ) ? $input['fallback_shadow_color'] : '', $defaults['fallback_shadow_color'] ),
);
}
/**
* Allocate a GD color from a hex value.
*
* @param GdImage|resource $image Destination image.
* @param string $hex Hex color.
* @param string $fallback Fallback color.
* @return int|false
*/
function asc_sp_event_image_allocate_hex_color( $image, $hex, $fallback = '#4B5563' ) {
$rgb = sscanf( asc_sp_event_image_color( $hex, $fallback ), '#%02x%02x%02x' );
return imagecolorallocate( $image, $rgb[0], $rgb[1], $rgb[2] );
}
/**
* Get cache style hash for image settings and generator version.
*
* @return string
*/
function asc_sp_event_image_cache_style_hash() {
return substr( md5( wp_json_encode( asc_sp_event_image_get_settings() ) . '|' . ASC_SP_EVENT_IMAGE_CACHE_VERSION ), 0, 10 );
}
/**
* Public image URL version token.
*
* @return string
*/
function asc_sp_event_image_url_version() {
return ASC_SP_EVENT_IMAGE_CACHE_VERSION . '-' . asc_sp_event_image_cache_style_hash();
}
/**
* Supported image variants.
*
* @return array<string,array{width:int,height:int}>
*/
function asc_sp_event_image_variants() {
return array(
'wide' => array(
'width' => 1200,
'height' => 628,
),
'square' => array(
'width' => 1200,
'height' => 1200,
),
);
}
/**
* Sanitize an image variant.
*
* @param string $variant Variant.
* @return string
*/
function asc_sp_event_image_sanitize_variant( $variant ) {
$variant = sanitize_key( $variant );
$variants = asc_sp_event_image_variants();
return isset( $variants[ $variant ] ) ? $variant : 'wide';
}
/**
* Get dimensions for an image variant.
*
* @param string $variant Variant.
* @return array{width:int,height:int}
*/
function asc_sp_event_image_variant_dimensions( $variant ) {
$variants = asc_sp_event_image_variants();
$variant = asc_sp_event_image_sanitize_variant( $variant );
return $variants[ $variant ];
}
/**
* Register Tony's Settings tab.
*
* @param array $tabs Existing tabs.
* @return array
*/
function asc_sp_event_image_register_settings_tab( $tabs ) {
$tabs[ ASC_SP_EVENT_IMAGE_SETTINGS_TAB ] = __( 'Open Graph Images', 'tonys-sportspress-enhancements' );
return $tabs;
}
add_filter( 'tse_tonys_settings_tabs', 'asc_sp_event_image_register_settings_tab' );
/**
* Register image generator settings.
*/
function asc_sp_event_image_register_settings() {
register_setting(
ASC_SP_EVENT_IMAGE_OPTION_GROUP,
ASC_SP_EVENT_IMAGE_OPTION_KEY,
array(
'type' => 'array',
'sanitize_callback' => 'asc_sp_event_image_sanitize_settings',
'default' => asc_sp_event_image_default_settings(),
)
);
}
add_action( 'admin_init', 'asc_sp_event_image_register_settings' );
/**
* Capability required to save image generator settings.
*
* @return string
*/
function asc_sp_event_image_settings_capability() {
return 'manage_sportspress';
}
add_filter( 'option_page_capability_' . ASC_SP_EVENT_IMAGE_OPTION_GROUP, 'asc_sp_event_image_settings_capability' );
/**
* Render a color setting row.
*
* @param string $key Setting key.
* @param string $label Field label.
* @param string $help Help text.
* @param array $settings Current settings.
*/
function asc_sp_event_image_render_color_row( $key, $label, $help, array $settings ) {
$value = isset( $settings[ $key ] ) ? asc_sp_event_image_color( $settings[ $key ], asc_sp_event_image_default_settings()[ $key ] ) : asc_sp_event_image_default_settings()[ $key ];
$name = ASC_SP_EVENT_IMAGE_OPTION_KEY . '[' . $key . ']';
$id = 'asc-sp-event-image-' . str_replace( '_', '-', $key );
echo '<tr>';
echo '<th scope="row"><label for="' . esc_attr( $id ) . '">' . esc_html( $label ) . '</label></th>';
echo '<td>';
echo '<input type="color" id="' . esc_attr( $id ) . '" name="' . esc_attr( $name ) . '" value="' . esc_attr( $value ) . '" />';
echo ' <code>' . esc_html( $value ) . '</code>';
echo '<p class="description">' . esc_html( $help ) . '</p>';
echo '</td>';
echo '</tr>';
}
/**
* Render Tony's Settings Open Graph image tab.
*/
function asc_sp_event_image_render_settings_tab() {
if ( ! current_user_can( 'manage_sportspress' ) ) {
return;
}
$settings = asc_sp_event_image_get_settings();
settings_errors( ASC_SP_EVENT_IMAGE_OPTION_GROUP );
echo '<form method="post" action="options.php">';
settings_fields( ASC_SP_EVENT_IMAGE_OPTION_GROUP );
echo '<h2>' . esc_html__( 'Open Graph Matchup Images', 'tonys-sportspress-enhancements' ) . '</h2>';
echo '<p>' . esc_html__( 'Control the generated social preview image used for SportsPress events when a team logo is missing or a team color is not available.', 'tonys-sportspress-enhancements' ) . '</p>';
echo '<p class="description">' . esc_html__( 'The image cache key includes these values, so saving changes forces future image requests to generate fresh files.', 'tonys-sportspress-enhancements' ) . '</p>';
echo '<table class="form-table" role="presentation"><tbody>';
asc_sp_event_image_render_color_row( 'fallback_left_background', __( 'Default Left Background', 'tonys-sportspress-enhancements' ), __( 'Neutral grey used when the first displayed team does not have a valid primary color.', 'tonys-sportspress-enhancements' ), $settings );
asc_sp_event_image_render_color_row( 'fallback_right_background', __( 'Default Right Background', 'tonys-sportspress-enhancements' ), __( 'Neutral grey used when the second displayed team does not have a valid primary color.', 'tonys-sportspress-enhancements' ), $settings );
asc_sp_event_image_render_color_row( 'fallback_text_color', __( 'Fallback Team Text', 'tonys-sportspress-enhancements' ), __( 'Large team-name text drawn when a logo is missing.', 'tonys-sportspress-enhancements' ), $settings );
asc_sp_event_image_render_color_row( 'fallback_shadow_color', __( 'Text Shadow', 'tonys-sportspress-enhancements' ), __( 'A subtle offset shadow for readability. No heavy outline is drawn.', 'tonys-sportspress-enhancements' ), $settings );
echo '</tbody></table>';
echo '<div style="width:420px;max-width:100%;aspect-ratio:1200/628;position:relative;overflow:hidden;background:' . esc_attr( $settings['fallback_left_background'] ) . ';border:1px solid #dcdcde;">';
echo '<div style="position:absolute;inset:0;clip-path:polygon(60% 0,100% 0,100% 100%,40% 100%);background:' . esc_attr( $settings['fallback_right_background'] ) . ';"></div>';
echo '<div style="position:absolute;left:8%;right:8%;top:50%;transform:translateY(-50%);display:flex;justify-content:space-between;gap:24px;font-family:Impact,Arial Black,sans-serif;font-size:44px;line-height:.9;color:' . esc_attr( $settings['fallback_text_color'] ) . ';text-shadow:2px 2px 0 ' . esc_attr( $settings['fallback_shadow_color'] ) . ';">';
echo '<span>' . esc_html__( 'HAWKS', 'tonys-sportspress-enhancements' ) . '</span><span>' . esc_html__( 'ELECTRONS', 'tonys-sportspress-enhancements' ) . '</span>';
echo '</div></div>';
submit_button( __( 'Save Settings', 'tonys-sportspress-enhancements' ) );
echo '</form>';
}
add_action( 'tse_tonys_settings_render_tab_' . ASC_SP_EVENT_IMAGE_SETTINGS_TAB, 'asc_sp_event_image_render_settings_tab' );
/**
* Load a raster logo with GD when the local install supports the format.
*
* @param string $path Local image path.
* @return GdImage|resource|false
*/
function asc_sp_event_image_create_from_file( $path ) {
if ( ! is_string( $path ) || '' === $path || ! is_readable( $path ) ) {
return false;
}
$image_type = function_exists( 'exif_imagetype' ) ? @exif_imagetype( $path ) : false;
if ( IMAGETYPE_PNG === $image_type && function_exists( 'imagecreatefrompng' ) ) {
return @imagecreatefrompng( $path );
}
if ( IMAGETYPE_JPEG === $image_type && function_exists( 'imagecreatefromjpeg' ) ) {
return @imagecreatefromjpeg( $path );
}
if ( IMAGETYPE_GIF === $image_type && function_exists( 'imagecreatefromgif' ) ) {
return @imagecreatefromgif( $path );
}
if ( defined( 'IMAGETYPE_WEBP' ) && IMAGETYPE_WEBP === $image_type && function_exists( 'imagecreatefromwebp' ) ) {
return @imagecreatefromwebp( $path );
}
return false;
}
/**
* Destroy a GD image on PHP versions where that still has an effect.
*
* @param GdImage|resource $image Image resource.
*/
function asc_sp_event_image_destroy( $image ) {
if ( defined( 'PHP_VERSION_ID' ) && PHP_VERSION_ID >= 80500 ) {
return;
}
if ( $image ) {
imagedestroy( $image );
}
}
/**
* Get the bundled fallback font path.
*
* @return string
*/
function asc_sp_event_image_font_path() {
if ( defined( 'TONY_SPORTSPRESS_ENHANCEMENTS_DIR' ) ) {
return TONY_SPORTSPRESS_ENHANCEMENTS_DIR . 'assets/fonts/BebasNeue-Regular.ttf';
}
return dirname( __DIR__ ) . '/assets/fonts/BebasNeue-Regular.ttf';
}
/**
* Measure TrueType text dimensions.
*
* @param int $font_size Font size.
* @param string $font_path Font path.
* @param string $text Text.
* @return array{width:int,height:int}
*/
function asc_sp_event_image_ttf_text_size( $font_size, $font_path, $text ) {
$box = imagettfbbox( $font_size, 0, $font_path, $text );
if ( ! is_array( $box ) ) {
return array(
'width' => 0,
'height' => 0,
);
}
return array(
'width' => absint( max( $box[2], $box[4] ) - min( $box[0], $box[6] ) ),
'height' => absint( max( $box[1], $box[3] ) - min( $box[5], $box[7] ) ),
);
}
/**
* Wrap text for a TrueType bounding box.
*
* @param string $text Text.
* @param string $font_path Font path.
* @param int $font_size Font size.
* @param int $max_width Maximum line width.
* @return string[]
*/
function asc_sp_event_image_wrap_ttf_text( $text, $font_path, $font_size, $max_width ) {
$words = preg_split( '/\s+/', trim( $text ) );
if ( ! is_array( $words ) || empty( $words ) ) {
return array();
}
$lines = array();
$line = '';
foreach ( $words as $word ) {
$candidate = '' === $line ? $word : "{$line} {$word}";
$size = asc_sp_event_image_ttf_text_size( $font_size, $font_path, $candidate );
if ( $size['width'] <= $max_width || '' === $line ) {
$line = $candidate;
continue;
}
$lines[] = $line;
$line = $word;
}
if ( '' !== $line ) {
$lines[] = $line;
}
return $lines;
}
/**
* Draw large fallback team text with the bundled sporty font.
*
* @param GdImage|resource $image Destination image.
* @param string $text Text to draw.
* @param int $center Center x-coordinate.
* @param int $width Canvas width.
* @param int $height Canvas height.
* @param int|null $center_y Center y-coordinate.
* @return bool True when drawn.
*/
function asc_sp_event_image_draw_ttf_team_text( $image, $text, $center, $width, $height, $center_y = null ) {
$font_path = asc_sp_event_image_font_path();
if ( ! function_exists( 'imagettftext' ) || ! function_exists( 'imagettfbbox' ) || ! is_readable( $font_path ) ) {
return false;
}
$text = strtoupper( trim( wp_strip_all_tags( (string) $text ) ) );
if ( '' === $text ) {
return false;
}
$center_y = null === $center_y ? (int) ( $height / 2 ) : (int) $center_y;
$half_width = (int) ( $width / 2 );
$min_x = $center < $half_width ? 48 : $half_width + 48;
$max_x = $center < $half_width ? $half_width - 48 : $width - 48;
$max_width = $max_x - $min_x;
$max_height = (int) ( $height * 0.68 );
$font_size = 190;
$lines = array( $text );
$line_gap = 12;
while ( $font_size >= 42 ) {
$lines = asc_sp_event_image_wrap_ttf_text( $text, $font_path, $font_size, $max_width );
$line_heights = array();
$widest = 0;
foreach ( $lines as $line ) {
$size = asc_sp_event_image_ttf_text_size( $font_size, $font_path, $line );
$line_heights[] = $size['height'];
$widest = max( $widest, $size['width'] );
}
$total_height = array_sum( $line_heights ) + max( 0, count( $lines ) - 1 ) * $line_gap;
if ( $widest <= $max_width && $total_height <= $max_height && count( $lines ) <= 3 ) {
break;
}
$font_size -= 6;
}
$line_heights = array();
$total_height = 0;
foreach ( $lines as $line ) {
$size = asc_sp_event_image_ttf_text_size( $font_size, $font_path, $line );
$line_heights[] = $size['height'];
$total_height += $size['height'];
}
$total_height += max( 0, count( $lines ) - 1 ) * $line_gap;
$y = (int) ( $center_y - ( $total_height / 2 ) );
$settings = asc_sp_event_image_get_settings();
$fill = asc_sp_event_image_allocate_hex_color( $image, $settings['fallback_text_color'], '#F9FAFB' );
$shadow = asc_sp_event_image_allocate_hex_color( $image, $settings['fallback_shadow_color'], '#1F2937' );
foreach ( $lines as $index => $line ) {
$size = asc_sp_event_image_ttf_text_size( $font_size, $font_path, $line );
$x = (int) ( $center - ( $size['width'] / 2 ) );
$x = max( $min_x, min( $x, $max_x - $size['width'] ) );
$y += $line_heights[ $index ];
imagettftext( $image, $font_size, 0, $x + 4, $y + 5, $shadow, $font_path, $line );
imagettftext( $image, $font_size, 0, $x, $y, $fill, $font_path, $line );
$y += $line_gap;
}
return true;
}
/**
* Draw fallback team text when no readable logo is available.
*
* @param GdImage|resource $image Destination image.
* @param string $text Text to draw.
* @param int $center Center x-coordinate.
* @param int $width Canvas width.
* @param int $height Canvas height.
* @param int|null $center_y Center y-coordinate.
*/
function asc_sp_event_image_draw_team_text( $image, $text, $center, $width, $height, $center_y = null ) {
$text = trim( wp_strip_all_tags( (string) $text ) );
if ( '' === $text ) {
return;
}
if ( asc_sp_event_image_draw_ttf_team_text( $image, $text, $center, $width, $height, $center_y ) ) {
return;
}
$center_y = null === $center_y ? (int) ( $height / 2 ) : (int) $center_y;
$font = 5;
$lines = explode( "\n", wordwrap( strtoupper( $text ), 14, "\n", true ) );
$lines = array_slice( $lines, 0, 3 );
$line_height = imagefontheight( $font ) + 8;
$total = count( $lines ) * $line_height;
$y = (int) ( $center_y - ( $total / 2 ) );
$settings = asc_sp_event_image_get_settings();
$fill = asc_sp_event_image_allocate_hex_color( $image, $settings['fallback_text_color'], '#F9FAFB' );
$shadow = asc_sp_event_image_allocate_hex_color( $image, $settings['fallback_shadow_color'], '#1F2937' );
$half_width = (int) ( $width / 2 );
foreach ( $lines as $line ) {
$line = trim( $line );
$text_width = imagefontwidth( $font ) * strlen( $line );
$x = (int) ( $center - ( $text_width / 2 ) );
$min_x = $center < $half_width ? 12 : $half_width + 12;
$max_x = $center < $half_width ? $half_width - $text_width - 12 : $width - $text_width - 12;
$x = max( $min_x, min( $x, $max_x ) );
imagestring( $image, $font, $x + 2, $y + 2, $line, $shadow );
imagestring( $image, $font, $x, $y, $line, $fill );
$y += $line_height;
}
}
/**
* Place a logo on one half of the canvas, falling back to text.
*
* @param GdImage|resource $image Destination image.
* @param string $logo_path Logo path.
* @param string $fallback Fallback text.
* @param int $center Center x-coordinate.
* @param int $width Canvas width.
* @param int $height Canvas height.
* @param int|null $center_y Center y-coordinate.
*/
function asc_sp_event_image_place_logo_or_text( $image, $logo_path, $fallback, $center, $width, $height, $center_y = null ) {
$x_margin = 0.1 * ( $width / 2 );
$y_margin = 0.1 * $height;
$logo = asc_sp_event_image_create_from_file( $logo_path );
$center_y = null === $center_y ? (int) ( $height / 2 ) : (int) $center_y;
if ( ! $logo ) {
asc_sp_event_image_draw_team_text( $image, $fallback, $center, $width, $height, $center_y );
return;
}
imagealphablending( $logo, true );
imagesavealpha( $logo, true );
$logo_width = imagesx( $logo );
$logo_height = imagesy( $logo );
if ( $logo_width <= 0 || $logo_height <= 0 ) {
asc_sp_event_image_destroy( $logo );
asc_sp_event_image_draw_team_text( $image, $fallback, $center, $width, $height, $center_y );
return;
}
$max_width = ( $width / 2 ) - ( 2 * $x_margin );
$max_height = $height - ( 2 * $y_margin );
$new_width = $logo_width;
$new_height = $logo_height;
if ( $logo_width > $max_width || $logo_height > $max_height ) {
$aspect_ratio = $logo_width / $logo_height;
if ( $logo_width / $max_width > $logo_height / $max_height ) {
$new_width = $max_width;
$new_height = $max_width / $aspect_ratio;
} else {
$new_height = $max_height;
$new_width = $max_height * $aspect_ratio;
}
}
$logo_x = (int) ( $center - ( $new_width / 2 ) );
$logo_y = (int) ( $center_y - ( $new_height / 2 ) );
imagecopyresampled( $image, $logo, $logo_x, $logo_y, 0, 0, (int) $new_width, (int) $new_height, $logo_width, $logo_height );
asc_sp_event_image_destroy( $logo );
}
/**
* Generate a PNG matchup image.
*
* @param string $color1 Left color.
* @param string $color2 Right color.
* @param string $logo1_path Left logo path.
* @param string $logo2_path Right logo path.
* @param string $team1_fallback Left fallback text.
* @param string $team2_fallback Right fallback text.
* @param int $width Image width.
* @param int $height Image height.
* @return string
*/
function generate_bisected_image( $color1, $color2, $logo1_path, $logo2_path, $team1_fallback = '', $team2_fallback = '', $width = 1200, $height = 628 ) {
$width = max( 1, absint( $width ) );
$height = max( 1, absint( $height ) );
$image = imagecreatetruecolor( $width, $height ); $image = imagecreatetruecolor( $width, $height );
$settings = asc_sp_event_image_get_settings();
// Allocate colors imagealphablending( $image, true );
$rgb1 = sscanf($color1, "#%02x%02x%02x"); imagesavealpha( $image, true );
$rgb2 = sscanf($color2, "#%02x%02x%02x");
$color1_alloc = imagecolorallocate($image, $rgb1[0], $rgb1[1], $rgb1[2]); $color1_alloc = asc_sp_event_image_allocate_hex_color( $image, $color1, $settings['fallback_left_background'] );
$color2_alloc = imagecolorallocate($image, $rgb2[0], $rgb2[1], $rgb2[2]); $color2_alloc = asc_sp_event_image_allocate_hex_color( $image, $color2, $settings['fallback_right_background'] );
$points1 = array(
0,
0,
0,
$height,
$width * .40,
$height,
$width * .60,
0,
);
$points2 = array(
$width,
0,
$width,
$height,
$width * .40,
$height,
$width * .60,
0,
);
// Fill halves with a 15-degree angled bisection
$points1 = [
0, 0,
0, $height,
$width*.40, $height,
$width*.60, 0,
];
$points2 = [
$width, 0,
$width, $height,
$width*.40, $height,
$width*.60, 0,
];
imagefilledpolygon( $image, $points1, $color1_alloc ); imagefilledpolygon( $image, $points1, $color1_alloc );
imagefilledpolygon( $image, $points2, $color2_alloc ); imagefilledpolygon( $image, $points2, $color2_alloc );
// Add logos with resizing and positioning if paths are not empty $left_center_y = (int) ( $height / 2 );
if (!empty($logo1_path)) { $right_center_y = (int) ( $height / 2 );
$logo1 = imagecreatefrompng($logo1_path);
$logo1_width = imagesx($logo1);
$logo1_height = imagesy($logo1);
// Calculate max dimensions for logo 1 if ( $width === $height ) {
$max_width = ($width / 2) - (2 * $x_margin); $left_center_y = (int) ( $height * 0.28 );
$max_height = $height - (2 * $y_margin); $right_center_y = (int) ( $height * 0.72 );
// Resize logo 1
$new_logo1_width = $logo1_width;
$new_logo1_height = $logo1_height;
if ($logo1_width > $max_width || $logo1_height > $max_height) {
$aspect_ratio1 = $logo1_width / $logo1_height;
if ($logo1_width / $max_width > $logo1_height / $max_height) {
$new_logo1_width = $max_width;
$new_logo1_height = $max_width / $aspect_ratio1;
} else {
$new_logo1_height = $max_height;
$new_logo1_width = $max_height * $aspect_ratio1;
}
} }
// Center logo 1 asc_sp_event_image_place_logo_or_text( $image, $logo1_path, $team1_fallback, (int) ( $width / 4 ), $width, $height, $left_center_y );
$logo1_x = (int) ($width / 4) - ($new_logo1_width / 2); asc_sp_event_image_place_logo_or_text( $image, $logo2_path, $team2_fallback, (int) ( 3 * $width / 4 ), $width, $height, $right_center_y );
$logo1_y = (int) ($height / 2) - ($new_logo1_height / 2);
imagecopyresampled($image, $logo1, $logo1_x, $logo1_y, 0, 0, $new_logo1_width, $new_logo1_height, $logo1_width, $logo1_height);
imagedestroy($logo1);
}
if (!empty($logo2_path)) {
$logo2 = imagecreatefrompng($logo2_path);
$logo2_width = imagesx($logo2);
$logo2_height = imagesy($logo2);
// Calculate max dimensions for logo 2
$max_width = ($width / 2) - (2 * $x_margin);
$max_height = $height - (2 * $y_margin);
// Resize logo 2
$new_logo2_width = $logo2_width;
$new_logo2_height = $logo2_height;
if ($logo2_width > $max_width || $logo2_height > $max_height) {
$aspect_ratio2 = $logo2_width / $logo2_height;
if ($logo2_width / $max_width > $logo2_height / $max_height) {
$new_logo2_width = $max_width;
$new_logo2_height = $max_width / $aspect_ratio2;
} else {
$new_logo2_height = $max_height;
$new_logo2_width = $max_height * $aspect_ratio2;
}
}
// Center logo 2
$logo2_x = (int) (3 * $width / 4) - ($new_logo2_width / 2);
$logo2_y = (int) ($height / 2) - ($new_logo2_height / 2);
imagecopyresampled($image, $logo2, $logo2_x, $logo2_y, 0, 0, $new_logo2_width, $new_logo2_height, $logo2_width, $logo2_height);
imagedestroy($logo2);
}
// Start output buffering to capture the image data
ob_start(); ob_start();
imagepng($image); // Output the image as PNG imagepng( $image );
$image_data = ob_get_clean(); // Get the image data from the buffer $image_data = ob_get_clean();
// Clean up memory asc_sp_event_image_destroy( $image );
imagedestroy($image);
return $image_data; return $image_data;
} }
/**
* Register the image endpoint.
*/
function add_image_generator_endpoint() { function add_image_generator_endpoint() {
add_rewrite_endpoint( 'head-to-head', EP_ROOT, true ); add_rewrite_endpoint( 'head-to-head', EP_ROOT, true );
} }
add_action( 'init', 'add_image_generator_endpoint' ); add_action( 'init', 'add_image_generator_endpoint' );
function handle_image_request() { /**
if (!isset($_GET['post'])) return; * Return a clean 404 response for bad image requests.
*
* @param string $message Response body.
*/
function asc_sp_event_image_not_found( $message = 'Image not found.' ) {
status_header( 404 );
nocache_headers();
$post_id = $_GET['post']; while ( ob_get_level() ) {
ob_end_clean();
}
echo esc_html( $message );
exit;
}
/**
* Get a sanitized post ID from the request.
*
* @return int
*/
function asc_sp_event_image_request_post_id() {
if ( ! isset( $_GET['post'] ) ) {
return 0;
}
$post_id = wp_unslash( $_GET['post'] );
if ( is_array( $post_id ) ) {
return 0;
}
return absint( $post_id );
}
/**
* Get the requested image variant.
*
* @return string
*/
function asc_sp_event_image_request_variant() {
if ( ! isset( $_GET['variant'] ) ) {
return 'wide';
}
$variant = wp_unslash( $_GET['variant'] );
if ( is_array( $variant ) ) {
return 'wide';
}
return asc_sp_event_image_sanitize_variant( $variant );
}
/**
* Prepare image request data, or a WP_Error for 404 handling.
*
* @param int $post_id Event post ID.
* @param string $variant Image variant.
* @return array|WP_Error
*/
function asc_sp_event_prepare_image_request( $post_id, $variant = 'wide' ) {
$post_id = absint( $post_id );
$post = get_post( $post_id ); $post = get_post( $post_id );
$variant = asc_sp_event_image_sanitize_variant( $variant );
$dimensions = asc_sp_event_image_variant_dimensions( $variant );
// Verify post type if ( ! $post || 'sp_event' !== $post->post_type ) {
if (!$post && $post->post_type !== 'sp_event') return; return new WP_Error( 'invalid_event', __( 'Invalid event image request.', 'tonys-sportspress-enhancements' ) );
}
// Get associated teams from post meta if ( function_exists( 'asc_sp_event_team_ids' ) ) {
$team_ids = get_post_meta($post_id, 'sp_team', false); // false to get an array of values $team_ids = asc_sp_event_team_ids( $post );
} else {
$team_ids = array();
// Ensure we have exactly two teams foreach ( get_post_meta( $post_id, 'sp_team', false ) as $team_id ) {
if (count($team_ids) < 2) return; while ( is_array( $team_id ) ) {
$team_id = array_shift( array_filter( $team_id ) );
}
$team_id = absint( $team_id );
if ( $team_id > 0 ) {
$team_ids[] = $team_id;
}
}
}
$team_ids = array_values( array_unique( $team_ids ) );
if ( count( $team_ids ) < 2 ) {
return new WP_Error( 'missing_teams', __( 'Event image request is missing teams.', 'tonys-sportspress-enhancements' ) );
}
$team1_id = $team_ids[0]; $team1_id = $team_ids[0];
$team2_id = $team_ids[1]; $team2_id = $team_ids[1];
$team1 = get_post( $team1_id ); $team1 = get_post( $team1_id );
$team2 = get_post( $team2_id ); $team2 = get_post( $team2_id );
$team1_postmodified = strtotime($team1->post_modified);
$team2_postmodified = strtotime($team2->post_modified);
$cache_key = "team_image_{$team1_id}_{$team1_postmodified}-{$team2_id}_{$team2_postmodified}"; if ( ! $team1 || ! $team2 ) {
$cached_image_path = get_transient($cache_key); return new WP_Error( 'invalid_teams', __( 'Event image request has invalid teams.', 'tonys-sportspress-enhancements' ) );
}
$settings = asc_sp_event_image_get_settings();
$team1_colors = get_post_meta( $team1_id, 'sp_colors', true );
$team2_colors = get_post_meta( $team2_id, 'sp_colors', true );
$team1_color = is_array( $team1_colors ) && ! empty( $team1_colors['primary'] ) ? $team1_colors['primary'] : $settings['fallback_left_background'];
$team2_color = is_array( $team2_colors ) && ! empty( $team2_colors['primary'] ) ? $team2_colors['primary'] : $settings['fallback_right_background'];
$team1_logo_thumbnail_id = get_post_thumbnail_id( $team1_id );
$team2_logo_thumbnail_id = get_post_thumbnail_id( $team2_id );
$team1_logo = $team1_logo_thumbnail_id ? get_attached_file( $team1_logo_thumbnail_id ) : '';
$team2_logo = $team2_logo_thumbnail_id ? get_attached_file( $team2_logo_thumbnail_id ) : '';
$team1_modified = strtotime( $team1->post_modified );
$team2_modified = strtotime( $team2->post_modified );
return array(
'cache_key' => 'team_image_v' . ASC_SP_EVENT_IMAGE_CACHE_VERSION . '_' . asc_sp_event_image_cache_style_hash() . "_{$variant}_{$team1_id}_{$team1_modified}-{$team2_id}_{$team2_modified}",
'variant' => $variant,
'width' => $dimensions['width'],
'height' => $dimensions['height'],
'team1_color' => asc_sp_event_image_color( $team1_color, $settings['fallback_left_background'] ),
'team2_color' => asc_sp_event_image_color( $team2_color, $settings['fallback_right_background'] ),
'team1_logo' => $team1_logo,
'team2_logo' => $team2_logo,
'team1_fallback' => function_exists( 'asc_sp_team_short_name' ) ? asc_sp_team_short_name( $team1_id ) : get_the_title( $team1_id ),
'team2_fallback' => function_exists( 'asc_sp_team_short_name' ) ? asc_sp_team_short_name( $team2_id ) : get_the_title( $team2_id ),
);
}
/**
* Handle the image endpoint request.
*/
function handle_image_request() {
if ( ! isset( $_GET['post'] ) ) {
return;
}
$request = asc_sp_event_prepare_image_request( asc_sp_event_image_request_post_id(), asc_sp_event_image_request_variant() );
if ( is_wp_error( $request ) ) {
asc_sp_event_image_not_found( $request->get_error_message() );
}
$cached_image_path = get_transient( $request['cache_key'] );
if ( $cached_image_path && file_exists( $cached_image_path ) ) { if ( $cached_image_path && file_exists( $cached_image_path ) ) {
serve_image( $cached_image_path ); serve_image( $cached_image_path );
exit; exit;
} }
// Get team colors and logos $image_data = generate_bisected_image(
$team1_colors = get_post_meta($team1_id, 'sp_colors', true); $request['team1_color'],
$team2_colors = get_post_meta($team2_id, 'sp_colors', true); $request['team2_color'],
$request['team1_logo'],
$default_color = '#FFFFFF'; // Default color (black) $request['team2_logo'],
$team1_color = !empty($team1_colors['primary']) ? $team1_colors['primary'] : $default_color; $request['team1_fallback'],
$team2_color = !empty($team2_colors['primary']) ? $team2_colors['primary'] : $default_color; $request['team2_fallback'],
$request['width'],
// Security check for hex color $request['height']
$team1_color = preg_match('/^#[a-fA-F0-9]{6}$/', $team1_color) ? $team1_color : '#FFFFFF'; );
$team2_color = preg_match('/^#[a-fA-F0-9]{6}$/', $team2_color) ? $team2_color : '#FFFFFF'; $image_path = save_image_to_cache( $image_data, $request['cache_key'] );
$team1_logo_url = get_the_post_thumbnail_url($team1_id, 'full');
$team2_logo_url = get_the_post_thumbnail_url($team2_id, 'full');
// Check if both team colors are default and both logos are empty
if (($team1_color === $default_color && empty($team1_logo_url)) && ($team2_color === $default_color && empty($team2_logo_url))) {
return; // Do nothing if both teams have no valid color or logo
}
$team1_logo_thumbnail_id = get_post_thumbnail_id($team1_id, 'full');
$team2_logo_thumbnail_id = get_post_thumbnail_id($team2_id, 'full');
$team1_logo = get_attached_file($team1_logo_thumbnail_id);
$team2_logo = get_attached_file($team2_logo_thumbnail_id);
// Generate the image if no valid cache exists
$image_data = generate_bisected_image($team1_color, $team2_color, $team1_logo, $team2_logo);
$image_path = save_image_to_cache($image_data, $cache_key);
set_transient($cache_key, $image_path, DAY_IN_SECONDS * 30); // Cache for 30 days
set_transient( $request['cache_key'], $image_path, DAY_IN_SECONDS * 30 );
serve_image( $image_path ); serve_image( $image_path );
exit; exit;
} }
add_action( 'template_redirect', 'handle_image_request' ); add_action( 'template_redirect', 'handle_image_request' );
/**
* Serve a cached image file.
*
* @param string $image_path Local image path.
*/
function serve_image( $image_path ) { function serve_image( $image_path ) {
header('Content-Type: image/png'); if ( ! file_exists( $image_path ) ) {
if (file_exists($image_path)) { asc_sp_event_image_not_found();
status_header( 200 );
} else {
status_header( 404 );
die("Image not found.");
} }
// Clear all output buffering to prevent any extra output status_header( 200 );
header( 'Content-Type: image/png' );
while ( ob_get_level() ) { while ( ob_get_level() ) {
ob_end_clean(); ob_end_clean();
} }
readfile( $image_path ); readfile( $image_path );
} }
/**
* Save generated image data to the upload cache.
*
* @param string $image_data Raw PNG bytes.
* @param string $cache_key Cache key.
* @return string
*/
function save_image_to_cache( $image_data, $cache_key ) { function save_image_to_cache( $image_data, $cache_key ) {
$upload_dir = wp_get_upload_dir(); $upload_dir = wp_get_upload_dir();
$file_path = $upload_dir['path'] . '/' . $cache_key . '.png'; $file_path = trailingslashit( $upload_dir['path'] ) . sanitize_file_name( $cache_key ) . '.png';
// Assuming $image_data is raw image data
file_put_contents( $file_path, $image_data ); file_put_contents( $file_path, $image_data );
return $file_path; return $file_path;

View File

@@ -1,51 +1,156 @@
<?php <?php
/* /**
Plugin Name: Custom Open Graph Tags with SportsPress Integration * Open Graph tags for SportsPress events.
Description: Adds custom Open Graph tags to posts based on their type, specifically handling sp_event post types with methods from the SportsPress SP_Event class. *
Version: 1.0 * @package Tonys_Sportspress_Enhancements
Author: Your Name
*/ */
add_action( 'wp_head', 'custom_open_graph_tags_with_sportspress_integration' ); add_action( 'wp_head', 'custom_open_graph_tags_with_sportspress_integration' );
function asc_sp_event_matchup_image_url( $post ) { /**
if ( is_numeric( $post ) ) { * Build the dynamic matchup image URL for an event.
$post = get_post( $post ); *
} * @param int|WP_Post $post Event post or ID.
* @param string $variant Image variant.
* @return string
*/
function asc_sp_event_matchup_image_url( $post, $variant = 'wide' ) {
$post = asc_sp_event_get_post( $post );
if ( ! $post || 'sp_event' !== $post->post_type ) { if ( ! $post ) {
return ''; return '';
} }
return get_site_url() . '/head-to-head?post=' . $post->ID; $args = array(
'post' => $post->ID,
);
if ( 'wide' !== $variant ) {
$args['variant'] = $variant;
} }
function asc_generate_sp_event_title( $post ) { if ( function_exists( 'asc_sp_event_image_url_version' ) ) {
// See https://github.com/ThemeBoy/SportsPress/blob/770fa8c6654d7d6648791e877709c2428677635b/includes/admin/post-types/class-sp-admin-cpt-event.php#L99C40-L99C55 $args['v'] = asc_sp_event_image_url_version();
if ( is_numeric( $post ) ) {
$post = get_post( $post );
} }
if ( ! $post || $post->post_type !== 'sp_event' ) {
return get_the_title(); return add_query_arg( $args, home_url( '/head-to-head' ) );
}
/**
* Build Open Graph image descriptors for an event.
*
* @param WP_Post $post Event post.
* @return array<int,array<string,string>>
*/
function asc_sp_event_open_graph_images( WP_Post $post ) {
return array(
array(
'url' => asc_sp_event_matchup_image_url( $post, 'wide' ),
'width' => '1200',
'height' => '628',
),
array(
'url' => asc_sp_event_matchup_image_url( $post, 'square' ),
'width' => '1200',
'height' => '1200',
),
);
}
/**
* Normalize an event post argument.
*
* @param int|WP_Post|null $post Post object or ID.
* @return WP_Post|null
*/
function asc_sp_event_get_post( $post = null ) {
if ( null === $post ) {
$post = get_post();
} elseif ( is_numeric( $post ) ) {
$post = get_post( absint( $post ) );
}
if ( ! $post instanceof WP_Post || 'sp_event' !== $post->post_type ) {
return null;
}
return $post;
}
/**
* Get event team IDs in SportsPress display order.
*
* @param int|WP_Post $post Event post or ID.
* @return int[]
*/
function asc_sp_event_team_ids( $post ) {
$post = asc_sp_event_get_post( $post );
if ( ! $post ) {
return array();
} }
$teams = get_post_meta( $post->ID, 'sp_team', false ); $teams = get_post_meta( $post->ID, 'sp_team', false );
$teams = array_filter( $teams ); $team_ids = array();
$team_names = array();
foreach ( $teams as $team ) { foreach ( $teams as $team ) {
while ( is_array( $team ) ) { while ( is_array( $team ) ) {
$team = array_shift( array_filter( $team ) ); $team = array_shift( array_filter( $team ) );
} }
$team = absint( $team );
if ( $team > 0 ) { if ( $team > 0 ) {
$team_names[] = sp_team_short_name( $team ); $team_ids[] = $team;
} }
} }
$team_names = array_unique( $team_names ); $team_ids = array_values( array_unique( $team_ids ) );
if ( get_option( 'sportspress_event_reverse_teams', 'no' ) === 'yes' ) { if ( 'yes' === get_option( 'sportspress_event_reverse_teams', 'no' ) ) {
$team_names = array_reverse( $team_names ); $team_ids = array_reverse( $team_ids );
}
return $team_ids;
}
/**
* Get a safe team short name with fallbacks for test and partial SportsPress environments.
*
* @param int $team_id Team post ID.
* @return string
*/
function asc_sp_team_short_name( $team_id ) {
$name = '';
if ( function_exists( 'sp_team_short_name' ) ) {
$name = (string) sp_team_short_name( $team_id );
}
if ( '' === trim( $name ) ) {
$name = get_the_title( $team_id );
}
return '' !== trim( $name ) ? $name : __( 'Team TBD', 'tonys-sportspress-enhancements' );
}
/**
* Generate a matchup title from event teams.
*
* @param int|WP_Post $post Event post or ID.
* @return string
*/
function asc_generate_sp_event_title( $post ) {
$post = asc_sp_event_get_post( $post );
if ( ! $post ) {
return get_the_title();
}
$team_names = array_map( 'asc_sp_team_short_name', asc_sp_event_team_ids( $post ) );
$team_names = array_values( array_filter( array_unique( $team_names ) ) );
if ( empty( $team_names ) ) {
return get_the_title( $post );
} }
$delimiter = ' ' . get_option( 'sportspress_event_teams_delimiter', 'vs' ) . ' '; $delimiter = ' ' . get_option( 'sportspress_event_teams_delimiter', 'vs' ) . ' ';
@@ -53,147 +158,343 @@ function asc_generate_sp_event_title( $post ) {
return implode( $delimiter, $team_names ); return implode( $delimiter, $team_names );
} }
/**
* Generate compact event date text.
*
* @param int|WP_Post $post Event post or ID.
* @param bool $withTime Include time.
* @return string
*/
function asc_generate_short_date( $post, $withTime = true ) { function asc_generate_short_date( $post, $withTime = true ) {
$post = asc_sp_event_get_post( $post );
if ( ! $post ) {
return '';
}
$formatted_date = get_the_date( 'D n/j/y', $post ); $formatted_date = get_the_date( 'D n/j/y', $post );
if ( ! $withTime ) { if ( ! $withTime ) {
return $formatted_date; return $formatted_date;
} }
if ( get_the_date('i', $post) == "00") { $formatted_time = '00' === get_the_date( 'i', $post ) ? get_the_date( 'gA', $post ) : get_the_date( 'g:iA', $post );
$formatted_time = get_the_date('gA', $post);
} else {
$formatted_time = get_the_date('g:iA', $post);
}
return $formatted_date . " " . $formatted_time ;
return trim( $formatted_date . ' ' . $formatted_time );
} }
function custom_open_graph_tags_with_sportspress_integration() { /**
if (is_single()) { * Get venue name for an event.
global $post; *
if ($post->post_type === 'sp_event') { * @param WP_Post $post Event post.
// Instantiate SP_Event object * @return string
$event = new SP_Event($post->ID); */
function asc_sp_event_venue_name( WP_Post $post ) {
// Fetch details using SP_Event methods
$publish_date = get_the_date('F j, Y', $post);
$venue_terms = get_the_terms( $post->ID, 'sp_venue' ); $venue_terms = get_the_terms( $post->ID, 'sp_venue' );
$venue_name = $venue_terms ? $venue_terms[0]->name : 'Venue TBD';
$results = $event->results(); // Using SP_Event method
$title = asc_generate_sp_event_title($post);
$sp_status = get_post_meta( $post->ID, 'sp_status', true );
$status = $event->status(); // Using SP_Event method
$publish_date_and_time = get_the_date('F j, Y g:i A', $post);
$description = "{$publish_date_and_time} at {$venue_name}.";
if ( 'postponed' == $sp_status || 'cancelled' == $sp_status || 'tbd' == $sp_status) { if ( is_wp_error( $venue_terms ) || empty( $venue_terms ) ) {
$description = strtoupper($sp_status) . "" . $description; return __( 'Venue TBD', 'tonys-sportspress-enhancements' );
$title = strtoupper($sp_status) . "" . $title . "" . asc_generate_short_date($post) . "" . $venue_name;
} }
if ( 'future' == $status ) { return $venue_terms[0]->name;
$description = $description;
$title = $title . "" . asc_generate_short_date($post) . "" . $venue_name;
} }
if ( 'results' == $status ) { // checks if there is a final score /**
// Get event result data * Normalize event body content for meta descriptions.
$data = $event->results(); *
* @param WP_Post $post Event post.
* @return string
*/
function asc_sp_event_body_excerpt( WP_Post $post ) {
$content = strip_shortcodes( $post->post_content );
$content = wp_strip_all_tags( $content, true );
$content = html_entity_decode( $content, ENT_QUOTES, get_bloginfo( 'charset' ) );
$content = preg_replace( '/\s+/', ' ', $content );
$content = trim( (string) $content );
// The first row should be column labels if ( '' === $content ) {
$labels = $data[0]; return '';
// Remove the first row to leave us with the actual data
unset( $data[0] );
$data = array_filter( $data );
if ( empty( $data ) ) {
return false;
} }
// Initialize return wp_trim_words( $content, 35, '' );
$i = 0;
$result_string = '';
$title_string = '';
// Reverse teams order if the option "Events > Teams > Order > Reverse order" is enabled.
$reverse_teams = get_option( 'sportspress_event_reverse_teams', 'no' ) === 'yes' ? true : false;
if ( $reverse_teams ) {
$data = array_reverse( $data, true );
} }
$teams_result_array = []; /**
* Safely instantiate a SportsPress event object.
*
* @param WP_Post $post Event post.
* @return object|null
*/
function asc_sp_event_object( WP_Post $post ) {
if ( ! class_exists( 'SP_Event' ) ) {
return null;
}
try {
return new SP_Event( $post->ID );
} catch ( Throwable $e ) {
return null;
}
}
/**
* Get the SportsPress event status with fallbacks.
*
* @param WP_Post $post Event post.
* @param object|null $event SportsPress event object.
* @return string
*/
function asc_sp_event_status( WP_Post $post, $event = null ) {
if ( $event && is_callable( array( $event, 'status' ) ) ) {
try {
$status = (string) $event->status();
if ( '' !== $status ) {
return $status;
}
} catch ( Throwable $e ) {
return '';
}
}
return 'future' === $post->post_status ? 'future' : '';
}
/**
* Get SportsPress result rows safely.
*
* @param object|null $event SportsPress event object.
* @return array
*/
function asc_sp_event_results( $event = null ) {
if ( ! $event || ! is_callable( array( $event, 'results' ) ) ) {
return array();
}
try {
$results = $event->results();
return is_array( $results ) ? $results : array();
} catch ( Throwable $e ) {
return array();
}
}
/**
* Convert a result row into outcome labels.
*
* @param array $result Result row.
* @return array
*/
function asc_sp_event_result_outcomes( array $result ) {
$result_outcome = isset( $result['outcome'] ) ? $result['outcome'] : null;
if ( ! is_array( $result_outcome ) ) {
return array();
}
foreach ( $data as $team_id => $result ) :
$outcomes = array(); $outcomes = array();
$result_outcome = sp_array_value( $result, 'outcome' );
if ( ! is_array( $result_outcome ) ) : foreach ( $result_outcome as $outcome ) {
$outcomes = array( '&mdash;' );
else :
foreach ( $result_outcome as $outcome ) :
$the_outcome = get_page_by_path( $outcome, OBJECT, 'sp_outcome' ); $the_outcome = get_page_by_path( $outcome, OBJECT, 'sp_outcome' );
if ( is_object( $the_outcome ) ) :
$outcomes[] = $the_outcome->post_title;
endif;
endforeach;
endif;
unset( $result['outcome'] );
$team_name = sp_team_short_name( $team_id );
$team_abbreviation = sp_team_abbreviation( $team_id );
if ( $the_outcome instanceof WP_Post ) {
$outcome_abbreviation = get_post_meta( $the_outcome->ID, 'sp_abbreviation', true ); $outcome_abbreviation = get_post_meta( $the_outcome->ID, 'sp_abbreviation', true );
if ( ! $outcome_abbreviation ) { if ( ! $outcome_abbreviation ) {
$outcome_abbreviation = sp_substr( $the_outcome->post_title, 0, 1 ); $outcome_abbreviation = function_exists( 'sp_substr' ) ? sp_substr( $the_outcome->post_title, 0, 1 ) : substr( $the_outcome->post_title, 0, 1 );
} }
array_push($teams_result_array, [ $outcomes[] = array(
"result" => $result, 'title' => $the_outcome->post_title,
"outcome" => $the_outcome->post_title, 'abbreviation' => $outcome_abbreviation,
"outcome_abbreviation" => $outcome_abbreviation,
"team_name" => $team_name,
"team_abbreviation" => $team_abbreviation
]
); );
$i++; }
endforeach; }
$publish_date = asc_generate_short_date($post, false);
return $outcomes;
}
/**
* Build a result title/description from SportsPress result data.
*
* @param WP_Post $post Event post.
* @param array $results Event results data.
* @param string $description Existing description.
* @return array{title:string,description:string}|null
*/
function asc_sp_event_result_meta( WP_Post $post, array $results, $description ) {
unset( $results[0] );
$results = array_filter( $results );
if ( count( $results ) < 2 ) {
return null;
}
if ( 'yes' === get_option( 'sportspress_event_reverse_teams', 'no' ) ) {
$results = array_reverse( $results, true );
}
$teams_result_array = array();
foreach ( $results as $team_id => $result ) {
if ( ! is_array( $result ) ) {
continue;
}
$outcomes = asc_sp_event_result_outcomes( $result );
$first_outcome = ! empty( $outcomes ) ? $outcomes[0] : array( 'title' => __( 'Result', 'tonys-sportspress-enhancements' ), 'abbreviation' => '' );
$team_name = asc_sp_team_short_name( $team_id );
$team_score = isset( $result['r'] ) && '' !== $result['r'] ? $result['r'] : null;
$team_score = null !== $team_score ? (string) $team_score : '';
if ( '' === $team_score ) {
continue;
}
$teams_result_array[] = array(
'score' => $team_score,
'outcome' => $first_outcome['title'],
'outcome_abbreviation' => $first_outcome['abbreviation'],
'team_name' => $team_name,
);
}
if ( count( $teams_result_array ) < 2 ) {
return null;
}
$special_result_suffix_abbreviation = ''; $special_result_suffix_abbreviation = '';
$special_result_suffix = ''; $special_result_suffix = '';
foreach ( $teams_result_array as $team ) { foreach ( $teams_result_array as $team ) {
$outcome_abbreviation = strtoupper( $team['outcome_abbreviation'] ); // Normalize case $outcome_abbreviation = strtoupper( (string) $team['outcome_abbreviation'] );
if ( $outcome_abbreviation === 'TF-W' ) { if ( 'TF-W' === $outcome_abbreviation ) {
$special_result_suffix_abbreviation = 'TF-W'; $special_result_suffix_abbreviation = 'TF-W';
$special_result_suffix = 'Technical Forfeit Win'; $special_result_suffix = 'Technical Forfeit Win';
break; break;
} elseif ( $outcome_abbreviation === 'TF-L' ) { }
if ( 'TF-L' === $outcome_abbreviation ) {
$special_result_suffix_abbreviation = 'TF'; $special_result_suffix_abbreviation = 'TF';
$special_result_suffix = 'Technical Forfeit'; $special_result_suffix = 'Technical Forfeit';
break; break;
} elseif ( $outcome_abbreviation === 'F-W' || $outcome_abbreviation === 'F-L' ) { }
if ( 'F-W' === $outcome_abbreviation || 'F-L' === $outcome_abbreviation ) {
$special_result_suffix_abbreviation = 'Forfeit'; $special_result_suffix_abbreviation = 'Forfeit';
$special_result_suffix = 'Forfeit'; $special_result_suffix = 'Forfeit';
break; break;
} }
} }
$title = "{$teams_result_array[0]['team_name']} {$teams_result_array[0]['result']['r']}-{$teams_result_array[1]['result']['r']} {$teams_result_array[1]['team_name']}{$publish_date}" . ($special_result_suffix ? "({$special_result_suffix_abbreviation})" : ""); $publish_date = asc_generate_short_date( $post, false );
$description .= " " . "{$teams_result_array[0]['team_name']} ({$teams_result_array[0]['outcome']}), {$teams_result_array[1]['team_name']} ({$teams_result_array[1]['outcome']})." ; $title = sprintf(
'%1$s %2$s-%3$s %4$s%s',
$teams_result_array[0]['team_name'],
$teams_result_array[0]['score'],
$teams_result_array[1]['score'],
$teams_result_array[1]['team_name'],
$publish_date ? ' - ' . $publish_date : ''
);
if ( $special_result_suffix ) {
$title .= " ({$special_result_suffix_abbreviation})";
} }
$description .= " " . $post->post_content;
$image = asc_sp_event_matchup_image_url( $post ); $description .= sprintf(
echo '<meta property="og:type" content="article" />' . "\n"; ' %1$s (%2$s), %3$s (%4$s).',
echo '<meta property="og:image" content="'. $image . '" />' . "\n"; $teams_result_array[0]['team_name'],
echo '<meta property="og:title" content="' . $title . '" />' . "\n"; $teams_result_array[0]['outcome'],
echo '<meta property="og:description" content="' . $description . '" />' . "\n"; $teams_result_array[1]['team_name'],
echo '<meta property="og:url" content="' . get_permalink() . '" />' . "\n"; $teams_result_array[1]['outcome']
);
return array(
'title' => $title,
'description' => $description,
);
}
/**
* Build all Open Graph values for an event.
*
* @param int|WP_Post $post Event post or ID.
* @return array<string,mixed>
*/
function asc_sp_event_open_graph_data( $post ) {
$post = asc_sp_event_get_post( $post );
if ( ! $post ) {
return array();
}
$event = asc_sp_event_object( $post );
$venue_name = asc_sp_event_venue_name( $post );
$publish_date_and_time = get_the_date( 'F j, Y g:i A', $post );
$description = trim( "{$publish_date_and_time} at {$venue_name}." );
$title = asc_generate_sp_event_title( $post );
$sp_status = get_post_meta( $post->ID, 'sp_status', true );
$status = asc_sp_event_status( $post, $event );
if ( in_array( $sp_status, array( 'postponed', 'cancelled', 'tbd' ), true ) ) {
$status_label = strtoupper( $sp_status );
$description = "{$status_label} - {$description}";
$title = trim( "{$status_label} - {$title} - " . asc_generate_short_date( $post ) . " - {$venue_name}", ' -' );
} elseif ( 'future' === $status ) {
$title = trim( $title . ' - ' . asc_generate_short_date( $post ) . " - {$venue_name}", ' -' );
} elseif ( 'results' === $status ) {
$result_meta = asc_sp_event_result_meta( $post, asc_sp_event_results( $event ), $description );
if ( $result_meta ) {
$title = $result_meta['title'];
$description = $result_meta['description'];
} }
} }
$body_excerpt = asc_sp_event_body_excerpt( $post );
if ( '' !== $body_excerpt ) {
$description = trim( $description . ' ' . $body_excerpt );
}
return array(
'type' => 'article',
'images' => asc_sp_event_open_graph_images( $post ),
'image' => asc_sp_event_matchup_image_url( $post, 'wide' ),
'image_width' => '1200',
'image_height' => '628',
'title' => $title,
'description' => $description,
'url' => get_permalink( $post ),
);
}
/**
* Echo Open Graph meta tags for single SportsPress events.
*/
function custom_open_graph_tags_with_sportspress_integration() {
if ( ! is_single() ) {
return;
}
$post = asc_sp_event_get_post();
if ( ! $post ) {
return;
}
$meta = asc_sp_event_open_graph_data( $post );
if ( empty( $meta ) ) {
return;
}
echo '<meta property="og:type" content="' . esc_attr( $meta['type'] ) . '" />' . "\n";
foreach ( $meta['images'] as $image ) {
echo '<meta property="og:image" content="' . esc_url( $image['url'] ) . '" />' . "\n";
echo '<meta property="og:image:width" content="' . esc_attr( $image['width'] ) . '" />' . "\n";
echo '<meta property="og:image:height" content="' . esc_attr( $image['height'] ) . '" />' . "\n";
}
echo '<meta property="og:title" content="' . esc_attr( $meta['title'] ) . '" />' . "\n";
echo '<meta property="og:description" content="' . esc_attr( $meta['description'] ) . '" />' . "\n";
echo '<meta property="og:url" content="' . esc_url( $meta['url'] ) . '" />' . "\n";
} }
?>

View File

@@ -0,0 +1,222 @@
<?php
/**
* Tests for the SportsPress event image generator.
*
* @package Tonys_Sportspress_Enhancements
*/
/**
* Featured image generator tests.
*/
class Test_Featured_Image_Generator extends WP_UnitTestCase {
/**
* Temp files created during a test.
*
* @var string[]
*/
private $temp_files = array();
/**
* Clean up temp files.
*/
public function tear_down(): void {
foreach ( $this->temp_files as $file ) {
if ( file_exists( $file ) ) {
unlink( $file );
}
}
$this->temp_files = array();
parent::tear_down();
}
/**
* Create a post.
*
* @param string $type Post type.
* @param string $title Title.
* @return int
*/
private function create_post_of_type( $type, $title ) {
return self::factory()->post->create(
array(
'post_type' => $type,
'post_title' => $title,
'post_status' => 'publish',
)
);
}
/**
* Create a small raster fixture.
*
* @param string $extension File extension.
* @return string
*/
private function create_raster_fixture( $extension ) {
$image = imagecreatetruecolor( 24, 24 );
$red = imagecolorallocate( $image, 200, 0, 0 );
imagefilledrectangle( $image, 0, 0, 23, 23, $red );
$file = tempnam( sys_get_temp_dir(), 'sp-img-' );
$path = $file . '.' . $extension;
rename( $file, $path );
switch ( $extension ) {
case 'jpg':
imagejpeg( $image, $path );
break;
case 'gif':
imagegif( $image, $path );
break;
case 'webp':
imagewebp( $image, $path );
break;
case 'png':
default:
imagepng( $image, $path );
break;
}
asc_sp_event_image_destroy( $image );
$this->temp_files[] = $path;
return $path;
}
/**
* Invalid IDs and non-event posts produce request errors.
*/
public function test_invalid_and_non_event_requests_prepare_404_errors() {
$this->assertWPError( asc_sp_event_prepare_image_request( 999999 ) );
$post_id = $this->create_post_of_type( 'post', 'Regular Post' );
$error = asc_sp_event_prepare_image_request( $post_id );
$this->assertWPError( $error );
$this->assertSame( 'invalid_event', $error->get_error_code() );
}
/**
* Missing team logo paths fall back to generated text and valid dimensions.
*/
public function test_missing_logo_path_generates_png_with_expected_dimensions() {
$image_data = generate_bisected_image( '#123456', '#abcdef', '/missing-left.png', '/missing-right.png', 'Hawks', 'Electrons' );
$image = imagecreatefromstring( $image_data );
$this->assertNotFalse( $image );
$this->assertSame( 1200, imagesx( $image ) );
$this->assertSame( 628, imagesy( $image ) );
asc_sp_event_image_destroy( $image );
}
/**
* Square image variant generates square PNG dimensions.
*/
public function test_square_variant_generates_expected_dimensions() {
$dimensions = asc_sp_event_image_variant_dimensions( 'square' );
$image_data = generate_bisected_image( '#123456', '#abcdef', '/missing-left.png', '/missing-right.png', 'Hawks', 'Electrons', $dimensions['width'], $dimensions['height'] );
$image = imagecreatefromstring( $image_data );
$this->assertNotFalse( $image );
$this->assertSame( 1200, imagesx( $image ) );
$this->assertSame( 1200, imagesy( $image ) );
asc_sp_event_image_destroy( $image );
}
/**
* Raster loader supports common GD-backed formats.
*/
public function test_raster_loader_supports_common_formats_when_available() {
$formats = array(
'png' => 'imagecreatefrompng',
'jpg' => 'imagecreatefromjpeg',
'gif' => 'imagecreatefromgif',
);
if ( function_exists( 'imagewebp' ) && function_exists( 'imagecreatefromwebp' ) ) {
$formats['webp'] = 'imagecreatefromwebp';
}
foreach ( $formats as $extension => $function ) {
if ( ! function_exists( $function ) ) {
continue;
}
$path = $this->create_raster_fixture( $extension );
$image = asc_sp_event_image_create_from_file( $path );
$this->assertNotFalse( $image, "Failed loading {$extension}" );
$this->assertSame( 24, imagesx( $image ) );
$this->assertSame( 24, imagesy( $image ) );
asc_sp_event_image_destroy( $image );
}
}
/**
* Bundled sporty font is available for fallback text.
*/
public function test_bundled_bebas_neue_font_is_available() {
$this->assertFileExists( asc_sp_event_image_font_path() );
$this->assertIsReadable( asc_sp_event_image_font_path() );
}
/**
* Prepared event request includes fallback text for missing logos.
*/
public function test_prepare_image_request_uses_team_short_name_fallbacks() {
$team1 = $this->create_post_of_type( 'sp_team', 'Hawks' );
$team2 = $this->create_post_of_type( 'sp_team', 'Electrons' );
$event = $this->create_post_of_type( 'sp_event', 'Hawks vs Electrons' );
add_post_meta( $event, 'sp_team', $team1 );
add_post_meta( $event, 'sp_team', $team2 );
$request = asc_sp_event_prepare_image_request( $event );
$this->assertIsArray( $request );
$this->assertSame( 'Hawks', $request['team1_fallback'] );
$this->assertSame( 'Electrons', $request['team2_fallback'] );
$this->assertSame( '', $request['team1_logo'] );
$this->assertSame( '', $request['team2_logo'] );
}
/**
* Invalid colors are safely normalized.
*/
public function test_invalid_colors_fall_back_to_configured_defaults() {
$this->assertSame( '#4B5563', asc_sp_event_image_color( 'not-a-color' ) );
$this->assertSame( '#6B7280', asc_sp_event_image_color( 'not-a-color', '#6B7280' ) );
$this->assertSame( '#112233', asc_sp_event_image_color( '#112233' ) );
}
/**
* Image cache keys include the generator version and style hash.
*/
public function test_prepare_image_request_uses_versioned_style_cache_key() {
$team1 = $this->create_post_of_type( 'sp_team', 'Hawks' );
$team2 = $this->create_post_of_type( 'sp_team', 'Electrons' );
$event = $this->create_post_of_type( 'sp_event', 'Hawks vs Electrons' );
add_post_meta( $event, 'sp_team', $team1 );
add_post_meta( $event, 'sp_team', $team2 );
$request = asc_sp_event_prepare_image_request( $event );
$this->assertStringStartsWith( 'team_image_v' . ASC_SP_EVENT_IMAGE_CACHE_VERSION . '_' . asc_sp_event_image_cache_style_hash(), $request['cache_key'] );
$this->assertSame( 'wide', $request['variant'] );
$this->assertSame( 1200, $request['width'] );
$this->assertSame( 628, $request['height'] );
$square_request = asc_sp_event_prepare_image_request( $event, 'square' );
$this->assertStringContainsString( '_square_', $square_request['cache_key'] );
$this->assertSame( 'square', $square_request['variant'] );
$this->assertSame( 1200, $square_request['width'] );
$this->assertSame( 1200, $square_request['height'] );
}
}

View File

@@ -0,0 +1,271 @@
<?php
/**
* Tests for SportsPress event Open Graph output.
*
* @package Tonys_Sportspress_Enhancements
*/
if ( ! class_exists( 'SP_Event' ) ) {
/**
* Minimal SportsPress event test double.
*/
class SP_Event {
/**
* Event post ID.
*
* @var int
*/
private $id;
/**
* Status values by event ID.
*
* @var array<int,string>
*/
public static $statuses = array();
/**
* Result values by event ID.
*
* @var array<int,array>
*/
public static $results = array();
/**
* Constructor.
*
* @param int $id Event post ID.
*/
public function __construct( $id ) {
$this->id = absint( $id );
}
/**
* Get event status.
*
* @return string
*/
public function status() {
return self::$statuses[ $this->id ] ?? '';
}
/**
* Get event results.
*
* @return array
*/
public function results() {
return self::$results[ $this->id ] ?? array();
}
}
}
/**
* Open Graph tests.
*/
class Test_Open_Graph_Tags extends WP_UnitTestCase {
/**
* Reset mock SportsPress state.
*/
public function set_up(): void {
parent::set_up();
if ( property_exists( 'SP_Event', 'statuses' ) ) {
SP_Event::$statuses = array();
SP_Event::$results = array();
}
update_option( 'sportspress_event_reverse_teams', 'no' );
update_option( 'sportspress_event_teams_delimiter', 'vs' );
}
/**
* Create a team.
*
* @param string $name Team name.
* @return int
*/
private function create_team( $name ) {
return self::factory()->post->create(
array(
'post_type' => 'sp_team',
'post_title' => $name,
)
);
}
/**
* Create an event.
*
* @param array $args Post args.
* @return int
*/
private function create_event( array $args = array() ) {
return self::factory()->post->create(
wp_parse_args(
$args,
array(
'post_type' => 'sp_event',
'post_title' => 'Test Event',
'post_status' => 'future',
'post_date' => '2026-05-02 13:00:00',
'post_content' => 'First pitch at one.',
)
)
);
}
/**
* Future event emits complete Open Graph data.
*/
public function test_future_event_emits_core_open_graph_values() {
$home = $this->create_team( 'Hawks' );
$away = $this->create_team( 'Electrons' );
$event = $this->create_event();
add_post_meta( $event, 'sp_team', $home );
add_post_meta( $event, 'sp_team', $away );
if ( property_exists( 'SP_Event', 'statuses' ) ) {
SP_Event::$statuses[ $event ] = 'future';
}
$meta = asc_sp_event_open_graph_data( $event );
$this->assertSame( 'article', $meta['type'] );
$this->assertStringContainsString( 'Hawks vs Electrons', $meta['title'] );
$this->assertStringContainsString( 'First pitch at one.', $meta['description'] );
$this->assertCount( 2, $meta['images'] );
$this->assertSame( '1200', $meta['images'][0]['width'] );
$this->assertSame( '628', $meta['images'][0]['height'] );
$this->assertSame( '1200', $meta['images'][1]['width'] );
$this->assertSame( '1200', $meta['images'][1]['height'] );
$this->assertSame( '1200', $meta['image_width'] );
$this->assertSame( '628', $meta['image_height'] );
$this->assertStringContainsString( '/head-to-head?post=' . $event, $meta['image'] );
$this->assertStringContainsString( 'variant=square', $meta['images'][1]['url'] );
$this->assertNotEmpty( $meta['url'] );
}
/**
* Postponed, cancelled, and TBD labels appear in title and description.
*
* @dataProvider status_provider
*
* @param string $status Status slug.
*/
public function test_schedule_status_appears_in_title_and_description( $status ) {
$home = $this->create_team( 'Hawks' );
$away = $this->create_team( 'Electrons' );
$event = $this->create_event();
add_post_meta( $event, 'sp_team', $home );
add_post_meta( $event, 'sp_team', $away );
update_post_meta( $event, 'sp_status', $status );
$meta = asc_sp_event_open_graph_data( $event );
$label = strtoupper( $status );
$this->assertStringStartsWith( $label, $meta['title'] );
$this->assertStringStartsWith( $label, $meta['description'] );
}
/**
* Status provider.
*
* @return array
*/
public function status_provider() {
return array(
array( 'postponed' ),
array( 'cancelled' ),
array( 'tbd' ),
);
}
/**
* Result events with scores emit score titles.
*/
public function test_result_event_with_scores_emits_score_title() {
$home = $this->create_team( 'Hawks' );
$away = $this->create_team( 'Electrons' );
$event = $this->create_event( array( 'post_status' => 'publish' ) );
add_post_meta( $event, 'sp_team', $home );
add_post_meta( $event, 'sp_team', $away );
if ( property_exists( 'SP_Event', 'statuses' ) ) {
SP_Event::$statuses[ $event ] = 'results';
SP_Event::$results[ $event ] = array(
0 => array( 'r' => 'R' ),
$home => array( 'r' => '7' ),
$away => array( 'r' => '4' ),
);
}
$meta = asc_sp_event_open_graph_data( $event );
$this->assertStringContainsString( 'Hawks 7-4 Electrons', $meta['title'] );
}
/**
* Missing teams/results/outcomes still produce valid data.
*/
public function test_missing_sportspress_data_does_not_break_meta_generation() {
$event = $this->create_event(
array(
'post_title' => 'Sparse Event',
'post_content' => '',
)
);
if ( property_exists( 'SP_Event', 'statuses' ) ) {
SP_Event::$statuses[ $event ] = 'results';
SP_Event::$results[ $event ] = array();
}
$meta = asc_sp_event_open_graph_data( $event );
$this->assertSame( 'Sparse Event', $meta['title'] );
$this->assertNotEmpty( $meta['description'] );
$this->assertSame( '1200', $meta['image_width'] );
}
/**
* HTML-heavy post content is stripped and escaped in rendered tags.
*/
public function test_description_strips_html_and_rendered_tags_are_escaped() {
$home = $this->create_team( 'Hawks "A"' );
$away = $this->create_team( 'Electrons <B>' );
$event = $this->create_event(
array(
'post_content' => '<script>alert("x")</script><p>Bring <strong>bats</strong> & gloves.</p>',
)
);
add_post_meta( $event, 'sp_team', $home );
add_post_meta( $event, 'sp_team', $away );
$meta = asc_sp_event_open_graph_data( $event );
$this->assertStringNotContainsString( '<script', $meta['description'] );
$this->assertStringContainsString( 'Bring bats & gloves.', $meta['description'] );
$GLOBALS['post'] = get_post( $event );
$GLOBALS['wp_query']->is_single = true;
ob_start();
custom_open_graph_tags_with_sportspress_integration();
$output = ob_get_clean();
$this->assertStringContainsString( 'og:image:width', $output );
$this->assertSame( 2, substr_count( $output, 'property="og:image" content=' ) );
$this->assertStringContainsString( 'content="628"', $output );
$this->assertStringContainsString( 'content="1200"', $output );
$this->assertStringContainsString( 'variant=square', $output );
$this->assertStringContainsString( 'Hawks &quot;A&quot;', $output );
$this->assertStringNotContainsString( '<B>', $output );
$this->assertStringNotContainsString( '<script', $output );
}
}