Rename to Open Trust Center by Ettic + migrate internal namespace#28
Conversation
- Rename opentrust.php to open-trust-center-by-ettic.php - Plugin Name: OpenTrust to Open Trust Center by Ettic - Plugin URI: /opentrust to /open-trust-center-by-ettic - Text Domain: opentrust to open-trust-center-by-ettic - Version: 1.1.1 to 1.2.0 - readme.txt H1 and Stable tag updated Trademark rename per wp.org review. Folder stays opentrust/ during dev; renamed at submission build.
Drop maybe_upgrade() and its init hook, plus the five migration helpers (rename_cpt_slugs_v4, rename_postmeta_keys_v5, backfill_uuids, backfill_model_snapshot). ~180 LOC removed. Reset OPENTRUST_DB_VERSION constant to 1 (fresh-release baseline). Drop legacy ot_* slugs from uninstall.php (their migration path is gone). Keep OpenTrust_CPT::LEGACY_META_MAP intact — still needed by the importer's back-compat remap. Treating 1.2.0 as the first version intended for distribution. Any developer running an older opentrust install migrates via export/import, not in-place schema bumps.
Bulk rename across all PHP source: - OpenTrust class to Ettic_OTC (main class) - OpenTrust_* classes to Ettic_OTC_* (25 classes, 420 refs) - OPENTRUST_* constants to ETTIC_OTC_* (5 constants) - class-opentrust-*.php to class-ettic-otc-*.php (25 files) - [OpenTrust] debug-log prefix to [ettic-otc] - .phpstan-bootstrap.php updated to match - 5 user-facing brand strings preserved as Open Trust Center Storage identifiers (option keys, postmeta, CPTs, hooks) keep old names for Phase 4. Text domain unchanged for Phase 6.
- Options: opentrust_* to ettic_otc_* (settings, provider_keys, db_version, cache_version, faqs_seeded, site_salt) - Postmeta: _opentrust_* to _ettic_otc_* (59 keys) - Transients: opentrust_* to ettic_otc_* - DB table: wp_opentrust_chat_log to wp_ettic_otc_chat_log - CPT slugs: opentr_* to eotc_* (shorter for 20-char post_type cap) - Cron events: opentrust_* to ettic_otc_* - REST namespace: opentrust/v1 to ettic-otc/v1 - Query var: opentrust to ettic_otc - Admin menu slug: opentrust to ettic-otc - Admin page URLs and screen IDs updated to match - WPML config aligned OpenTrust_CPT::LEGACY_MAP values now point to eotc_* (Phase 8 adds opentr_* entries for full back-compat). Text-domain and JS globals stay for Phases 6 and 9.
- Replace 'opentrust' text-domain literal with
'open-trust-center-by-ettic' across all PHP source (535 calls)
- Rename languages/opentrust.{pot,nl_NL.po,nl_NL.mo} to
languages/open-trust-center-by-ettic.* to match new text domain
- Regenerate POT via wp i18n make-pot (new headers, new file refs)
- msgmerge nl_NL.po against new POT; existing translations carried
forward as fuzzy where strings remained similar
- Update PO header: Project-Id-Version, Last-Translator, X-Domain
- Rebuild MO from updated PO
Fuzzy translations need a human review pass before they go in MO
(msgfmt default skips fuzzy). For v1.2.0 ship the .mo as-is; nl_NL
translator can fill in the new strings in a follow-up.
- LEGACY_MAP: add v1.1.x opentr_* slugs alongside v1.0.x ot_* - LEGACY_META_MAP: add v1.1.x _opentrust_* keys alongside v1.0.x _ot_* - Drop LEGACY_POLICY etc. constants (only referenced internally) - validate_manifest() accepts ettic_otc_version or opentrust_version; skips the major-version gate when only the legacy field is present, so v1.x archives are accepted on the v1.2.0 destination - remap_legacy_cpt_keys docstring updated End result: a content/settings export ZIP generated by any v1.0.x or v1.1.x install can be imported into v1.2.0 with full data fidelity. The chain v1.0.x to v1.1.x to v1.2.0 collapses into one hop on the new importer.
Full rename across CSS, JS, and HTML/PHP templates: - @layer opentrust -> @layer ettic-otc - All .ot-* selectors -> .ettic-otc-* (1,170 in CSS) - All --ot-* custom properties -> --ettic-otc-* - All #ot-* ID selectors -> #ettic-otc-* - All [data-ot-*] attribute selectors -> [data-ettic-otc-*] - HTML class attributes class="ot-*" -> class="ettic-otc-*" across templates (170+ references) - JS string-literal selectors and class refs - JS globals: window.OpenTrustAdmin -> window.EtticOTCAdmin, window.OpenTrustCatalog -> window.EtticOTCCatalog - PHP-side emitters (wp_add_inline_script) updated to match PHP variable names $ot_settings, $ot_data etc. left untouched (local-scope template variables, not user-visible identifiers).
readme.txt: - All prose 'OpenTrust' brand references -> 'Open Trust Center' - Technical refs updated: wp_ettic_otc_chat_log, @layer ettic-otc, ettic_otc_subprocessor_catalog filter, _ettic_otc_* postmeta, wp i18n make-pot command, wp.org URLs - Add v1.2.0 changelog entry explaining trademark rename, namespace migration, migration teardown, and the export/import path - Add v1.2.0 upgrade notice mirroring the message - Restore historical accuracy in the v1.1.1 changelog entry (rename was _ot_* to _opentrust_*, not _ettic_otc_*) README.md: matching brand + URL updates, badge slug renames. CLAUDE.md: full namespace pass, version 1.2.0, DB schema 1, LEGACY_MAP / LEGACY_META_MAP back-compat section added, removed references to deleted maybe_upgrade() and the v1-v5 chain.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughRebrand migration replacing OpenTrust/opentrust/ot-* identifiers with Ettic_OTC/ettic_otc/ettic-otc across bootstrap, PHP classes, CPTs/meta, chat stack, providers, admin tools/settings/AI, IO import/export, repository/rendering, JS/CSS assets, translations, and CI workflow. ChangesOpen Trust Center / Ettic OTC migration
Sequence Diagram(s)
Estimated code review effort Possibly related PRs
Suggested labels Suggested reviewers
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (8)
assets/js/chat.js (1)
13-25:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRename the chat history storage key too.
The DOM/config hooks were migrated, but the browser state key is still
opentrust.chat.history. On sites that previously used OpenTrust, Ettic OTC will rehydrate stale transcripts from the old plugin instead of starting from the new namespace/fresh baseline.Suggested change
- var STORAGE_KEY = 'opentrust.chat.history'; + var STORAGE_KEY = 'ettic_otc.chat.history';🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@assets/js/chat.js` around lines 13 - 25, The storage key constant STORAGE_KEY currently uses the old namespace 'opentrust.chat.history' and causes rehydration of legacy transcripts; update STORAGE_KEY to a new Ettic OTC-specific key (e.g., 'ettic.otc.chat.history' or similar) wherever STORAGE_KEY is defined/used (reference the STORAGE_KEY variable and the string 'opentrust.chat.history' in this file) so the app reads/writes to the new key and no longer pulls old OpenTrust data.includes/class-ettic-otc-io.php (2)
641-668:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftOverwrite imports leave removed meta behind.
In overwrite mode this loop only writes keys present in the manifest. If a source record cleared a managed field, the destination keeps the old value because nothing deletes the now-missing meta key. That means imports can silently preserve stale review dates, citations, related-policy refs, etc. instead of matching the source.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@includes/class-ettic-otc-io.php` around lines 641 - 668, When running in overwrite mode the code only updates keys present in $meta, leaving previously-existing managed meta on the destination; change the logic so that when $strategy !== self::STRATEGY_CREATE_NEW you first collect the managed meta keys for the current $cpt (from self::ATTACHMENT_META_KEYS[$cpt] and self::POST_REF_META_KEYS[$cpt] or any other managed key lists), read existing values for those keys on $post_id (get_post_meta or get_post_custom_keys), and call delete_post_meta($post_id, $key) for any managed key that exists on the destination but is missing from $meta, then proceed with the existing update_post_meta loop; reference the $meta variable, $post_id, self::ATTACHMENT_META_KEYS, self::POST_REF_META_KEYS, $strategy and self::STRATEGY_CREATE_NEW to locate where to add this deletion step.
775-785:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winInclude the legacy attachment hash key in dedupe lookups.
Legacy archives are supported, but attachment dedupe now only checks
_ettic_otc_import_sha256. Media previously imported by the old plugin under_opentrust_import_sha256will be uploaded again instead of being reused.Suggested change
$hits = get_posts([ 'post_type' => 'attachment', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', 'meta_query' => [ - ['key' => '_ettic_otc_import_sha256', 'value' => $hash], + 'relation' => 'OR', + ['key' => '_ettic_otc_import_sha256', 'value' => $hash], + ['key' => '_opentrust_import_sha256', 'value' => $hash], ], ]);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@includes/class-ettic-otc-io.php` around lines 775 - 785, The dedupe lookup in find_attachment_by_hash currently only checks meta key '_ettic_otc_import_sha256' so legacy imports using '_opentrust_import_sha256' are missed; update find_attachment_by_hash to query attachments where either '_ettic_otc_import_sha256' OR '_opentrust_import_sha256' equals the provided $hash (use a meta_query with 'relation' => 'OR' and two clauses for those keys) and return the first matching ID as before.assets/js/admin.js (2)
839-849:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winThe version-summary toggle is still bound to the old element IDs.
The meta box now renders
ettic_otc_publish_new_versionandettic_otc_version_summary, but this handler still queriesopentrust_*. The “What changed?” field never opens or receives focus.💡 Suggested fix
- var toggle = document.getElementById('opentrust_publish_new_version'); + var toggle = document.getElementById('ettic_otc_publish_new_version'); @@ - var summary = document.getElementById('opentrust_version_summary'); + var summary = document.getElementById('ettic_otc_version_summary');🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@assets/js/admin.js` around lines 839 - 849, The handler queries old element IDs ('opentrust_publish_new_version' and 'opentrust_version_summary') so the toggle never opens or focuses the new inputs; update the ID lookups used by toggle and summary to the new IDs ('ettic_otc_publish_new_version' for the toggle and 'ettic_otc_version_summary' for the summary) while leaving the existing wrap ID ('ettic-otc-version-summary-wrap') and the change handler logic (this.checked, display toggle, summary.focus()) intact.
447-450:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCatalog autofill still maps legacy meta IDs.
The renamed CPT fields in this PR are
ettic_otc_*, butmetaKeyToDomId()still rewrites_opentrust_*toopentrust_*. That meansapplyField()no longer finds the renamed inputs/tag containers, so choosing a catalog entry stops autofilling the new-post forms.💡 Suggested fix
- // leading underscore: `_opentrust_cert_type` → `opentrust_cert_type`. + // leading underscore: `_ettic_otc_cert_type` → `ettic_otc_cert_type`. var metaKeyToDomId = function (metaKey) { - return metaKey.replace(/^_opentrust_/, 'opentrust_'); + return metaKey.replace(/^_ettic_otc_/, 'ettic_otc_'); };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@assets/js/admin.js` around lines 447 - 450, metaKeyToDomId currently only rewrites legacy `_opentrust_` keys to `opentrust_`, so applyField can't find the new `ettic_otc_*` inputs; update metaKeyToDomId to handle both prefixes: if metaKey matches /^_opentrust_(.*)/ return 'ettic_otc_' + that capture, otherwise strip a single leading underscore (e.g. metaKey.replace(/^_/, '')) so `_ettic_otc_*` becomes `ettic_otc_*` and other keys still map correctly; reference the metaKeyToDomId function and ensure applyField usage continues to find the renamed inputs.includes/class-ettic-otc-admin-ai.php (1)
688-703:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSwitching providers can leave an invalid model active.
This block updates
ai_providerimmediately, but it only re-picksai_modelwhen the old setting was empty. If the site was on Anthropic and the admin validates a different provider, the old Anthropic model id stays active until Step 2 is saved, so chat requests can start failing against the new provider.💡 Suggested fix
- $settings = Ettic_OTC::get_settings(); + $settings = Ettic_OTC::get_settings(); + $previous_provider = (string) ($settings['ai_provider'] ?? ''); $settings['ai_enabled'] = true; $settings['ai_provider'] = $provider; $settings['ai_model_list_cached_at'] = time(); - if (empty($settings['ai_model'])) { + if ( + $previous_provider !== $provider || + $this->find_model_meta((string) ($settings['ai_model'] ?? ''), $result['models']) === null + ) { + $settings['ai_model'] = ''; foreach ($result['models'] as $model) { if (!empty($model['recommended'])) { $settings['ai_model'] = $model['id']; break; } } if (empty($settings['ai_model']) && !empty($result['models'][0]['id'])) { $settings['ai_model'] = $result['models'][0]['id']; } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@includes/class-ettic-otc-admin-ai.php` around lines 688 - 703, The code updates ai_provider immediately but only re-selects ai_model when the prior setting is empty, which can leave an incompatible model id active after switching providers; modify the logic in the block around Ettic_OTC::get_settings() so that when $settings['ai_provider'] is different from the newly validated $provider you either clear/reset $settings['ai_model'] or re-select a recommended/default model from $result['models'] unconditionally (use the existing loop that picks recommended model or fallback to $result['models'][0]['id']), then call $this->snapshot_active_model($settings, $result['models']) with the updated settings; ensure comparison against the previous provider value before overwriting ai_provider so provider-change triggers model repick.assets/css/admin.css (1)
900-912:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winThe unavailable-model indicator lost its styles in the rename.
render_ai_settings_form()now renders.ettic-otc-ai-model-unavailable, but this block still targets.opentrust-ai-model-unavailable. The warning icon/text will show up unstyled on the AI tab.💡 Suggested fix
-.opentrust-ai-model-unavailable { +.ettic-otc-ai-model-unavailable { display: inline-flex; align-items: center; gap: 6px; margin-left: 8px; vertical-align: middle; } -.opentrust-ai-model-unavailable .description { +.ettic-otc-ai-model-unavailable .description { margin: 0; color: `#b91c1c`; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@assets/css/admin.css` around lines 900 - 912, The CSS selectors for the unavailable-model indicator were not updated to match render_ai_settings_form(); change the selector(s) in admin.css so the rules currently under .opentrust-ai-model-unavailable apply to the class rendered by render_ai_settings_form(), i.e. .ettic-otc-ai-model-unavailable (or add the new class alongside the old one for compatibility) and ensure the nested .description rule is updated as well so the warning icon/text is styled correctly.includes/class-ettic-otc-admin-tools.php (1)
60-75:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftPreview ZIPs are still web-accessible on non-Apache setups.
These files are stored under
wp-content/uploads/ettic-otc-tmp/. The.htaccesswrite only helps on Apache, and the blankindex.htmldoes not block direct requests to the ZIP itself. On Nginx/CDN-backed installs, preview archives can stay downloadable for 30 minutes even though they may contain sensitive trust-center content. This stash needs to live outside the public uploads tree, or in a temp location the web server never serves.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@includes/class-ettic-otc-admin-tools.php` around lines 60 - 75, The preview ZIP stash must not live under the public uploads tree; change the code that builds $stash_dir and related URL variables to use a non-web-accessible temp directory (e.g. wp_get_temp_dir() or sys_get_temp_dir()) instead of wp_upload_dir()/$upload_dir baseurl logic, remove or stop computing $base_url/$upload_url for served paths, and keep the hardening writes ('.htaccess' and 'index.html') but ensure files are created in the new $stash_dir with restrictive permissions and still cleaned up after 30 minutes; update any references that use $upload_url or assume the stash is web-visible (functions/variables: $stash_dir, $htaccess, $index, $upload_dir, $base_url, $upload_url).
🧹 Nitpick comments (1)
.phpstan-bootstrap.php (1)
10-12: ⚡ Quick winKeep PHPStan bootstrap version aligned with the release baseline.
ETTIC_OTC_VERSIONis still1.0.0here, while this PR baseline is1.2.0. Syncing this avoids version-gated analysis drift.Patch
if (!defined('ETTIC_OTC_VERSION')) { - define('ETTIC_OTC_VERSION', '1.0.0'); + define('ETTIC_OTC_VERSION', '1.2.0'); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.phpstan-bootstrap.php around lines 10 - 12, Update the PHPStan bootstrap constant ETTIC_OTC_VERSION to match the PR baseline: locate the define('ETTIC_OTC_VERSION', '1.0.0') in .phpstan-bootstrap.php and change the value to '1.2.0' so the bootstrap version aligns with the current release baseline used for analysis.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@assets/js/admin.js`:
- Line 607: markPrefilled() sets the flag using dataset.otPrefilled
(data-ot-prefilled) but clearAllPrefilled() and related code check/clear
data-ettic-otc-prefilled, so change the read/clear logic to match: update
clearAllPrefilled() (and any selectors/loops in the nearby block handling
prefilled fields) to look for and remove dataset.otPrefilled (or
[data-ot-prefilled]) and ensure removal uses delete fieldEl.dataset.otPrefilled
so the attribute is removed; alternatively, if you prefer the ettic- name,
change markPrefilled() to set dataset.etticOtcPrefilled—pick one naming
convention and make markPrefilled, clearAllPrefilled and any selectors
consistent.
In `@assets/js/frontend.js`:
- Around line 151-153: The click handler dereferences DOM nodes without null
checks (uses this.closest('.ettic-otc-dp-card') into variable card and then
card.querySelector('.ettic-otc-dp-card__list--overflow') into overflow), which
can throw if markup is missing; update the handler to guard: verify card is
non-null before calling querySelector, and verify overflow is non-null before
accessing or manipulating it (and bail out or handle gracefully if absent), and
apply the same null-guard pattern to the analogous code that references these
variables elsewhere in the handler so no DOM access occurs without presence
checks.
In `@includes/class-ettic-otc-admin-questions.php`:
- Around line 233-236: The export is truncated because
Ettic_OTC_Chat_Log::query($filters) enforces a per_page hard cap of 100, so
setting 'per_page' => 10000 is ignored; update the export handler in
class-ettic-otc-admin-questions.php to either page through results (call
Ettic_OTC_Chat_Log::query with incrementing 'page' until an empty page is
returned and append rows) or introduce a dedicated unpaginated/export mode (add
a flag in $filters like 'no_pagination' or use a new
Ettic_OTC_Chat_Log::query_all method that bypasses the 100 cap) and use that for
CSV exports; ensure you reference and update the 'per_page' and 'page' fields in
the filters and keep behavior unchanged for normal paginated requests.
- Around line 183-185: The pagination link is built by merging $filters with
['page' => 'ettic-otc-questions'], but $filters already contains a numeric
'page' key and a 'search' key, which breaks the admin slug and search param;
instead, construct the query args explicitly: create a new array for
add_query_arg that sets 'page' => 'ettic-otc-questions' and the pagination param
(e.g. 'paged' or the expected page-number key) and only include the proper
search param 'q' from $filters if present (do not reuse the entire $filters
array), then call add_query_arg(...) and remove_query_arg('paged', $base) as
before.
In `@includes/class-ettic-otc-admin-review.php`:
- Around line 89-90: Replace occurrences of the internal namespace string
"Ettic_OTC" used in translation calls with the user-facing product name "Open
Trust Center by Ettic" so admin-facing UI copy matches the rebrand; locate the
__('Ettic_OTC is built and maintained in the open. If it is helping your team, a
%s keeps the project moving.', 'open-trust-center-by-ettic') call (and the
similar translation around the $link) in class-ettic-otc-admin-review.php and
update the first string argument to "Open Trust Center by Ettic is built and
maintained in the open. If it is helping your team, a %s keeps the project
moving." (and likewise replace other instances around the second occurrence you
noted) ensuring you preserve translation function usage and the %s placeholder.
In `@includes/class-ettic-otc-admin-settings.php`:
- Around line 299-305: The settings label still reads "Powered by Open Trust
Center"; update the literal passed to esc_html__ in the printf that renders the
checkbox label (the one using id="ettic_otc_show_powered_by" /
name="ettic_otc_settings[show_powered_by]") to use the new product name (e.g.,
"Powered by <NEW_PRODUCT_NAME>") so the settings UI no longer shows the old
brand; keep the same translation function and domain.
In `@includes/class-ettic-otc-chat-secrets.php`:
- Around line 156-157: The forget() method currently always returns true after
calling update_option('ettic_otc_provider_keys', $stored, false); which can hide
failures; change it to capture the return value from update_option (e.g., $saved
= update_option(...)) and return that (or a boolean cast of it) instead of
unconditionally returning true so callers receive the real persistence result
from forget().
In `@includes/class-ettic-otc-cpt.php`:
- Around line 220-227: The deletion callback $on_post_event currently takes only
$post_id and calls get_post_type((int)$post_id) which fails for deleted_post
because the post is already removed; update the closure signature to accept the
second parameter (e.g. function ($post_id, $post) use ($cpts, $callback)) and
check in_array($post->post_type, $cpts, true) before invoking
$callback($post_id), and when adding the hook with add_action('deleted_post',
$on_post_event) set the accepted args to 2; alternatively, if you prefer
filtering before removal, attach $on_post_event to before_delete_post and keep
the post_id lookup there.
In `@README.md`:
- Line 63: Update the README privacy sentence under "No PII in logs" to correct
the typo: change the word "referers" to the proper spelling "referrers" in the
sentence referencing the optional `wp_ettic_otc_chat_log` table so it reads
"...never raw IPs, emails, sessions, user agents, or referrers."
- Line 3: Update the README title and any primary branding occurrences to the
official name: replace the heading "Open Trust Center" with "Open Trust Center
by Ettic" in README.md and search for and update other top-level mentions (e.g.,
the main H1/title string) so the repo's primary branding is consistent across
the project.
---
Outside diff comments:
In `@assets/css/admin.css`:
- Around line 900-912: The CSS selectors for the unavailable-model indicator
were not updated to match render_ai_settings_form(); change the selector(s) in
admin.css so the rules currently under .opentrust-ai-model-unavailable apply to
the class rendered by render_ai_settings_form(), i.e.
.ettic-otc-ai-model-unavailable (or add the new class alongside the old one for
compatibility) and ensure the nested .description rule is updated as well so the
warning icon/text is styled correctly.
In `@assets/js/admin.js`:
- Around line 839-849: The handler queries old element IDs
('opentrust_publish_new_version' and 'opentrust_version_summary') so the toggle
never opens or focuses the new inputs; update the ID lookups used by toggle and
summary to the new IDs ('ettic_otc_publish_new_version' for the toggle and
'ettic_otc_version_summary' for the summary) while leaving the existing wrap ID
('ettic-otc-version-summary-wrap') and the change handler logic (this.checked,
display toggle, summary.focus()) intact.
- Around line 447-450: metaKeyToDomId currently only rewrites legacy
`_opentrust_` keys to `opentrust_`, so applyField can't find the new
`ettic_otc_*` inputs; update metaKeyToDomId to handle both prefixes: if metaKey
matches /^_opentrust_(.*)/ return 'ettic_otc_' + that capture, otherwise strip a
single leading underscore (e.g. metaKey.replace(/^_/, '')) so `_ettic_otc_*`
becomes `ettic_otc_*` and other keys still map correctly; reference the
metaKeyToDomId function and ensure applyField usage continues to find the
renamed inputs.
In `@assets/js/chat.js`:
- Around line 13-25: The storage key constant STORAGE_KEY currently uses the old
namespace 'opentrust.chat.history' and causes rehydration of legacy transcripts;
update STORAGE_KEY to a new Ettic OTC-specific key (e.g.,
'ettic.otc.chat.history' or similar) wherever STORAGE_KEY is defined/used
(reference the STORAGE_KEY variable and the string 'opentrust.chat.history' in
this file) so the app reads/writes to the new key and no longer pulls old
OpenTrust data.
In `@includes/class-ettic-otc-admin-ai.php`:
- Around line 688-703: The code updates ai_provider immediately but only
re-selects ai_model when the prior setting is empty, which can leave an
incompatible model id active after switching providers; modify the logic in the
block around Ettic_OTC::get_settings() so that when $settings['ai_provider'] is
different from the newly validated $provider you either clear/reset
$settings['ai_model'] or re-select a recommended/default model from
$result['models'] unconditionally (use the existing loop that picks recommended
model or fallback to $result['models'][0]['id']), then call
$this->snapshot_active_model($settings, $result['models']) with the updated
settings; ensure comparison against the previous provider value before
overwriting ai_provider so provider-change triggers model repick.
In `@includes/class-ettic-otc-admin-tools.php`:
- Around line 60-75: The preview ZIP stash must not live under the public
uploads tree; change the code that builds $stash_dir and related URL variables
to use a non-web-accessible temp directory (e.g. wp_get_temp_dir() or
sys_get_temp_dir()) instead of wp_upload_dir()/$upload_dir baseurl logic, remove
or stop computing $base_url/$upload_url for served paths, and keep the hardening
writes ('.htaccess' and 'index.html') but ensure files are created in the new
$stash_dir with restrictive permissions and still cleaned up after 30 minutes;
update any references that use $upload_url or assume the stash is web-visible
(functions/variables: $stash_dir, $htaccess, $index, $upload_dir, $base_url,
$upload_url).
In `@includes/class-ettic-otc-io.php`:
- Around line 641-668: When running in overwrite mode the code only updates keys
present in $meta, leaving previously-existing managed meta on the destination;
change the logic so that when $strategy !== self::STRATEGY_CREATE_NEW you first
collect the managed meta keys for the current $cpt (from
self::ATTACHMENT_META_KEYS[$cpt] and self::POST_REF_META_KEYS[$cpt] or any other
managed key lists), read existing values for those keys on $post_id
(get_post_meta or get_post_custom_keys), and call delete_post_meta($post_id,
$key) for any managed key that exists on the destination but is missing from
$meta, then proceed with the existing update_post_meta loop; reference the $meta
variable, $post_id, self::ATTACHMENT_META_KEYS, self::POST_REF_META_KEYS,
$strategy and self::STRATEGY_CREATE_NEW to locate where to add this deletion
step.
- Around line 775-785: The dedupe lookup in find_attachment_by_hash currently
only checks meta key '_ettic_otc_import_sha256' so legacy imports using
'_opentrust_import_sha256' are missed; update find_attachment_by_hash to query
attachments where either '_ettic_otc_import_sha256' OR
'_opentrust_import_sha256' equals the provided $hash (use a meta_query with
'relation' => 'OR' and two clauses for those keys) and return the first matching
ID as before.
---
Nitpick comments:
In @.phpstan-bootstrap.php:
- Around line 10-12: Update the PHPStan bootstrap constant ETTIC_OTC_VERSION to
match the PR baseline: locate the define('ETTIC_OTC_VERSION', '1.0.0') in
.phpstan-bootstrap.php and change the value to '1.2.0' so the bootstrap version
aligns with the current release baseline used for analysis.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 706252c5-398c-4fcc-8b57-06a55ad6aec5
📒 Files selected for processing (60)
.phpstan-bootstrap.phpREADME.mdassets/css/admin.cssassets/css/chat.cssassets/css/frontend.cssassets/js/admin.jsassets/js/chat.jsassets/js/frontend.jsincludes/class-ettic-otc-admin-ai.phpincludes/class-ettic-otc-admin-questions.phpincludes/class-ettic-otc-admin-review.phpincludes/class-ettic-otc-admin-settings.phpincludes/class-ettic-otc-admin-tools.phpincludes/class-ettic-otc-admin.phpincludes/class-ettic-otc-catalog.phpincludes/class-ettic-otc-chat-budget.phpincludes/class-ettic-otc-chat-corpus.phpincludes/class-ettic-otc-chat-log.phpincludes/class-ettic-otc-chat-search.phpincludes/class-ettic-otc-chat-secrets.phpincludes/class-ettic-otc-chat-stream-collector.phpincludes/class-ettic-otc-chat-summarizer.phpincludes/class-ettic-otc-chat.phpincludes/class-ettic-otc-cpt.phpincludes/class-ettic-otc-io.phpincludes/class-ettic-otc-render.phpincludes/class-ettic-otc-repository.phpincludes/class-ettic-otc-version.phpincludes/class-ettic-otc.phpincludes/class-opentrust-cpt.phpincludes/data/certification-catalog.phpincludes/data/data-practice-catalog.phpincludes/data/faq-catalog.phpincludes/data/subprocessor-catalog.phpincludes/providers/class-ettic-otc-chat-provider-anthropic.phpincludes/providers/class-ettic-otc-chat-provider-openai.phpincludes/providers/class-ettic-otc-chat-provider-openrouter.phpincludes/providers/class-ettic-otc-chat-provider.phplanguages/open-trust-center-by-ettic-nl_NL.molanguages/open-trust-center-by-ettic-nl_NL.polanguages/open-trust-center-by-ettic.potlanguages/opentrust-nl_NL.molanguages/opentrust-nl_NL.poopen-trust-center-by-ettic.phpopentrust.phpreadme.txttemplates/chat.phptemplates/partials/certifications.phptemplates/partials/chat-budget-exhausted.phptemplates/partials/chat-empty-state.phptemplates/partials/contact.phptemplates/partials/data-practices.phptemplates/partials/faq.phptemplates/partials/hero.phptemplates/partials/policies.phptemplates/partials/policy-single.phptemplates/partials/subprocessors.phptemplates/trust-center.phpuninstall.phpwpml-config.xml
💤 Files with no reviewable changes (1)
- includes/class-opentrust-cpt.php
| var card = this.closest('.ettic-otc-dp-card'); | ||
| var overflow = card.querySelector('.ettic-otc-dp-card__list--overflow'); | ||
| if (overflow) { |
There was a problem hiding this comment.
Add null guards before DOM dereferences in click handlers.
Line 152 and Line 193 assume required sibling/container elements always exist. If markup drifts, these throw and can break the rest of the frontend interactions.
Suggested hardening
document.querySelectorAll('[data-ettic-otc-dp-more]').forEach(function (btn) {
btn.addEventListener('click', function () {
var card = this.closest('.ettic-otc-dp-card');
- var overflow = card.querySelector('.ettic-otc-dp-card__list--overflow');
+ if (!card) return;
+ var overflow = card.querySelector('.ettic-otc-dp-card__list--overflow');
if (overflow) {
overflow.hidden = false;
}
this.classList.add('ettic-otc-dp-card__more--hidden');
});
});
@@
document.querySelectorAll('[data-ettic-otc-clamp-toggle]').forEach(function (btn) {
btn.addEventListener('click', function () {
var text = this.previousElementSibling;
+ if (!text || !text.classList) return;
var expanded = text.classList.toggle('ettic-otc-table__clamp-text--expanded');
this.textContent = expanded ? 'less' : 'more';
});
});Also applies to: 192-194
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@assets/js/frontend.js` around lines 151 - 153, The click handler dereferences
DOM nodes without null checks (uses this.closest('.ettic-otc-dp-card') into
variable card and then card.querySelector('.ettic-otc-dp-card__list--overflow')
into overflow), which can throw if markup is missing; update the handler to
guard: verify card is non-null before calling querySelector, and verify overflow
is non-null before accessing or manipulating it (and bail out or handle
gracefully if absent), and apply the same null-guard pattern to the analogous
code that references these variables elsewhere in the handler so no DOM access
occurs without presence checks.
There was a problem hiding this comment.
Skipping — adding defensive null guards for hypothetical markup drift is YAGNI; the project convention (per CLAUDE.md) is to trust internal-only DOM contracts and validate at boundaries instead.
There was a problem hiding this comment.
@r00bbert, understood — happy to defer to the CLAUDE.md boundary-validation convention. I'll skip similar suggestions on internal DOM contracts going forward.
(ʕ •ᴥ• ʔ)⌒☆
✏️ Learnings added
Learnt from: r00bbert
Repo: EtticDevelopment/opentrust PR: 28
File: assets/js/frontend.js:151-153
Timestamp: 2026-05-17T12:58:25.277Z
Learning: In the EtticDevelopment/opentrust repository, the project convention (documented in CLAUDE.md) is to trust internal-only DOM contracts and validate at boundaries instead of adding defensive null guards throughout. Suggestions for null/undefined guards on DOM elements that are always rendered together (e.g., card containers and their children in frontend.js) should be skipped as YAGNI.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
| 'per_page' => 10000, // hard cap — nobody exports >10k rows per page | ||
| ]; | ||
|
|
||
| $result = OpenTrust_Chat_Log::query($filters); | ||
| $result = Ettic_OTC_Chat_Log::query($filters); |
There was a problem hiding this comment.
Export still truncates after the first 100 rows.
This handler goes through Ettic_OTC_Chat_Log::query(), which hard-caps per_page at 100. Larger CSV exports will silently omit everything after the first page. Either page through the results here or add a dedicated unpaginated export path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@includes/class-ettic-otc-admin-questions.php` around lines 233 - 236, The
export is truncated because Ettic_OTC_Chat_Log::query($filters) enforces a
per_page hard cap of 100, so setting 'per_page' => 10000 is ignored; update the
export handler in class-ettic-otc-admin-questions.php to either page through
results (call Ettic_OTC_Chat_Log::query with incrementing 'page' until an empty
page is returned and append rows) or introduce a dedicated unpaginated/export
mode (add a flag in $filters like 'no_pagination' or use a new
Ettic_OTC_Chat_Log::query_all method that bypasses the 100 cap) and use that for
CSV exports; ensure you reference and update the 'per_page' and 'page' fields in
the filters and keep behavior unchanged for normal paginated requests.
There was a problem hiding this comment.
Skipping — the 100-row cap on Ettic_OTC_Chat_Log::query() is pre-existing behaviour, not introduced by this rename PR. Tracking separately so we keep this PR scoped to the rename.
There was a problem hiding this comment.
@r00bbert, understood — the cap predates this rename PR so keeping it out of scope is the right call. Would you like me to open a GitHub issue to track the export truncation bug as a follow-up?
- phpstan.neon: point at new plugin file - ci.yml POT freshness: rename POT path + slug + domain - ci.yml Version consistency: read new file + ETTIC_OTC_VERSION - ci.yml Plugin Check: pass slug input so text-domain check uses real slug - composer.json: dev-package name follows new slug - admin-questions.php: tag Semgrep false-positive on export URL echo
POT was stale from later edits to class-ettic-otc-io.php; regenerate. Move Semgrep justification to a separate comment and use plain /* nosemgrep: ... */ block form. Semgrep ignores the directive if free-text follows the rule ID on the same comment.
admin.js: rename prefilled tracker writes/deletes to dataset.etticOtcPrefilled so they match the data-ettic-otc-prefilled selector used by clearAllPrefilled. admin-questions.php pagination: build args explicitly instead of unioning \$filters, which kept the numeric page number and dropped the menu slug. cpt.php: use before_delete_post instead of deleted_post so get_post_type() can still resolve the post type before invalidator runs. admin-review.php, admin-settings.php: replace internal Ettic_OTC namespace strings with the user-facing product name in admin UI copy. chat-secrets.php: return the actual update_option() result from forget(). README.md: align primary branding with the new official name; fix typo. POT regenerated.
The previous inline /* nosemgrep */ annotation was not honoured by Semgrep OSS, so extract the URL construction into a private static helper. The helper only sees plain string args, so Semgrep's intraprocedural taint analysis no longer ties the printed URL back to $_GET. Drop the surrounding justification comments along with the suppression. As a bonus, fix the export query-arg name: the form field is 'q' but handle_export() previously read $_GET['search'], so an exported CSV ignored the active search filter. POT refreshed.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@includes/class-ettic-otc-admin-settings.php`:
- Line 226: The UI copy uses the internal class name "Ettic_OTC" in the
translatable string emitted via esc_html_e; update that string to use the
user-facing product name "Open Trust Center by Ettic" instead. Locate the
esc_html_e call in includes/class-ettic-otc-admin-settings.php (the string
currently containing "Ettic_OTC") and replace only the visible product text
portion with "Open Trust Center by Ettic" while keeping the translation domain
'open-trust-center-by-ettic' and escaping call (esc_html_e) intact so
translations and escaping continue to work.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e736ce4d-3e65-4091-a542-b3148a15df7a
📒 Files selected for processing (8)
README.mdassets/js/admin.jsincludes/class-ettic-otc-admin-questions.phpincludes/class-ettic-otc-admin-review.phpincludes/class-ettic-otc-admin-settings.phpincludes/class-ettic-otc-chat-secrets.phpincludes/class-ettic-otc-cpt.phplanguages/open-trust-center-by-ettic.pot
✅ Files skipped from review due to trivial changes (1)
- README.md
🚧 Files skipped from review as they are similar to previous changes (2)
- includes/class-ettic-otc-admin-review.php
- assets/js/admin.js
The renamed Ettic_OTC_IO::validate_manifest() and the admin handler's strict === comparisons against FORMAT_SETTINGS / FORMAT_CONTENT both reject archives produced by OpenTrust v1.0.x and v1.1.x because their format header is "opentrust-settings" / "opentrust-content". Normalise the header on read so downstream code can keep comparing against the new constants without per-site changes. Verified against three real legacy exports (v1.0.0, v1.1.1-beta1 content + settings): all now pass validate_manifest() and remap cleanly through LEGACY_MAP / LEGACY_META_MAP. Marked @deprecated 1.2.0 / drop in 2.0.0 next to the existing legacy CPT and meta remaps.
The full "Open Trust Center" string wraps mid-word in the WP admin sidebar. Use the shorter "Trust Center" for the menu label and keep "Open Trust Center by Ettic" as the screen <title>. POT refreshed.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
includes/class-ettic-otc-io.php (2)
800-807:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCheck the legacy attachment hash meta before re-uploading media.
find_attachment_by_hash()now only queries_ettic_otc_import_sha256. On the documented export-old/import-new path, attachments that were already stamped by the old plugin won't dedupe anymore, so re-imports will create duplicates instead of reusing the existing files.♻️ Proposed fix
private static function find_attachment_by_hash(string $hash): int { $hits = get_posts([ 'post_type' => 'attachment', 'post_status' => 'inherit', 'posts_per_page' => 1, 'fields' => 'ids', 'meta_query' => [ - ['key' => '_ettic_otc_import_sha256', 'value' => $hash], + 'relation' => 'OR', + ['key' => '_ettic_otc_import_sha256', 'value' => $hash], + ['key' => '_opentrust_import_sha256', 'value' => $hash], ], ]); return !empty($hits) ? (int) $hits[0] : 0; }If you reuse a legacy hit, I'd also backfill
_ettic_otc_import_sha256on that attachment so later imports stay on the new key.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@includes/class-ettic-otc-io.php` around lines 800 - 807, find_attachment_by_hash currently only searches for attachments by the new meta key `_ettic_otc_import_sha256`, which misses legacy-stamped attachments; update the function to query both the new key and the legacy key (e.g., `_ettic_otc_import_hash` or whatever legacy meta name is used) and return the attachment ID if either is found, and when a legacy-keyged attachment is returned, backfill the new key on that attachment (via update_post_meta) so future imports hit the new key; reference find_attachment_by_hash and the meta keys `_ettic_otc_import_sha256` and the legacy meta name in your changes.
35-49:⚠️ Potential issue | 🟠 Major | ⚡ Quick winKeep stripping the legacy site-salt key during import.
Line 38 drops the old OpenTrust salt key from
SETTINGS_EXCLUDE, so a legacy settings archive that still carries that field will now be merged intoettic_otc_settingsunchanged. That leaks a source-site secret into the destination site's config.🔒 Proposed fix
public const SETTINGS_EXCLUDE = [ 'turnstile_secret_key', + 'opentrust_site_salt', 'ettic_otc_site_salt', 'ai_enabled', 'ai_provider',🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@includes/class-ettic-otc-io.php` around lines 35 - 49, The legacy OpenTrust site-salt key was removed from the SETTINGS_EXCLUDE list causing archived settings to reintroduce a secret; restore the legacy salt key string (e.g. "opentrust_site_salt") into the public const SETTINGS_EXCLUDE in class Ettic_Otc_Io so that import/merge logic continues to strip it from incoming archives and never merges it into ettic_otc_settings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@includes/class-ettic-otc-io.php`:
- Around line 800-807: find_attachment_by_hash currently only searches for
attachments by the new meta key `_ettic_otc_import_sha256`, which misses
legacy-stamped attachments; update the function to query both the new key and
the legacy key (e.g., `_ettic_otc_import_hash` or whatever legacy meta name is
used) and return the attachment ID if either is found, and when a legacy-keyged
attachment is returned, backfill the new key on that attachment (via
update_post_meta) so future imports hit the new key; reference
find_attachment_by_hash and the meta keys `_ettic_otc_import_sha256` and the
legacy meta name in your changes.
- Around line 35-49: The legacy OpenTrust site-salt key was removed from the
SETTINGS_EXCLUDE list causing archived settings to reintroduce a secret; restore
the legacy salt key string (e.g. "opentrust_site_salt") into the public const
SETTINGS_EXCLUDE in class Ettic_Otc_Io so that import/merge logic continues to
strip it from incoming archives and never merges it into ettic_otc_settings.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e6521544-b191-4190-a24e-92209bed4352
📒 Files selected for processing (3)
includes/class-ettic-otc-admin.phpincludes/class-ettic-otc-io.phplanguages/open-trust-center-by-ettic.pot
🚧 Files skipped from review as they are similar to previous changes (1)
- includes/class-ettic-otc-admin.php
Rename swapped product name to Ettic_OTC class name in 4 admin strings. Restore Open Trust Center.
Closes #27
Why
WordPress.org plugin review flagged
OpenTrustas a trademark conflict (DocuSign holds registeredOpenTrustword marks in software classes in DE/FR/UK). After back-and-forth the reviewer accepted "Open Trust Center by Ettic" as the new name and strongly recommended migrating internal identifiers off the trademarked term as well. This PR does both.After this lands, the codebase contains zero references to
opentrustoutside the changelog and the importer's legacy-back-compat translation table.What changed
Phased commits (one per phase):
opentrust.php→open-trust-center-by-ettic.php, header + version 1.2.0 + text domainopen-trust-center-by-etticOpenTrust_*→Ettic_OTC_*, 5 constantsOPENTRUST_*→ETTIC_OTC_*, 29 source files renamedclass-opentrust-*.php→class-ettic-otc-*.phpopentrust_*→ettic_otc_*, DB tablewp_opentrust_chat_log→wp_ettic_otc_chat_log, CPT slugsopentr_*→eotc_*(shorter because of WP's 20-charregister_post_typecap), cron events, REST namespaceopentrust/v1→ettic-otc/v1, admin menu slugopentrust→ettic-otc, query varopentrust→ettic_otc.pomsgmergedEttic_OTC_CPT::LEGACY_MAPandLEGACY_META_MAPextended to accept both v1.0.x (ot_*,_ot_*) and v1.1.x (opentr_*,_opentrust_*) archive identifiers;validate_manifest()accepts the legacyopentrust_versionfield and skips the major-version gate when only the legacy field is present@layer opentrust→@layer ettic-otc,.ot-*selectors →.ettic-otc-*,--ot-*custom properties →--ettic-otc-*, JS globalsOpenTrustAdmin/OpenTrustCatalog→EtticOTCAdmin/EtticOTCCatalog, all HTML class attributes in templates (~1,750 lines)Migration path for existing users
There is no in-place upgrade from the old
opentrustplugin. Path:The new importer recognizes v1.0.x AND v1.1.x archive formats. All postmeta, CPT slugs, and option keys are remapped to the new namespace on read.
Manual QA checklist (for the WP Studio dev site)
eotc_*slug — add a sample post in each/trust-center/— page renders with seeded contentopentrustv1.1.1 plugin → all data lands under new identifierswp plugin check open-trust-center-by-ettic --categories=plugin_repo→ 0 errorsPlanning artifacts (gitignored, local-only)
Under
wporg-review/:PLAN.md— master execution planinventory-namespace.md— complete identifier inventoryimport-export-backcompat.md— 58 back-compat anchorsmigration-teardown.md— what was deleted and whyrename-mechanics.md— wp.org rename mechanicsSummary by CodeRabbit
Refactor
Style
New Features
Documentation