# RDG Corp / SH Games Plugin for WordPress

Tags: sh games, sh, game
Requires at least: 6.0
Tested up to: 6.0
Requires PHP: 8.1
Stable tag: 1.1.55
License: GPLv3 or later
License URI: http://www.gnu.org/licenses/gpl-3.0.html

# Changelog
-   1.1.55 (2026-07-16)
  * DEV-1285-sh-games-escape-block-templates-output
    - Security hardening (stored XSS): escaped all dynamic output in the 5 block templates (`shg-game`, `shg-game-top-handicapper`, `shg-game-picks-game`, `shg-game-odds`, `shg-game-links`). Data coming from the external API and post meta was previously echoed raw, so HTML/JS injected into those fields (via a compromised feed URL or by a low-privilege editor writing to the custom fields, which bypass kses) executed in every visitor's browser. Escaping is context-aware: `esc_html()` for text nodes (team names, scores, odds, handicapper names, titles), `esc_url()` for `src`/`href`, `esc_attr()` for attribute values, and `wp_kses_post()` for `shg_game_top_handicapper_description` — the only field that legitimately carries HTML markup from the feed, which keeps rendering its content tags while stripping scripts/event handlers. No markup or logic changes; rendering is visually identical for legitimate data. Version 1.1.54 is taken by DEV-1284 (in review), hence 1.1.55.

-   1.1.54 (2026-07-16)
  * DEV-1284-sh-games-delete-expired-previews-301-redirect
    - Added `wp sportshub_games cleanup` CLI command: permanently deletes (bypassing trash, since trashed posts still block re-inserts via `isTitleDuplicate`) published game preview posts older than `shg_cleanup_hours` hours (default 48) whose `shg_game_date` is already in the past. WPML translations of each deleted post are deleted along with it. Attachments are never deleted (team logos are shared across posts). Supports `--dry-run` and `--limit=<n>` (default 200 per run). Uses the same transient lock pattern as `pull` (`shg_cleanup_running`, 30 min TTL). The game-date condition prevents a delete/re-insert loop: the pull only fetches games with `date >= today`, so deleting a post whose game has not been played yet would just get re-created (with a new URL) on the next pull.
    - Backlog protection: only posts published on or after `shg_cleanup_start_date` are eligible. That option is stamped automatically the first time the feature is enabled, so pre-existing posts are never touched.
    - Added `sportshub_cleanup_cron_event` WP-cron event (every 3 hours) that shells out to the CLI command, mirroring the existing pull cron pattern. Scheduled/cleared from the Sports Settings save handler and cleared on plugin deactivation.
    - Added two fields to Sports Settings: "Auto-delete expired games" (Inactive/Active kill-switch, default Inactive — saved to `shg_cleanup_enabled`) and "Delete after (hours)" (default 48 — saved to `shg_cleanup_hours`).
    - Added pattern-based 301 redirect on `template_redirect`: any 404 under the game CPT base path redirects to its parent path (one segment up). Because the CPT is hierarchical, a deleted preview URL (e.g. `/previews/baseball/mlb/team-a-vs-team-b-123/`) lands on its sport container page (`/previews/baseball/mlb/`). No per-post redirect rules are stored, so this retroactively covers posts deleted manually or before this feature, and handles WPML language prefixes (`/es/...`). The redirect is always active (independent of the cleanup kill-switch).

-   1.1.53 (2026-04-29)
  * previews-archive-test
    - Errata for 1.1.51: testing if deleting the archive-previews.php we are able to use the elements design.

-   1.1.52 (2026-04-29)
  * DEV-1205-sh-games-fix-header-version
    - Errata for 1.1.51: bumped the plugin file header in `sportshub-games.php` (`@version` and `Version:`) from `1.1.50` to `1.1.52` so the version reported by WordPress in `wp-admin/plugins.php` matches the published package. The 1.1.51 release updated the README/changelog and shipped the new code (CPT archive migration) but missed updating the PHP header, so production correctly received the 1.1.51 code yet `plugins.php` kept showing `1.1.50`. No functional changes — only the version number string.
-   1.1.51 (2026-04-29)
  * DEV-1205-sh-games-replace-previews-static-page-with-cpt-archive
    - Removed the two `add_rewrite_rule` calls in `add_custom_post_type()` that hijacked `/<shg_slug>/` and `/<shg_slug>/page/N/` to `pagename=<shg_slug>`, forcing the URL to be served by a static WordPress page instead of the registered CPT archive. With these rules gone, WordPress falls back to the native CPT archive (`has_archive => true` is already set on `register_post_type()`), so `/previews/` is now an archive context and `/previews/page/N/` paginates natively — fixing the broken Gutenberg query-loop pagination (`?query-XXXX-page=N`) and the SEO/canonical/OG issues that came from rendering the URL as a singular page.
    - Added an `archive_template` filter (`shg_load_previews_archive_template`) that serves a plugin-bundled archive template for the previews CPT when the active theme does not provide one. The filter is theme-first: it calls `locate_template(['archive-<shg_slug>.php'])` and returns the theme override if found; otherwise it falls back to `SHGAMES_PLUGIN_DIR . 'templates/archive-<shg_slug>.php'`. This guarantees a working archive out of the box without coupling the plugin to any specific theme.
    - Added `templates/archive-previews.php`: a minimal archive template (`get_header()` + standard loop calling `the_content()` per post + `the_posts_pagination()` + `get_footer()`). No layout/design is hard-coded — each preview's block content is rendered through `the_content()` so editors keep full control via Gutenberg.
    - Production rollout requires trashing the existing static `previews` page (`wp_trash_post`) and running `wp rewrite flush --hard` so the old hijacking rules are dropped from the rewrite cache and the native CPT archive route takes effect.
-   1.1.50 (2026-04-20)
  * DEV-1147-sh-games-check-previews-updatings
    - Made the cross-post logo sweep position-agnostic and able to use logos from past games. A team's logo URL is identical regardless of home/visitor role, so each `team_id` now triggers a sweep across BOTH `home` and `visitor` positions instead of only the position the team played in the current preview. Added `sweepTeamLogoBothPositions()` wrapper for clarity. Additionally, the past-game filter (1.1.46) was extended: even when a game is skipped because its `date < today`, its team logo URLs are still harvested and fed into the sweep, so legacy posts where a team appears in any position can recover their logos from API responses that include past games (the API frequently returns these as documented in 1.1.46). This is what unblocks legacy posts like 400858 (Toronto-Raptors as visitor) where the team has no future game scheduled but appears in the API's past-game payload.
-   1.1.49 (2026-04-20)
  * DEV-1147-sh-games-check-previews-updatings
    - Added cross-post team logo sweep in `backfillTeamLogos()`: when the pull receives a real (non-placeholder) logo URL for a `team_id` from a future game, all past-game preview posts where the same `team_id` appears in the same position (`home` or `visitor`) and still has a `default_picture` placeholder are updated with the real URL. This is needed because the pull only fetches games with `date >= today` (1.1.46), so legacy posts of past games would never recover their logos otherwise. Implemented as `sweepTeamLogoAcrossPosts($team_id, $real_url, $position)` with a static request-scoped cache (`$sweptTeamPositions`) that prevents repeated work when the same team appears in multiple games during a single pull. Lookup is one indexed SQL query per (team_id, position) pair joining `wp_postmeta` twice (`shg_id_*_team` + `shg_*_image LIKE '%default_picture%'`); the actual image download via `uploadImage()` reuses the existing attachment when the file is already in the media library.
-   1.1.48 (2026-04-19)
  * DEV-1147-sh-games-check-previews-updatings
    - Fixed `backfillTeamLogos()` to detect and replace `default_picture.jpg` placeholder URLs that the API returns when a team's logo is not yet available. Previously the backfill only triggered when `shg_home_image`/`shg_visitor_image`/`shg_home_local_url`/`shg_visitor_local_url` were empty, so posts created during the API's "no logo yet" window kept the placeholder forever (e.g. post 400858 Cleveland Cavaliers vs Toronto-Raptors had `shg_visitor_image=default_picture.jpg` even after the API received the real Toronto-Raptors logo). Added private `isPlaceholderOrEmpty()` helper that treats URLs containing `default_picture` as empty for backfill purposes, and prevents overwriting real URLs with placeholders. Affects published posts on subsequent pulls when the API has obtained the real logo.
-   1.1.47 (2026-04-16)
  * DEV-1147-sh-games-check-previews-updatings
    - Added `noindex` meta for old game preview posts: posts with more than 7 hours since publication are marked as `noindex` to prevent search engines from indexing stale game content. Implemented via `rank_math/frontend/robots` filter for sites using Rank Math, with a `wp_head` fallback that outputs a raw `<meta name="robots" content="noindex, follow">` tag when Rank Math is not active. Both hooks registered in `add_hooks()`.
-   1.1.46 (2026-04-15)
  * DEV-1147-sh-games-check-previews-updatings
    - Added transient-based lock (`shg_pull_running`, 30 min TTL) at the entry point of `Sportshub_Games_CLI::pull()` to prevent concurrent cron + manual executions from creating duplicate posts. On 2026-04-15 this race produced 46 external_ids with duplicates (some with 9 copies) before being cleaned up manually. Lock release is handled via `register_shutdown_function` so it fires on normal return, exceptions, and fatal errors; the TTL is a safety net if the process is killed before the shutdown handler runs.
    - Added past-game filter in the Members source pull loop: games whose `date` is strictly before today (server local time) are skipped. The `previews-v2` API occasionally returns historical games (observed: January 2026 and August 2025 games returned on 2026-04-15) even when `start_date=today` is requested. Filtering client-side prevents creating new posts with today's `post_date` for games that already happened.
-   1.1.45 (2026-04-14)
  * DEV-1147-sh-games-check-previews-updatings
-   1.1.44 (2026-04-14)
  * DEV-1147-sh-games-check-previews-updatings
    - Enhanced `backfillTeamLogos()` and `updateBlockGameData()` to also backfill missing meta fields (`shg_home_team`, `shg_visitor_team`, `shg_game_date`, `shg_sport_name`, etc.), `post_parent`, and `previews_categories` taxonomy on existing posts with empty values
-   1.1.43 (2026-04-14)
  * DEV-1147-sh-games-check-previews-updatings
    - Removed `leagues` parameter from previews-v2 API requests; the API returns all available previews for the given sports without league filtering, avoiding mismatched or negative league IDs that caused empty responses
    - Fixed `post_parent` assignment: preserved original `shg_sports_main_games` mapping before the sh-mapping conversion and passed the correct parent post ID through each request, so previews are linked to the right parent (e.g. NHL, NBA, MLB)
    - Added taxonomy inheritance: new posts now inherit `previews_categories` from their parent post on insert, ensuring they appear on the correct sport/league archive pages
    - Added null-safety checks on image upload calls (`shg_home_image`, `shg_visitor_image`, pick images) to prevent warnings when the API returns empty image fields
    - Added `backfillTeamLogos()` method to populate missing team logo meta on published posts without overwriting other content
-   1.1.42 (2026-03-16)
  * DEV-1073-sh-games-new-endpoint-support-team-logo-field
     - Added support for `previews-v2` endpoint with updated JSON schema
     - Replaced `away_team` references with `visitor_team` (backward-compatible fallback to `away_team`)
     - Normalized `recap` field: supports both v2 string format and v1 array `[{body: "..."}]`
     - Added `parse_odds_data_v2()` method for flat odds object format (v2); auto-detects v1 vs v2 at runtime
     - Added `id_sport` fallback from `picks[0].id_sport` when missing at game level
     - Lowered `picks_game` field threshold from 7 to 4 to accommodate reduced pick fields in v2
-   1.1.37 (2025-11-26)
  * DEV-937-sh-game-handle-empty-request
     - Fixed undefined array key warning for 'shg_game_top_handicapper_api_image' in class-sportshub-games-cli.php
     - Fixed deprecated explode() warning when passing null in getNameFile() in class-sportshub-games-helpers.php
     - Added validation checks before accessing potentially missing array keys
-   1.1.36 (2025-11-26)
  * DEV-937-sh-game-handle-empty-request
     - Fixed exe_request() to validate HTTP status codes, empty responses, and proper cURL error handling
-   1.1.35 (2025-11-26)
  * DEV-937-sh-game-handle-empty-request
     - Fixed exe_request() to validate HTTP status codes, empty responses, and proper cURL error handling
-   1.1.34 (2025-11-03)
  * DEV-881-sas-change-previews-top-bar-to-h
     - Change top bar links block to hide as default.
-   1.1.33 (2025-08-21)
  * DEV-747-sh-games-php-warnings-due-to-undefined-array
      - Check if the key exists.
-   1.1.31 (2025-05-28)
  * DEV-684-new-endpoint-for-previews-creation.
      - The location from which the logs file folder is created is modified.
-   1.1.30 (2025-04-28)
  * DEV-629-multiple-requests-in-sh-game.
      - Multiple requests are made when executing the command to create psots games.
-   1.1.29 (2025-03-19)
  * DEV-588-sh-games-game-url-undefined-warning.
      - Check for the existence of a variable game url.
-   1.1.28 (2025-03-07)
  * DEV-558-sh-games-fix-cls-issue-remove-link-in-empty-url.
      - Fix picks count link when game url is empty.
-   1.1.27 (2025-03-05)
  * DEV-558-sh-games-fix-cls-issue.
      - Load styles in header, display picks count.
-   1.1.26 (2025-02-19)
  * DEV-529-performance-assets-on-demand.
      - Loading of scripts and styles is optimized.
-   1.1.25 (2024-11-18)
  * DEV-469-implement-new-endpoint
      - New endpoint previews implemented.
-   1.1.24 (2024-11-18)
  * DEV-462-create-block-picks-games
      - shg picks game block is finished.
-   1.1.23 (2024-11-07)
  * DEV-461-feature-to-validate-content-in-posts
      - Allows you to enable or disable duplicate content validation.
-   1.1.22 (2024-10-30)
  * DEV-450-update-additional-content-in-games
      - Additional content in posts is validated
-   1.1.21 (2024-10-29)
  * DEV-456-safari-issue-on-mobile
      - Top handicapper block css updated
-   1.1.20 (2024-10-29)
  * DEV-455-handicapper-image-change
      - Added alt to cpt games images
-   1.1.19 (2024-10-15)
  * DEV-441-add-knup-preview
      - Knup content is added to posts
-   1.1.18 (2024-09-30)
  * DEV-434-rewrite-rules
      - Rules are overwritten in the cpt game.
-   1.1.17 (2024-09-23)
  * DEV-405-previews-hub-page-design
      - The sportshubGamesGetParentsGames method was created to return the post configuration.
-   1.1.16 (2024-09-06)
  * DEV-397-sh-games-prevent-duplicates
      - Prevent post duplication by checking the title and content.
-   1.1.15 (2024-09-05)
  * DEV-404-sh-games-custom-date-range
    - Add days range input in sh-games settings, update endpoint to request based on the range of date.
-   1.1.14 (2024-07-23)
  * DEV-354-previews-categories-and-tags
    - Fixed post status on game preview update.
-   1.1.13 (2024-05-23)
    * DEV-315-sh-games-fix-game-preview-status
        - Fixed post status on game preview update.
-   1.1.12 (2024-05-10)
    * DEV-283-sh-games-changes-on-logic
        - Changes on logic.
-   1.1.11 (2024-05-03)
    * DEV-275-fix-insert-auto-post
        - Insert auto post fixed.
-   1.1.10 (2024-04-10)
    * DEV-270-fix-cron-job-options
        - SH Games options form fixed.
-   1.1.9 (2024-04-10)
    * DEV-265-repair-access-to-manifest-file
        - Manifest file is accessed without http protocol.
-   1.1.8 (2024-04-04)
    * DEV-261-show-or-hide-sh-links-block
        - It is allowed to show or hide the sh game links block.
-   1.1.7 (2024-04-03)
    * DEV-259-assign-image-to-game
        - Featured preview image is assigned if it exists.
-   1.1.6 (2024-03-20)
    * DEV-103-sh-games-plugin-complete
        - The origin of sports and leagues was adjusted to mapping.
-   1.1.5 (2024-03-19)
    * DEV-103-sh-games-plugin-complete
        - Rankmath variables.
        - Custom game status.
-   1.1.4 (2024-03-12)
    * DEV-103-sh-games-plugin-complete
        - Added featured image and other changes.
-   1.1.3 (2024-03-12)
    * DEV-103-sh-games-plugin-complete
        - Title shadow color is customized.
-   1.1.2 (2024-03-12)
    * DEV-103-sh-games-plugin-complete
        - The implementation of SH Mapping is validated.
-   1.1.1 (2024-03-11)
    * DEV-103-sh-games-plugin-complete
        - The requested changes have been completed.
-   1.1.0 (2024-03-05)
    * DEV-103-sh-games-plugin-complete
        - Versions for simple and complete sh game are integrated.
-   1.0.18 (2023-10-16)
    * DEV-103-sh-games-plugin-simple-fix-slug
        - cpt slug defined in environment constants.
-   1.0.17 (2023-10-11)
    * resease/1.0.12
        - The origin of the date for the posts is modified.
-   1.0.16 (2023-10-10)
    * resease/1.0.12
        - Game menu name updated.
-   1.0.15 (2023-10-09)
    * resease/1.0.12
        - Fixed redis data reading and writing.
-   1.0.14 (2023-10-06)
    * resease/1.0.12
        - Image validation is fixed.
        - Date selection in request is fixed.
-   1.0.13 (2023-10-05)
    * resease/1.0.12
        - Title generation fixed.
        - CPT label name fix.
-   1.0.12 (2023-10-05)
    * DEV-103-sh-games-plugin-simple-fix
        - URL generation fixed.
-   1.0.11 (2023-10-04)
    * DEV-103-sh-games-plugin-simple
        - Simple version of the sh-games plugin is finalized.
-   1.0.10 (2023-08-24)
    * DEV-103-sh-games-plugin-request
        - It is allowed to assign game parts by sports.
-   1.0.9 (2023-08-11)
    * DEV-103-sh-games-plugin-request
        - Assignment of parents to categories is allowed.
-   1.0.8 (2023-08-11)
    * DEV-103-sh-games-plugin-request
        - Cleaned up query.
        - Validation adjustments were made.
-   1.0.7 (2023-08-10)
    * DEV-103-sh-games-plugin-request
        - CPT customization is allowed.
-   1.0.6 (2023-08-10)
    * DEV-103-sh-games-plugin-request
        - Multiple sports filter applied.
-   1.0.5 (2023-08-08)
    * DEV-103-sh-games-plugin
        - Form Automatic Games changed.
-   1.0.4 (2023-08-08)
    * DEV-103-sh-games-plugin
        - Form priority changed.
-   1.0.3 (2023-08-08)
    * DEV-103-sh-games-plugin
        - Update form key.
-   1.0.2 (2023-08-07)
    * DEV-103-sh-games-plugin
        - Create composer file.
-   1.0.1 (2023-08-07)
    * DEV-103-sh-games-plugin
        - Most of the features have been implemented.
        - Missing the final api and remove the slug from the CTP.
-   1.0.0 (2023-07-26)
    * DEV-103-sh-games-plugin
        - The base of the sh games plugin was initialized.
