Skip to content

Add Theme Calendar functionality - #1191

Merged
cjyabraham merged 9 commits into
mainfrom
theme-calendars
Sep 29, 2026
Merged

cjyabraham merged 9 commits into
mainfrom
theme-calendars

Conversation

@cjyabraham

@cjyabraham cjyabraham commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Example calendar

This adds a Theme Calendar page type: the definitive calendar for a topic, for example "AI Conferences 2026 & 2027". It lists Linux Foundation events alongside third-party External Events that share an Event Category. To help fill those calendars, admins get a guided tool: it generates a research prompt for Claude Desktop, then imports Claude's JSON results as draft External Events for review. The PR also consolidates schema.org Event JSON-LD into one pipeline and extends it to the existing Search & Filter calendar pages.

…play

Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Copilot AI balanced review requested due to automatic review settings September 26, 2026 08:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Event expiry, structured-data accuracy, accessibility, and rewrite activation issues must be addressed.

Review effort: Balanced
Findings: 7 Medium severity

Open (7)
What changed in this PR

Adds theme calendars combining categorized Linux Foundation and external events with cards, grouping, styling, admin management, and structured data.

Changes:

  • Adds Theme Calendar and External Event post types and metadata.
  • Queries, normalizes, groups, and renders upcoming events.
  • Adds calendar styling, branding, admin columns, and JSON-LD.
File Description
template-parts/​theme-calendar-card.php Renders event cards.
src/​scss/​modules/​_theme-calendar.scss Styles calendar cards.
src/​scss/​app.scss Imports calendar styles.
src/​images/​lf-mark.svg Adds LF branding asset.
single-lfe_theme_calendar.php Renders calendar pages.
library/​lfe-functions.php Queries events and generates JSON-LD.
includes/​class-lfevents.php Registers admin-column hooks.
admin/​partials/​sidebars.php Defines external-event fields.
admin/​partials/​cpts.php Registers new post types.
admin/​class-lfevents-admin.php Extends taxonomies and admin columns.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

'hierarchical' => false,
'menu_icon' => 'dashicons-calendar-alt',
'menu_position' => 23,
'rewrite' => array( 'slug' => 'calendar' ),
Comment on lines +668 to +672
$date_start = get_post_meta( $post->ID, 'lfes_date_start', true );
$date_end = get_post_meta( $post->ID, 'lfes_date_end', true );
if ( check_string_is_date( $date_end ) && $date_end < $today ) {
continue;
}
Comment on lines +718 to +724
foreach ( $query->posts as $post ) {
$events[] = array(
'id' => $post->ID,
'title' => get_the_title( $post ),
'url' => get_post_meta( $post->ID, 'lfes_external_event_url', true ),
'date_start' => get_post_meta( $post->ID, 'lfes_external_date_start', true ),
'date_end' => get_post_meta( $post->ID, 'lfes_external_date_end', true ),
'url' => esc_url( $event['url'] ),
);

if ( $event['virtual'] && $event['city'] ) {

$item = array(
'@type' => 'Event',
'name' => esc_html( $event['title'] ),
Comment on lines +814 to +817
$item['organizer'] = array(
'@type' => 'Organization',
'name' => $event['is_external'] && $event['organizer'] ? esc_html( $event['organizer'] ) : 'The Linux Foundation',
);
Comment on lines +31 to +39
<h5 class="medium-margin-right small-margin-bottom line-height-tight">
<strong>
<?php if ( $event['url'] ) { ?>
<a href="<?php echo esc_url( $event['url'] ); ?>"<?php echo $event['is_external'] ? ' target="_blank" rel="noopener"' : ''; ?>><?php echo esc_html( $event['title'] ); ?></a>
<?php } else { ?>
<?php echo esc_html( $event['title'] ); ?>
<?php } ?>
</strong>
</h5>
…ed clarity and consistency

Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Copilot AI review requested due to automatic review settings September 26, 2026 10:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Date filtering, CFP handling, and structured-data generation contain correctness issues.

Review effort: Balanced
Findings: 7 Medium severity

Open (7)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Use start date when LF event end date is missing

web/​wp-content/​themes/​lfevents/​library/​lfe-functions.php:694

An LF event whose end date is blank is never removed by this fallback, even after its start date has passed. End dates are optional in the editor, and the existing has_ended() logic elsewhere explicitly falls back to the start date, so stale events can remain on theme calendars when the lazily updated passed flag is absent. Use the start date as the effective end date when no valid end date exists.

This issue also appears on line 715 of the same file.

Medium severity Validate optional CFP dates before determining status

web/​wp-content/​themes/​lfevents/​template-parts/​theme-calendar-card.php:119

These CFP dates are optional free-text fields. When a start is present but the end is blank, strtotime( '' ) is false, so this branch reports the CFP as Closed; malformed values can also reach new DateTime() below. Validate both dates and show “Details Coming Soon” unless both are usable.

… for improved clarity and reusability

Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Copilot AI review requested due to automatic review settings September 26, 2026 10:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Event expiration and JSON-LD generation contain several data-correctness issues.

Review effort: Balanced
Findings: 7 Medium severity

Open (7)
Previously missed (4)

In code that hasn't changed since last review

Medium severity Classify hybrid events using all physical-location fields

web/​wp-content/​themes/​lfevents/​library/​lfe-functions.php:654

Hybrid events are classified as mixed only when city is populated, even though the new editor and card allow a physical location to consist of a country (or other place fields). Such events are emitted as online-only and lose their physical Place in the structured data. Base this decision on any populated physical-location data.

Medium severity Avoid HTML-escaping values before JSON-LD serialization

web/​wp-content/​themes/​lfevents/​library/​lfe-functions.php:668

These values are being HTML-escaped before being serialized as JSON-LD. JSON parsers do not decode HTML entities, so titles such as A & B become literal A &amp; B, and URLs with multiple query parameters contain &#038;, corrupting the schema data. Keep text/URLs in their raw sanitized forms and make both script emitters use wp_json_encode with the JSON_HEX flags so script embedding remains safe.

Medium severity Expire LF events with missing end dates using start date

web/​wp-content/​themes/​lfevents/​library/​lfe-functions.php:727

The missing-end-date case is also unhandled for LF events: the normalizer and schema support a valid start with no valid end, but this check then never expires it. Since the lazy lfes_event_has_passed flag is not updated for incomplete date ranges, that event can remain in the upcoming calendar indefinitely; use the start date as the effective end.

Medium severity Use start date when external event end date is missing

web/​wp-content/​themes/​lfevents/​library/​lfe-functions.php:758

The query explicitly includes records with a blank or missing end date, but this loop never falls back to the start date when deciding whether they have passed. A single-day external event saved with only date_start therefore remains in every upcoming theme calendar indefinitely, despite the schema already treating a missing end as equal to the start.

- Implemented LFEvents_CLI class for WP-CLI commands to discover external events and show discovery status.
- Created LFEvents_LiteLLM_Client class to handle communication with LiteLLM API, including chat completions and model management.
- Updated LFEvents class to include new LiteLLM client and agent discovery functionalities.
- Enhanced admin settings page to configure LiteLLM options and provide tools for testing connections and running event discovery.
- Added logging and reporting for discovery runs, including error handling and user notifications.

Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Copilot AI review requested due to automatic review settings September 27, 2026 11:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +756 to +758
foreach ( $query->posts as $post ) {
$events[] = lfe_normalize_external_event( $post );
}
Comment thread web/wp-content/mu-plugins/custom/lfevents/admin/class-lfevents-admin.php Outdated
Comment thread web/wp-content/mu-plugins/custom/lfevents/includes/class-lfevents-cli.php Outdated
…and trailing separators

Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Copilot AI review requested due to automatic review settings September 28, 2026 06:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Dry-run safety, category association, stale caches, rewrite activation, and event filtering issues must be addressed.

Review effort: Balanced
Findings: 1 High severity · 11 Medium severity

Open (12)
Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Invalidate calendars when LF events change

web/​wp-content/​mu-plugins/​custom/​lfevents/​includes/​class-lfevents.php:205

Theme Calendars also query LF Event post types, but the new invalidation hook only covers External Events. Changes to an LF event’s title, dates, location, or category will not purge calendar pages that include it, leaving their cached cards and JSON-LD stale. Register equivalent invalidation for saves and taxonomy changes on lfe_get_post_types() events.

Copilot AI review requested due to automatic review settings September 29, 2026 05:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +722 to +726
$event = lfe_normalize_lf_event( $post );
if ( check_string_is_date( $event['date_end'] ) && $event['date_end'] < $today ) {
continue;
}
$events[] = $event;
*/

get_header();
get_template_part( 'template-parts/header-global' );
Comment on lines +117 to +119
} elseif ( ! $event['cfp_date_start'] ) {
echo 'Details Coming Soon';
} elseif ( strtotime( $event['cfp_date_end'] ) < $time ) {
- Included the LFEvents_External_Import class for managing external event imports.
- Registered meta fields and admin menu for the external import tool.

Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Copilot AI review requested due to automatic review settings September 29, 2026 06:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Comment on lines +737 to +739
update_post_meta( $post_id, 'lfes_external_virtual', $candidate['virtual'] ? '1' : '' );
update_post_meta( $post_id, self::META_CONFIDENCE, $candidate['confidence'] );
update_post_meta( $post_id, self::META_DATE, gmdate( 'Y/m/d' ) );
$query = $params ? '?' . http_build_query( $params ) : '';
}

return $host . strtolower( $path ) . $query;
Comment on lines +3 to +7
* Admin tool for External Events researched manually with Claude Desktop.
*
* An admin generates a research prompt for an Event Category, runs it in
* Claude Desktop with web search, and pastes the JSON result back here. The
* tool validates and de-duplicates the results and files them as drafts.
…permission check for updating drafts

Signed-off-by: Chris Abraham <cjyabraham@gmail.com>
Copilot AI review requested due to automatic review settings September 29, 2026 06:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Past-event filtering, JSON-LD accuracy, URL deduplication, rewrite deployment, and scope discrepancies remain unresolved.

Review effort: Balanced
Findings: 1 High severity · 12 Medium severity · 1 Low severity

Open (14)
Resolved since last review (1)
Previously missed (2)

In code that hasn't changed since last review

Medium severity HTML escaping corrupts JSON-LD event data

web/​wp-content/​themes/​lfevents/​library/​lfe-functions.php:668

These values are data inside JSON-LD, not HTML output. esc_html() leaves names such as Cloud & AI as the literal Cloud &amp; AI, while display-context esc_url() turns query separators into &#038;, producing an incorrect event URL. Sanitize the data without HTML entity encoding before JSON serialization.

Medium severity Unknown external organizers incorrectly attributed to Linux Foundation

web/​wp-content/​themes/​lfevents/​library/​lfe-functions.php:677

An External Event is allowed to have no organizer, but this fallback then identifies The Linux Foundation as its organizer in JSON-LD. That publishes false attribution for third-party events; omit organizer when an external organizer is unknown and reserve the LF fallback for LF Events.

@cjyabraham
cjyabraham merged commit 67cfaa6 into main Sep 29, 2026
10 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants