Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/stevemacias/ordo_kalendar/archive/refs/heads/main.zipStandalone WordPress plugin scaffold for a lightweight 1928 Book of Common Prayer liturgical calendar.
Version 0.5.3 — What's New
May–July 2026 content batch — 32 canonical feast pages are now live (up from 17). All content uses 1928 BCP propers only.
New and completed movable feast pages:
- Fourth Sunday after Easter (Easter +28, May 3, 2026) — new entry; Collect "O Almighty God, who alone canst order the unruly wills"; James 1:17; John 16:5.
- Fifth Sunday after Easter (Easter +35, May 10) — completed stub; Collect "O Lord, from whom all good things do come"; James 1:22; John 16:23.
- Sunday after Ascension Day (Easter +42, May 17) — completed stub; Collect "O God, the King of glory"; 1 Peter 4:7; John 15:26.
- Monday in Whitsun Week (Easter +50, May 25) — completed stub; Acts 10:34; John 3:16.
- Tuesday in Whitsun Week (Easter +51, May 26) — completed stub; Acts 8:14; John 10:1.
- First through Eighth Sundays after Trinity (Easter +63 through +112, June 7–July 26) — nine new entries with full 1928 BCP Collects, Epistle and Gospel references, 3-paragraph theological notes, and three FAQs each.
New and completed fixed feast pages:
- Saint Philip and Saint James (May 1) — James 1:1; John 14:1; Collect "O Almighty God, whom truly to know is everlasting life."
- Saint Barnabas (June 11) — Acts 11:22; John 15:12; Collect "O Lord God Almighty, who didst endue thy holy Apostle Barnabas."
- Nativity of Saint John Baptist (June 24) — Isaiah 40:1; Luke 1:57; Collect "Almighty God, by whose providence thy servant John Baptist was wonderfully born."
- Saint Peter (June 29) — Acts 12:1; Matthew 16:13; Collect "O Almighty God, who by thy Son Jesus Christ didst give to thy Apostle Saint Peter."
- Saint James (July 25) — Acts 11:27; Matthew 20:20; Collect "Grant, O merciful God, that as thine holy Apostle Saint James."
Sitemap expanded — get_content_day_dates() in SPBCP_Calendar_Sitemaps updated to include all new movable feast offsets (+28, +63 through +112) and all five new fixed-feast dates (May 1, June 11, June 24, June 29, July 25).
Version 0.5.2 — What's New
- Fatal-error fix:
render_season()—DateTimeImmutable::createFromFormat()was chained directly with->format()without a null check. If the date object was unexpectedlyfalsethis would have caused a fatal error. The return value is now checked withinstanceof DateTimeImmutablebefore formatting. - SEO: invalid feast and season slugs now 404 — Previously
/feast/anything/and/season/anything/returned HTTP 200 with placeholder content, which Google would index as thin pages.maybe_404_invalid_calendar_request()now validates feast and season slugs against the data layer and issues a proper 404 for any unrecognised slug. - Require order corrected —
class-spbcp-calendar-propers.phpis now loaded beforeclass-spbcp-calendar-data.phpin the main plugin bootstrap, matching the dependency relationship (the data class calls the propers class). - Sitemap performance —
SPBCP_Calendar_Sitemaps::get_url_entries()now caches its result in a static variable. WordPress core calls bothget_max_num_pages()andget_url_list()per sitemap request, which previously triggered two full URL-list computations; now it computes once. - Developer tool hardening —
tools/extract-propers-1928.phpnow sends HTTP 403 when accessed from a browser (matching the existing behaviour oftools/validate-calendar-data.php). - Production deployment documentation — README now includes an Installation section, pre-release validator workflow, AIOSEO integration notes, developer tool access notes, supported year range explanation, and invalid-route SEO safety summary.
- QA checklist expanded — TESTING.md rewritten as a 13-section checklist covering activation, valid/invalid routes, SEO titles, meta descriptions, canonical URLs, JSON-LD, sitemaps, content rendering, security, plugin compatibility, and CLI validation.
Version 0.5.1 — What's New
- SEO titles for all route types — Feast, season, day, and month pages now each produce a unique, correctly-formatted
<title>tag. Season pages use "Anglican Liturgical Calendar (1928 BCP)"; day pages include the feast name and date; month pages include the month and year. - Meta descriptions — Every plugin-generated route now outputs a distinct 130–155 character
<meta name="description">tag derived from the page's feast, season, date, or month. - Canonical links for day and month pages — In addition to existing feast and season canonicals,
/1928-bcp-calendar/{year}/{month}/{day}/and/1928-bcp-calendar/{year}/{month}/routes now each emit a<link rel="canonical">pointing to their exact URL. - All in One SEO integration hardened — When AIOSEO is active the plugin no longer outputs canonical or description tags directly. Instead it injects values through
aioseo_title,aioseo_description, andaioseo_canonical_urlfilters so AIOSEO's own output carries the correct data without duplication. Apre_get_document_titlehook at priority 20 overrides AIOSEO's title on plugin pages. - Single SEO context method — All five SEO hooks (
filter_document_title_parts,filter_pre_get_document_title,render_canonical,render_meta_description, AIOSEO filters) share oneget_seo_context()call cached statically per request; no duplicate data-layer lookups.
Version 0.5.0 — What's New
- Completed Holy Week feasts — Palm Sunday, Maundy Thursday, Good Friday, and Easter Even are now marked
complete => truewith full theological notes and three FAQs each; all four are live canonical feast pages included in the sitemap. - Circumcision of Christ corrected — The theological note, which had been accidentally overwritten with Epiphany content, has been replaced with the correct note covering the eighth-day circumcision, the Epistle from Philippians 2, and the Gospel from Luke 2.
- Easter Week octave pages — Monday in Easter Week and Tuesday in Easter Week are now marked
public => trueandcomplete => truewith full 3-paragraph theological notes and three FAQs each (Acts 10:34 + Luke 24:13 Emmaus; Acts 13:26 + Luke 24:36 upper-room appearance).
Version 0.4.0 — What's New
completeflag — Feast definitions now require both'public' => trueAND'complete' => truebefore a canonical feast page is published or included in the sitemap. Entries with onlypublic => trueare treated as staging intent and are excluded from public routing until marked complete.- Renderer safeguards — Day-detail entries with no collect, epistle, or gospel no longer render empty liturgical content sections. Incomplete entries appear by title and date only.
- Validator hardened — New checks warn when
public=truebutcompleteis absent or false; warn for any calendar entry that appears on the grid with no propers data; and FAIL only when a declared-complete public feast is missing required fields.
Version 0.3.0 — What's New
- Standardised internal linking — Feast pages show a clickable next-occurrence date link; day-detail pages show prev/next navigation with actual dates as anchor text; season pages show a current-month link; feast-link anchor text uses the feast's own title throughout.
Version 0.2.0 — What's New
- Season hub pages at clean URLs (
/1928-bcp-calendar/season/{slug}/) for all six liturgical seasons (Advent, Christmas, Epiphany, Lent, Eastertide, Trinity). - Theological notes — 150–300 word original notes for seven major feasts: Easter Day, Ascension Day, Whitsunday, Trinity Sunday, Christmas Day, Epiphany, All Saints' Day.
- FAQ blocks — Three short FAQs per major feast, stored in the data layer, rendered as semantic HTML, with FAQPage JSON-LD on supporting feast pages.
- Internal linking — Feast pages link to their season and related season-mates; season pages list their feasts; day-detail pages show a season link and prev/next day navigation bounded to the supported year range.
- Public flag — Feast definitions now carry
'public' => trueto mark entries that have complete enough content to be canonical pages. Sitemaps and feast-page routing respect this flag. Incomplete entries appear in month views but do not link to feast pages. - Validator warnings — The validator emits non-failing
WARNlines for non-public feasts with partial data, reserving hardFAILfor public feasts with missing required fields.
Current Scope
- Plugin slug:
spbcp-calendar - Shortcode:
[spbcp_calendar] - Optional test attributes:
[spbcp_calendar year="2026" month="5"] - Clean month URLs:
/1928-bcp-calendar/2026/05/ - Clean day URLs:
/1928-bcp-calendar/2026/05/14/ - Canonical feast URLs:
/1928-bcp-calendar/feast/easter-day/ - Season hub URLs:
/1928-bcp-calendar/season/easter/ - Supported calendar route range: 2025 through 2028
- Invalid dates and out-of-range URLs return the active theme's 404 response
- XML sitemap entries for public canonical feast pages, season pages, supported month pages, and populated day pages
- JSON-LD structured data including
AboutPage,Event,BreadcrumbList, andFAQPage - No React, no Blocksy theme edits, no The Events Calendar integration
TODO — Remaining 1928 BCP Propers
The following feasts and Sundays are defined in the data files but not yet marked 'public' => true. They need full collects, epistle references, and gospel references before they are ready to publish as canonical pages.
Fixed Feasts (not yet public)
- Purification of the BVM (February 2)
- Annunciation of the BVM (March 25)
- St. Philip and St. James (May 1)
- St. Barnabas (June 11)
- St. John Baptist (June 24)
- St. Peter (June 29)
- St. James (July 25)
- St. Bartholomew (August 24)
- St. Matthew (September 21)
- St. Michael and All Angels (September 29)
- St. Luke (October 18)
- St. Simon and St. Jude (October 28)
- St. Andrew (November 30)
- St. Thomas (December 21)
- St. Stephen (December 26)
- St. John Evangelist (December 27)
- Holy Innocents (December 28)
Movable Feasts (not yet public)
All Holy Week feasts (Palm Sunday, Maundy Thursday, Good Friday, Easter Even) and Easter Week octave days (Monday and Tuesday in Easter Week) are now complete and public.
Sundays of the Year (not yet in data)
All 27 Sundays after Trinity, all 4 Sundays in Advent, all Sundays after Epiphany, Septuagesima, Sexagesima, Quinquagesima, the five Sundays in Lent (including Passion Sunday and Palm Sunday), and Easter Sunday through the Sunday after Ascension are not yet added as recurring entries.
These require the full Easter computus (already implemented in SPBCP_Calendar_Data::get_easter_for_year()) and offset-based date computation per the existing movable feast pattern.
Adding Future Feasts and Sundays Safely
The plugin uses a two-flag gate to prevent incomplete entries from accidentally becoming public canonical pages. Follow these rules when adding new feast or Sunday entries.
Flag meanings
public |
complete |
Result |
|---|---|---|
| absent / false | absent / false | Entry appears in the calendar grid only. No feast page, no sitemap URL. |
true |
absent / false | Staging intent declared. Still no feast page or sitemap URL. Validator emits a WARN. |
true |
true |
Full canonical feast page published. Included in sitemap. Validator fails if required fields are missing. |
Step-by-step process for a new feast or Sunday
-
Add the stub. In
fixed-feasts.phpormovable-feasts.php, add a minimal entry withslug,title,type,rank,liturgical_color, and eithermonth/day(fixed) oroffset(movable). No flags yet.'second-sunday-after-epiphany' => array( 'slug' => 'second-sunday-after-epiphany', 'title' => 'Second Sunday after Epiphany', 'type' => 'sunday', 'rank' => 'Sunday', 'liturgical_color' => 'green', 'offset' => 14, // Easter − 35 = approximate; recalculate per BCP table ),The entry will appear by title in month and day views. No feast page will be generated. No empty content sections will render.
-
Add propers. Once the Collect, Epistle reference, and Gospel reference are ready, add them directly to the definition or via a
proper_keyentry inincludes/data/propers-1928.php. Add atheological_note(150–300 words, multi-paragraph using\n\n) and at least threefaqsentries. -
Mark as staging. Set
'public' => trueon the definition. The feast page still will not publish (thecompletegate blocks it), but the validator will now warn you that the feast is queued for publication. -
Run the validator.
php tools/validate-calendar-data.phpFix any
FAILlines. ReviewWARNlines. The validator will confirm whether all required fields (collect, epistle_reference, gospel_reference) are present. -
Mark as complete. Once the validator passes cleanly, set
'complete' => true. The canonical feast page will now be live, the URL will appear in the sitemap, and the feast will appear in related-feasts lists on season pages. -
Rebuild and deploy.
zip -r spbcp-calendar.zip spbcp-calendar/ --exclude "*.DS_Store"
Required fields for complete => true
A definition must have all of these before setting complete => true:
slug— WordPress-style slug (lowercase, hyphens only)title— Human-readable namecollect— Full 1928 BCP collect text (or viaproper_key)epistle_reference— Scripture reference (e.g.Colossians 3:1)gospel_reference— Scripture reference (e.g.John 20:1)theological_note— 150–300 word original note, two or three paragraphs separated by\n\nfaqs— Array of at least three question/answer pairs
What will NOT work
- Setting
complete => truewithoutpublic => true— the validator will flag this as an inconsistent state. - Linking to a feast page from a month view — the renderer only generates feast links for entries where
SPBCP_Calendar_Data::get_feast()returns a result, which requires both flags. - Adding a feast to the sitemap manually — the sitemap delegates entirely to
SPBCP_Calendar_Data::get_public_feast_slugs(), which reads the flags dynamically. There is no hardcoded slug list to update.
Production Deployment
Installation
- Upload
spbcp-calendar.zipthrough Plugins → Add New → Upload Plugin or unzip intowp-content/plugins/. - Activate the plugin. Activation automatically creates the calendar page (
/1928-bcp-calendar/) if it does not exist and flushes rewrite rules. - If any plugin-generated URL returns a 404 after activation, go to Settings → Permalinks and click Save Changes to flush rewrite rules manually.
Before releasing content changes
Run the CLI validator from the plugin directory before packaging and uploading a new version:
php tools/validate-calendar-data.php
Or via WP-CLI on the server:
wp eval-file wp-content/plugins/spbcp-calendar/tools/validate-calendar-data.php
Fix all FAIL lines before proceeding. WARN lines are informational and do not block release. The validator will exit 0 on PASS or WARN and 1 on any FAIL.
All in One SEO
When AIOSEO is active the plugin supplies title, meta description, and canonical URL through AIOSEO's own filters (aioseo_title, aioseo_description, aioseo_canonical_url). Direct wp_head output is suppressed to prevent duplicate tags. No manual AIOSEO configuration is required.
If AIOSEO is not active the plugin outputs <meta name="description"> and <link rel="canonical"> directly in wp_head at priority 1 and 2.
Developer tools
Both files in the tools/ directory respond with HTTP 403 when accessed from a browser. They must be run from the command line or via WP-CLI. Do not expose the tools/ directory to the public internet (the default WordPress directory structure keeps it inaccessible from the web unless the webserver is misconfigured).
Supported year range
The plugin serves calendar routes for years 2025 through 2028, controlled by SPBCP_CALENDAR_MIN_YEAR and SPBCP_CALENDAR_MAX_YEAR constants in spbcp-calendar.php. Date URLs outside this range return the active theme's 404 response. Month navigation links are bounded to this range and will not generate links to unsupported years.
Invalid routes and SEO safety
- URLs with unrecognised feast or season slugs (e.g.
/feast/anything/,/season/anything/) return HTTP 404 — not placeholder content — so crawlers never index thin pages. - Invalid date combinations (e.g. February 30, November 31) return HTTP 404.
- Prev/next day navigation links are suppressed at the boundary of the supported year range.
- The sitemap includes only URLs for feast definitions where both
public => trueandcomplete => trueare set. Incomplete or staging-only entries are excluded automatically.
Files
spbcp-calendar.phpboots the plugin.includes/class-spbcp-calendar-data.phploads and normalizes calendar data files.includes/class-spbcp-calendar-propers.phploads the separate propers database.includes/data/fixed-feasts.phpstores recurring fixed-date feast definitions.includes/data/movable-feasts.phpstores movable feast definitions and explicit sample year dates.includes/data/propers-1928.phpstores structured Collect, Epistle, and Gospel data.includes/class-spbcp-calendar-renderer.phprenders the semantic month table, day detail view, feast pages, and season hub pages.includes/class-spbcp-calendar-schema.phpoutputs route-aware JSON-LD structured data inwp_head.includes/class-spbcp-calendar-sitemaps.phpbuilds plugin-generated XML sitemap URL entries.includes/class-spbcp-calendar-sitemap-provider.phpregisters those entries with WordPress core sitemaps.includes/class-spbcp-calendar-plugin.phpregisters assets, rewrite rules, query vars, and the shortcode.assets/css/spbcp-calendar.cssstyles the desktop table, mobile stacked cards, feast pages, and season hub pages.tools/extract-propers-1928.phpdocuments and scaffolds extraction from a convertedPropers_1928.txtsource.tools/validate-calendar-data.phpvalidates feast data from the command line before release.
Sitemap Behavior
The plugin registers a WordPress core sitemap provider named spbcpcalendar when core sitemap classes are available. The generated sitemap includes:
- Season hub pages for all six liturgical seasons.
- Canonical feast pages for all feast definitions with
'public' => true(currently: Palm Sunday, Maundy Thursday, Good Friday, Easter Even, Easter Day, Ascension Day, Whitsunday, Trinity Sunday, Christmas Day, Circumcision of Christ, Epiphany, All Saints' Day). - Month pages for every supported month from 2025 through 2028.
- Day pages for populated movable and fixed feast dates across all supported years.
If All in One SEO is active, the plugin also hooks aioseo_sitemap_additional_pages and appends the same URL set. AIOSEO requires its Sitemaps > Additional Pages toggle to be enabled before that filter is reflected in its sitemap output.
Structured Data
The plugin outputs JSON-LD only on /1928-bcp-calendar/ plugin routes:
- Canonical feast pages receive
AboutPageandBreadcrumbListschema. Pages with FAQs also receiveFAQPageschema. - Date/day pages receive
EventandBreadcrumbListschema. - Season hub pages receive
BreadcrumbListschema.
Calendar Data Structure
The calendar data is split by liturgical date type:
includes/data/fixed-feasts.phpcontains fixed civil-date definitions such as Christmas Day, Epiphany, All Saints' Day, and the fixed Saints' days. Entries with both'public' => trueand'complete' => trueare published as canonical feast pages.includes/data/movable-feasts.phpcontains movable definitions computed from Easter offsets. The seven fully-complete feasts (Easter Day, Ascension Day, Whitsunday, Trinity Sunday, and Christmas, Epiphany, All Saints' Day) are marked both public and complete. Holy Week feasts are marked public only and will not generate feast pages until marked complete.includes/data/propers-1928.phpremains the separate database for Collect, Epistle, and Gospel content. Calendar entries link to it throughproper_key.
Both 'public' => true AND 'complete' => true must be set for an entry to produce a canonical feast page, appear in the sitemap, or be linked from month/day views. Either flag absent means the entry shows in the calendar grid by title only, with no feast-page link and no empty content sections rendered.
Data Validation Workflow
Run the developer-only validator from the plugin directory before packaging larger data changes:
php tools/validate-calendar-data.php
In a WordPress environment with WP-CLI available:
wp eval-file wp-content/plugins/spbcp-calendar/tools/validate-calendar-data.php
The script is command-line only and exits with HTTP 403 if requested from a browser. Checks include: required feast fields, fixed and movable date data, populated Collect/Epistle/Gospel fields, public/complete flag consistency, calendar entries without propers, duplicate slugs, Lectionary Central URL formatting, and canonical feast page URL generation.
Exit codes:
0— all checks passed (PASS) or passed with warnings only (WARN)1— one or more hard failures (FAIL)
Failure and warning rules:
- FAIL — entry has
public=trueANDcomplete=truebut is missing collect, epistle_reference, or gospel_reference. - WARN — entry has
public=truebutcompleteis absent or false (staging intent; feast page won't publish yet). - WARN — entry has some propers data but is missing required fields and is not yet public+complete.
- WARN — entry appears on the calendar grid (has a valid date) but has no propers data at all.
Run the validator before every release and before setting complete => true on any new feast.
Propers Extraction Workflow
- Convert
Propers_1928.wpdto plain text, for example with LibreOffice:soffice --headless --convert-to txt --outdir /tmp /path/to/Propers_1928.wpd - Run the extractor against the verified text export:
php tools/extract-propers-1928.php /tmp/Propers_1928.txt > includes/data/propers-1928.generated.php - Review the generated PHP carefully against the source wording.
- Replace or merge into
includes/data/propers-1928.php. - Add matching
proper_keyvalues to calendar entries.