Skip to content

feat: Schema.org structured data integration#1171

Merged
bcordis merged 51 commits intodevelopmentfrom
feature/schemaorg-integration
Mar 19, 2026
Merged

feat: Schema.org structured data integration#1171
bcordis merged 51 commits intodevelopmentfrom
feature/schemaorg-integration

Conversation

@bcordis
Copy link
Member

@bcordis bcordis commented Mar 18, 2026

Summary

Full Schema.org structured data integration with Joomla's built-in system, Smart Sync, and per-item edit protection.

Core Integration

  • Implements SchemaorgServiceInterface in ProclaimComponent — enables Schema.org tab on sermon, teacher, and series edit forms
  • Creates plg_schemaorg_proclaim plugin with three schema types: Sermon (CreativeWork), Teacher (Person), Series (CreativeWorkSeries)
  • Auto-populates schema fields from item data on first open — admins can override any field
  • Frontend JSON-LD output via CwmschemaorgHelper on both detail and list views:
    • Detail views (sermon, teacher, series) — individual item schema
    • List views (teachers, series) — ItemList schema with collection metadata
    • Duplicate prevention against Joomla's system plugin output

Smart Sync

  • Per-field hash tracking (_fieldHashes) detects which fields the user has customized vs auto-generated
  • On individual save: compares submitted values against previous auto-generated hashes. Untouched fields auto-update; customized fields are preserved
  • Merges new fields into existing schema records — when new schema fields are added (e.g., datePublished, publisher), they auto-populate on existing items without losing customizations
  • Reset Schema button on each edit form (sermon, teacher, series) — regenerates schema from item data, clearing any customizations
  • Bulk sync from admin dashboard with three modes:
    • Smart Sync (default) — updates unedited items, skips manually edited (reports skip count)
    • New Items Only — only creates entries for items without schema rows
    • Force Update All — overwrites everything including manual edits
  • Dashboard sync UI — Schema.org Sync button on admin Control Panel with AJAX progress
  • Save validation — warns about missing recommended fields (headline, description, datePublished) as a notice, not a blocker

Schema Type Mapping

Entity Schema.org Type Auto-populated Fields
Sermon CreativeWork headline, datePublished, dateModified, author, description, image, publisher
Teacher Person name, jobTitle, description, image, url, sameAs (social links), worksFor
Series CreativeWorkSeries name, description, image, url, datePublished, dateModified, publisher

Organization Name

  • Admin setting (admin/forms/admin.xml — SEO & Metadata > Organization Name) overrides site name for schema.org publisher/worksFor
  • Per-teacher org_name field (admin/forms/teacher.xml) for visiting speakers — three-tier fallback: teacher → admin setting → site name
  • DB migration 10.3.0-20260318 adds org_name column to #__bsms_teachers

Context Normalization

  • Dual context handling — Joomla fires content events with model-based contexts (com_proclaim.cwmteacher, com_proclaim.cwmserie) while forms use different contexts (com_proclaim.teacher, com_proclaim.serie). Plugin maps both to canonical form context for consistent DB storage
  • Registered content event contexts for teachers and series so schema processes during model saves
  • Model integrationCwmmessageModel, CwmteacherModel, and CwmserieModel updated to load and process schema data during save

Router Fix

  • Fixed ProclaimNomenuRules warnings where $view->parent could be false instead of an object, causing "Attempt to read property on false" warnings on frontend routes without menu items

Installation & Build

  • Plugin registered in $installActionQueue — auto-installs and enables on fresh install/upgrade
  • Version bump manifest and dev symlink support added to build tooling

Tests

  • Test bootstrap updated with Schema.org runtime shims (CmsRuntimeShims.php)
  • New/updated test coverage for template migration helper and sermons filter

Test plan

  • All 538 PHPUnit tests pass (1159 assertions)
  • All 246 Jest tests pass (18 suites)
  • PHP syntax clean (1252 files)
  • Open sermon edit → Schema tab shows "Sermon" auto-selected with fields populated
  • Open teacher edit → Schema tab shows "Teacher" auto-selected, description persists on save
  • Open series edit → Schema tab shows "Series" with name, description, url, dates, publisher populated
  • Edit a schema field manually, save → field preserved as custom, other fields auto-update
  • Save teacher with description → schema persists to DB (dual-context fix verified)
  • Save series → all new fields (url, datePublished, dateModified, publisher) persist
  • Series URL uses proper SEF routing via Route::link('site', ...) respecting menu items
  • View sermon frontend page source → JSON-LD in <head>
  • View teacher/series list pages → ItemList JSON-LD in <head>
  • Validate with Google Rich Results Test
  • Set Organization Name in admin → reflected in publisher/worksFor output
  • Set per-teacher org_name → overrides site default in worksFor
  • Click "Reset Schema" on edit form → schema regenerated from item data
  • Admin dashboard → Schema.org Sync → Smart Sync skips manually edited items
  • Fresh install → schemaorg plugin auto-installed and enabled

Related

🤖 Generated with Claude Code

bcordis and others added 26 commits March 17, 2026 11:04
…ration

ProclaimComponent now implements SchemaorgServiceInterface, enabling
Joomla's built-in Schema.org system plugin to recognize Proclaim.
Admins get a "Schema" tab on sermon, teacher, and series edit forms
where they can configure structured data per item.

Contexts registered:
- com_proclaim.cwmmessage → Messages (sermons)
- com_proclaim.teacher → Teachers
- com_proclaim.serie → Series

The existing CwmschemaorgHelper auto-generation continues to work
as before. Phase 2 will add a schemaorg plugin to auto-populate
defaults from item data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement SchemaorgServiceInterface on ProclaimComponent so Joomla's
built-in Schema.org system plugin recognizes Proclaim. Adds Schema.org
tab to message, teacher, and series edit forms.

Key changes:
- ProclaimComponent implements SchemaorgServiceInterface with contexts
  for messages, teachers, and series
- Override preprocessForm in all 3 models to import system plugins
  into the model's local dispatcher (required for third-party components)
- Add Schema.org tab rendering to message, teacher, and series edit
  templates (conditional — only shows when system plugin is enabled)
- Add JBS_CMN_SCHEMAORG_TAB language string for translatable tab label

Admins can now select a schema type (Article, Event, Person, etc.)
per item. Phase 2 will add a Proclaim-specific schemaorg plugin with
auto-populated sermon/speaker fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Eliminates 15 PHPUnit deprecation warnings about metadata in
doc-comments. PHPUnit 12 will require attributes instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Creates plg_schemaorg_proclaim that adds "Sermon" as a schema type
for messages, teachers, and series. Auto-populates schema fields
from item data:

- Messages: headline (studytitle), description (studyintro),
  datePublished (studydate), author (teachername), image
- Teachers: name, jobTitle, bio, image
- Series: name, description, image

Admins can override any auto-populated field. Custom key/value
fields allow adding arbitrary schema.org properties.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…data

Auto-populates Schema.org fields from message data in loadFormData():
- headline, description, image, datePublished, dateModified
- author (all teachers from junction table, comma-separated)
- isPartOf (series name), about (translated topics)
- genre (message type), locationCreated (location name)
- publisher (site name)

Topics with language keys are translated via Text::_().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add hasJoomlaSchema() for future fallback checks. Pretty-print
JSON-LD output when JDEBUG is enabled for readable page source.

Keep both JSON-LD outputs: our helper provides item-specific
CreativeWork schema, the system plugin provides page-level @graph
(Organization, WebSite, WebPage). They complement each other.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The sermon view was setting raw studyintro (with HTML tags) as the
page meta description. Joomla's schema.org system plugin reads this
for the WebPage description in the @graph. Now strips HTML and
truncates to 160 chars for clean schema and SEO output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Brent Cordis <994259+bcordis@users.noreply.github.com>
…ention

Add hasJoomlaSchema check inside inject() so detail views skip standalone
JSON-LD when admin has configured per-item schema via Joomla's system plugin.
Add buildTeacherList() and buildSeriesList() helpers with ItemList schema
output for teacher and series list pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Include plg_schemaorg_proclaim in the install action queue so it is
auto-installed and enabled on fresh installs and upgrades. Register the
plugin manifest in bump.php for version bumping and add the dev symlink
entry in proclaim_build.php.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The plugin was only registering "Sermon" in the dropdown for all three
contexts. Teachers saw "Sermon" as the only option and data was stored
under the wrong key. Now each context gets its own schema type (Sermon,
Teacher, Series) with dedicated form fields and correct auto-population:

- Teacher → Person schema with name, jobTitle, description, image
- Series → CreativeWorkSeries with name, description, image
- onSchemaBeforeCompileHead handles Person and CreativeWorkSeries types
- Override onSchemaPrepareForm to add only the relevant type per context

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Joomla's system schemaorg plugin returns early from onContentPrepareData
when no #__schemaorg row exists, so the plugin's onSchemaPrepareData
event never fires for new items. The sermon model already handled this
by populating schema data in loadFormData(). Apply the same pattern to
teacher (Person) and series (CreativeWorkSeries) models.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add proper Person schema properties to the Teacher type:
- url: auto-populated from teacher website field
- sameAs: social profile URLs from social_links JSON or legacy fields
- worksFor: Organization with site name
- Form XML adds editable fields for URL, social profiles, and organization
- onSchemaBeforeCompileHead flattens sameAs subform for JSON-LD output

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a one-click bulk sync that populates Joomla's #__schemaorg table
for all published messages, teachers, and series with auto-generated
structured data:

- Dashboard card on Control Panel tab triggers XHR modal
- CwmschemaorgHelper::syncAll() iterates all published items
- Controller schemaSyncXHR() endpoint with CSRF protection
- Bootstrap 5 progress modal with success/error states
- Force mode overwrites all existing entries

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Model loadFormData() now checks #__schemaorg before auto-populating:
  if schema data was already saved (and possibly customized), skip
  auto-population to preserve manual edits
- Schema Sync modal now offers two modes:
  "Sync New Items Only" — skips items with existing schema entries
  "Force Update All" — overwrites everything (warns about custom changes)
- Remove auto worksFor from model/sync — Organization name may differ
  from site name, let admins set it manually via the form

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When saving an item with a schema type selected, check for empty
recommended fields (headline/name, description, datePublished) and
enqueue a notice message listing what's missing. The save still
completes — this is informational, not blocking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Smart Sync uses a _autoHash fingerprint stored inside the schema JSON
to detect whether an admin manually edited the schema fields. On sync:

- Generate fresh auto-schema from current item data
- Load stored schema, compare _autoHash against actual data hash
- If hashes match → never manually edited → safe to overwrite
- If hashes differ → admin customized → skip with count

Three sync modes available from the admin dashboard:
- Smart Sync (default): updates unedited items, skips manual edits
- New Items Only: only creates entries for items without schema rows
- Force Update All: overwrites everything including manual edits

Also stamps _autoHash on every save via onSchemaPrepareSave, and
strips it from frontend JSON-LD output in onSchemaBeforeCompileHead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Joomla's form filter strips _autoHash since it's not in the form XML.
On save, restore the existing hash from the DB row instead of computing
a new one — this preserves the original auto-generated fingerprint so
manual edits are correctly detected by Smart Sync.

First save (no existing row) stamps a fresh hash. Subsequent saves
restore the original hash, so any field changes cause a mismatch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When saving a message, teacher, or series, the schema.org plugin now
applies Smart Sync logic:

- If schema was auto-generated (hash matches): silently regenerate
  schema fields from the updated item data and stamp a new hash
- If schema was manually edited (hash mismatch): preserve the admin's
  customizations and show an info notice pointing to bulk sync

Adds generateSchemaFromItem() with buildSermonSchema(),
buildTeacherSchema(), buildSeriesSchema() methods that create fresh
schema arrays from the Table object during save.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When saving an item with manually customized schema, the info notice
now includes a "Refresh from item data" button that force-regenerates
the schema for that single item and redirects back to the edit page.

- Add CwmadminController::schemaForceRefresh() endpoint
- Add CwmschemaorgHelper::syncOne() for single-item force sync
- Extract buildSermonSchemaFromRow(), buildTeacherSchemaFromRow(),
  buildSeriesSchemaFromRow() as reusable static methods
- Notice shows inline button with CSRF-protected link

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The hasJoomlaSchema() DB check caused schema auto-population to be
skipped when a #__schemaorg row existed, but Joomla's system plugin
onContentPrepareData runs AFTER loadFormData and overwrites our
defaults with saved data anyway. Reverting to the original pattern:

- Always auto-populate schema defaults from item data
- Joomla's system plugin loads saved schema data on top if it exists
- The Smart Sync hash in onSchemaPrepareSave handles edit detection

This fixes messages showing "None" after bulk sync was run.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add an "Organization Name" field in Admin > SEO & Metadata that
overrides the site name for Schema.org publisher (sermons) and
worksFor (teachers). Falls back to the Joomla site name when blank.

- CwmschemaorgHelper::getOrgName() centralizes the lookup
- Used in bulk sync builders, model auto-populate, and plugin builders
- Per-teacher org override possible via the worksFor schema form field

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add org_name column to #__bsms_teachers so visiting speakers can have
their own organization (e.g., "Grace Community Church") in schema.org
worksFor output instead of the site default.

Three-tier fallback: teacher org_name → admin "Organization Name"
setting → Joomla site name. If the teacher field is blank, the site
default is used automatically.

- SQL migration 10.3.0-20260318
- Field in teacher form Details fieldset
- Property on CwmteacherTable
- Updated in helper buildTeacherSchemaFromRow(), plugin
  buildTeacherSchema(), and model loadFormData() auto-populate

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The field was added to the form XML but not rendered in the template.
Added it after the Title field in the Details tab.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bcordis and others added 3 commits March 18, 2026 13:55
The Smart Sync regeneration in onSchemaPrepareSave was overwriting
manual edits from the Schema tab — it couldn't distinguish "user
edited schema field" from "item data changed since last sync."

Simplified to: on save, just preserve the original _autoHash from DB
(or stamp one on first save). The user's form edits save as-is. The
model's loadFormData() provides fresh auto-populated defaults each
time the form opens. Bulk Smart Sync still handles the "update
unedited items from changed source data" use case.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The system schemaorg plugin listens on onContentPrepareData to load
saved schema from #__schemaorg, but preprocessData() only imports the
'content' plugin group by default. The system plugin never heard the
event, so saved schema data (including manual edits) was never loaded
from the DB — the model's auto-populated defaults always won.

Override preprocessData() in all three models to also import system
plugins into the dispatcher, matching what preprocessForm() already
does for onContentPrepareForm.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Joomla's loadForm() never calls preprocessData() — models must call it
explicitly (as com_content's ArticleModel does). Without it, the system
schemaorg plugin's onContentPrepareData never fires, so saved schema
from #__schemaorg is never loaded. The model's auto-populated defaults
always win, overwriting manual edits on every form open.

Added $this->preprocessData() call at the end of loadFormData() in all
three models, after auto-populate runs. The system plugin then loads
saved DB data on top, preserving manual edits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bcordis and others added 19 commits March 18, 2026 14:11
Compare incoming form data against existing DB data to detect whether
the user actually edited the schema tab:

Case 1: incoming == existing + hash matches (auto-generated)
  → User didn't touch schema, regenerate from updated item data
  → Title change auto-updates headline

Case 2: incoming == existing + hash mismatch (previously customized)
  → User didn't touch schema, preserve their prior customization

Case 3: incoming != existing (user actively edited schema tab)
  → Save user's edits, keep original hash for future detection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The form POST includes all schema fields with empty values (description
="", image="", noteSermon="") while the DB only stores non-empty
values. Direct comparison always detected a "user edit" (Case 3),
preventing auto-regeneration when the title changed (Case 1).

Added normalizeForCompare() that strips empty values, null values,
and note-prefixed fields before comparing incoming vs existing data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The data comparison approach was unreliable — form POST and DB data
have structural differences (empty fields, subform serialization)
that made detection impossible.

New approach: a hidden _schemaEdited field defaults to 0. JS sets it
to 1 when the user changes any schema field. On save:

- _schemaEdited=0 + hash matches → regenerate from item data (title
  change auto-updates headline)
- _schemaEdited=0 + hash mismatch → preserve prior manual edits
- _schemaEdited=1 → save user's current edits, keep old hash

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed the hash check from individual save. Now the logic is simple:

- _schemaEdited=0 (user didn't touch schema tab): always regenerate
  from current item data. Schema stays in sync with title, description,
  etc. automatically.
- _schemaEdited=1 (user actively edited a field): save their edits
  and stamp a fresh hash.

The _autoHash is now only used by bulk Smart Sync to detect items
with prior manual edits. Individual save relies purely on the JS flag.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Track which specific schema fields the user edits via JS, not just
a whole-schema flag. On save:

1. Regenerate fresh schema from current item data (title change
   auto-updates headline, date changes update datePublished, etc.)
2. Overlay only the fields the user actively edited in this session

Example: user edits description but not headline → headline auto-
updates from title, description keeps the user's custom value.

Uses a hidden _editedFields JSON array populated by JS change/input
listeners on schema form fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a "Reset Schema from Item Data" button at the bottom of the
Schema.org tab on message, teacher, and series edit forms. Clicking
it force-regenerates the schema from item data and redirects back.

Also fixes missing Uri and Session use statements in templates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace session-only _editedFields with persistent _customFields
stored in the schema JSON. When a user edits a specific schema field:

1. JS tracks field name in _editedFields (current session)
2. On save, _editedFields merges into _customFields (persistent in DB)
3. Fresh schema regenerates from item data
4. Fields listed in _customFields overlay from form data instead

Example flow:
- User customizes "description" → _customFields: ["description"]
- Next save, title changes → headline auto-updates, description stays
- Reset button clears _customFields and regenerates everything

_customFields stripped from frontend JSON-LD output alongside _autoHash.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Temporary debug output to trace Smart Sync field tracking:
- _editedFields raw value from form
- sessionEdited parsed array
- existing _customFields from DB
- merged customFields
- incoming vs fresh headline values

TODO: remove debug flag after testing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The system plugin's event object wasn't persisting our _customFields
modifications to the DB. Now our plugin writes directly to #__schemaorg
and unsets entry->schemaType to tell the system plugin to skip its
own DB write (it checks isset(schemaType) before writing).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CMSPlugin::getDatabase() is not available in all plugin contexts.
Use Factory::getContainer()->get(DatabaseInterface::class) instead
for both writeSchemaToDb and loadExistingSchema.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a user edits a schema field back to the auto-generated value
(e.g., sets headline to match the title), that field is removed from
_customFields. This means future saves will auto-update it again.

Allows users to "un-lock" a field by simply setting it back to
what the system would generate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Joomla's subform widget renders inner fields after DOMContentLoaded,
so direct querySelectorAll binding missed them. Switched to event
delegation on document with capture phase — catches change/input
events from any schema field regardless of when it's rendered.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Joomla's subform web component prevents input/change events from
bubbling to document. New approach:

1. Capture original field values after 1.5s delay (subform render)
2. On form submit, compare current values against originals
3. Any changed fields get added to _editedFields

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace all JS-based edit tracking with pure server-side comparison.
On every save:

1. Regenerate fresh schema from current item data
2. Compare each submitted field against the auto-generated value
3. If submitted != auto-generated → user customized → preserve
4. If submitted == auto-generated → not customized → use fresh value
5. Setting a field back to the auto value = un-customize

No JS, no timing issues, no event bubbling problems. The comparison
happens at save time using generateSchemaFromItem() as the baseline.

Tracked fields: headline, name, description, jobTitle, datePublished,
dateModified, image, url. Complex fields (author, sameAs, worksFor,
genericField) are always preserved from submission.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Only track headline/name, description, and jobTitle for custom edit
detection. Dates (timezone conversion), images (path format), and URLs
cause false positives due to format differences between form POST and
raw Table data. These always auto-update from item data now.

Also keeps unset(schemaType) to prevent system plugin from overwriting
our direct DB write.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store short hashes (8-char md5) of auto-generated field values instead
of full values — avoids duplicating long descriptions in the DB.

Compare submitted values against PREVIOUS auto-gen hashes (not current):
- submitted hash == previous auto-gen hash → user didn't touch → auto-update
- submitted hash == current auto-gen hash → user set it back → un-customize
- neither match → genuinely custom → preserve

Stored as _fieldHashes: {headline: "a1b2c3d4", description: "e5f6g7h8"}
Stripped from frontend JSON-LD output alongside other internal keys.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add 'url' to per-field hash tracking (headline, name, description,
  jobTitle, url)
- Remove unused _autoValues hidden form field
- Clean up stale _autoValues/_editedFields references
- Remove debug comments

Dates and images excluded from tracking due to format differences
(timezone conversion, path normalization) causing false positives.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bcordis and others added 3 commits March 18, 2026 16:03
The model name (cwmteacher/cwmserie) differs from the form name
(teacher/serie), creating two different contexts:
- Form context: com_proclaim.teacher (for Schema tab rendering)
- Content event context: com_proclaim.cwmteacher (for save events)

The system plugin's onContentAfterSave uses the content event context,
but our isSupported() only matched form contexts. Teacher and series
saves were silently skipped — schema data was never processed by our
Smart Sync logic.

Added content event contexts to getSchemaorgContexts(), CONTEXT_TYPE_MAP,
and generateSchemaFromItem(). Messages were unaffected (model name ==
form name: cwmmessage).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The save event context (com_proclaim.cwmteacher) differs from the form
context (com_proclaim.teacher) used when storing/loading schema data.
This caused loadExistingSchema and writeSchemaToDb to use different
contexts — the load found nothing, the write created a duplicate row.

Added CONTEXT_CANONICAL map to normalize content event contexts to
form contexts. All DB operations (load, write) now use the canonical
form context consistently.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…warnings

Add url (via Route::link), datePublished, dateModified, and publisher
fields to Series CreativeWorkSeries schema. Wire publisher org name into
sermon and series populate methods for form display. Merge fresh
auto-generated fields into existing schema records so new fields appear
on previously saved items. Fix ProclaimNomenuRules warnings where
$view->parent could be false instead of an object. Sync translations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bcordis bcordis merged commit dc0e131 into development Mar 19, 2026
6 checks passed
@bcordis bcordis deleted the feature/schemaorg-integration branch March 19, 2026 00:01
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.

1 participant