Skip to content

Rename to Open Trust Center by Ettic + migrate internal namespace#28

Merged
r00bbert merged 18 commits into
mainfrom
rename/ettic-otc-namespace
May 21, 2026
Merged

Rename to Open Trust Center by Ettic + migrate internal namespace#28
r00bbert merged 18 commits into
mainfrom
rename/ettic-otc-namespace

Conversation

@r00bbert
Copy link
Copy Markdown
Collaborator

@r00bbert r00bbert commented May 17, 2026

Closes #27

Why

WordPress.org plugin review flagged OpenTrust as a trademark conflict (DocuSign holds registered OpenTrust word 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 opentrust outside the changelog and the importer's legacy-back-compat translation table.

What changed

Phased commits (one per phase):

  1. Plugin metadata + main fileopentrust.phpopen-trust-center-by-ettic.php, header + version 1.2.0 + text domain open-trust-center-by-ettic
  2. Migration teardown — deleted v1→v5 in-place migration chain (~180 LOC); fresh-install semantics on activation
  3. PHP identifiers — 25 classes OpenTrust_*Ettic_OTC_*, 5 constants OPENTRUST_*ETTIC_OTC_*, 29 source files renamed class-opentrust-*.phpclass-ettic-otc-*.php
  4. Storage identifiers — options/postmeta/transients/hooks opentrust_*ettic_otc_*, DB table wp_opentrust_chat_logwp_ettic_otc_chat_log, CPT slugs opentr_*eotc_* (shorter because of WP's 20-char register_post_type cap), cron events, REST namespace opentrust/v1ettic-otc/v1, admin menu slug opentrustettic-otc, query var opentrustettic_otc
  5. Text domain (+ translations) — 535 i18n calls swapped; language files renamed and POT regenerated; nl_NL .po msgmerged
  6. Importer back-compatEttic_OTC_CPT::LEGACY_MAP and LEGACY_META_MAP extended to accept both v1.0.x (ot_*, _ot_*) and v1.1.x (opentr_*, _opentrust_*) archive identifiers; validate_manifest() accepts the legacy opentrust_version field and skips the major-version gate when only the legacy field is present
  7. CSS + JS full rename@layer opentrust@layer ettic-otc, .ot-* selectors → .ettic-otc-*, --ot-* custom properties → --ettic-otc-*, JS globals OpenTrustAdmin/OpenTrustCatalogEtticOTCAdmin/EtticOTCCatalog, all HTML class attributes in templates (~1,750 lines)
  8. Docs — readme.txt, README.md updated; v1.2.0 changelog + upgrade notice

Migration path for existing users

There is no in-place upgrade from the old opentrust plugin. Path:

  1. On the old plugin: Open Trust Center → Settings → Import & Export → export content ZIP + settings ZIP.
  2. Deactivate / delete the old plugin.
  3. Install Open Trust Center by Ettic.
  4. Open Trust Center → Settings → Import & Export → import the ZIPs.

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)

  • Activate plugin on a fresh WP — no fatals, default settings populated, default FAQs seeded
  • Each of the 5 CPTs registers under new eotc_* slug — add a sample post in each
  • Visit /trust-center/ — page renders with seeded content
  • Visit a policy single page — renders, version history works
  • Settings page loads (General / Contact / AI Chat / Import & Export tabs)
  • AI chat works end-to-end (Anthropic provider)
  • Export content + settings ZIPs from this plugin → re-import into a fresh install — idempotent
  • Legacy back-compat: import a content ZIP exported from the old opentrust v1.1.1 plugin → all data lands under new identifiers
  • Set site locale to nl_NL → Dutch labels visible (some new strings will be untranslated — expected for v1.2.0; translator pass to follow)
  • Run wp plugin check open-trust-center-by-ettic --categories=plugin_repo → 0 errors

Planning artifacts (gitignored, local-only)

Under wporg-review/:

  • PLAN.md — master execution plan
  • inventory-namespace.md — complete identifier inventory
  • import-export-backcompat.md — 58 back-compat anchors
  • migration-teardown.md — what was deleted and why
  • rename-mechanics.md — wp.org rename mechanics

Summary by CodeRabbit

  • Refactor

    • Complete rebrand to “Open Trust Center by Ettic”: plugin name, admin menus, settings pages, admin screens, and internal content types migrated.
  • Style

    • Overhauled admin, chat, and frontend UI styling and layout to match new branding; updated interaction states and responsive behavior.
  • New Features

    • New CPT-driven content model and enhanced admin meta/UIs for policies, certifications, subprocessors, data practices, and FAQs.
  • Documentation

    • README updated with new installation/navigation steps and Settings path.
    • Dutch (nl_NL) translation catalog added.

Review Change Stack

r00bbert added 8 commits May 17, 2026 14:13
- 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.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Rebrand 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.

Changes

Open Trust Center / Ettic OTC migration

Layer / File(s) Summary
Comprehensive migration (single checkpoint)
* (multiple files across the repo)`
All identifiers, constants, classes, options, CPT slugs, meta keys, REST namespace, cron hooks, IO manifest headers, CSS/JS selectors, translation domain and CI references were renamed/migrated to the Ettic OTC equivalents; import manifest aliasing and legacy meta remapping preserved backwards compatibility.
Bootstrap & packaging
.phpstan-bootstrap.php, composer.json, .github/workflows/ci.yml, README.md
Define ETTIC_OTC_* bootstrap constants, update composer name/description, CI plugin slug/POT/version checks, and README branding/install text.
CPTs & content model
includes/class-ettic-otc-cpt.php, includes/class-ettic-otc-version.php, includes/class-ettic-otc-io.php
Add Ettic_OTC_CPT, migrate meta keys/legacy mappings, version bump logic, IO remapping, manifest format identifiers, legacy format aliasing, and UUID/meta key swaps for import/export compatibility.
Chat stack & providers
includes/class-ettic-otc-chat.php, includes/class-ettic-otc-chat-corpus.php, includes/class-ettic-otc-chat-budget.php, includes/class-ettic-otc-chat-search.php, includes/class-ettic-otc-chat-log.php, includes/class-ettic-otc-chat-stream-collector.php, includes/class-ettic-otc-chat-summarizer.php, includes/providers/*
Rename chat REST namespace and orchestration, switch corpus/repository/search/budget/log keys and cron hooks, update stream collector and summarizer, and rename provider classes and factory wiring to Ettic_OTC_*.
Admin & settings / AI / Tools / Questions / Review
includes/class-ettic-otc-admin.php, includes/class-ettic-otc-admin-settings.php, includes/class-ettic-otc-admin-ai.php, includes/class-ettic-otc-admin-tools.php, includes/class-ettic-otc-admin-questions.php, includes/class-ettic-otc-admin-review.php
Migrate admin orchestrator, settings registration/fields, AI provider/key flows, import/export UI and handlers, questions CSV/clear/toggle endpoints, and review prompt wiring and text domains to Ettic naming.
Repository & render
includes/class-ettic-otc-repository.php, includes/class-ettic-otc-render.php
Update read-side repository to query Ettic CPTs, migrate render helpers, policy meta keys, template includes, and policy version/history rendering to Ettic keys and text domain.
Assets: frontend / chat / admin JS & CSS
assets/css/*, assets/js/*
Rename CSS layer and class prefixes to ettic-otc-*, migrate CSS variables/keyframes, and update all JS DOM selectors and globals to ettic-otc-*/window.EtticOTC*.
Import/Export & IO compatibility
includes/class-ettic-otc-io.php
Preserve import compatibility by accepting legacy opentrust_* manifest headers via aliasing, remap legacy CPT/meta keys on import, and write Ettic manifest headers.
Translations & locale
languages/open-trust-center-by-ettic-nl_NL.po, README pot command
Add/replace Dutch .po for open-trust-center-by-ettic and update POT generation name/path.

Sequence Diagram(s)

  • (none generated)

Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels
enhancement

Suggested reviewers

  • nolderoos

"A rabbit hops with a tidy plan,
I nibble keys and change the brand.
New names sprout in code and tree,
Hop—Ettic blooms — hip, hop, wee! 🥕"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rename/ettic-otc-namespace

Comment thread includes/class-ettic-otc-admin-questions.php Dismissed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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 win

Rename 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 lift

Overwrite 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 win

Include 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_sha256 will 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 win

The version-summary toggle is still bound to the old element IDs.

The meta box now renders ettic_otc_publish_new_version and ettic_otc_version_summary, but this handler still queries opentrust_*. 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 win

Catalog autofill still maps legacy meta IDs.

The renamed CPT fields in this PR are ettic_otc_*, but metaKeyToDomId() still rewrites _opentrust_* to opentrust_*. That means applyField() 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 win

Switching providers can leave an invalid model active.

This block updates ai_provider immediately, but it only re-picks ai_model when 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 win

The 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 lift

Preview ZIPs are still web-accessible on non-Apache setups.

These files are stored under wp-content/uploads/ettic-otc-tmp/. The .htaccess write only helps on Apache, and the blank index.html does 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 win

Keep PHPStan bootstrap version aligned with the release baseline.

ETTIC_OTC_VERSION is still 1.0.0 here, while this PR baseline is 1.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

📥 Commits

Reviewing files that changed from the base of the PR and between 1e8f78d and e87cc46.

📒 Files selected for processing (60)
  • .phpstan-bootstrap.php
  • README.md
  • assets/css/admin.css
  • assets/css/chat.css
  • assets/css/frontend.css
  • assets/js/admin.js
  • assets/js/chat.js
  • assets/js/frontend.js
  • includes/class-ettic-otc-admin-ai.php
  • includes/class-ettic-otc-admin-questions.php
  • includes/class-ettic-otc-admin-review.php
  • includes/class-ettic-otc-admin-settings.php
  • includes/class-ettic-otc-admin-tools.php
  • includes/class-ettic-otc-admin.php
  • includes/class-ettic-otc-catalog.php
  • includes/class-ettic-otc-chat-budget.php
  • includes/class-ettic-otc-chat-corpus.php
  • includes/class-ettic-otc-chat-log.php
  • includes/class-ettic-otc-chat-search.php
  • includes/class-ettic-otc-chat-secrets.php
  • includes/class-ettic-otc-chat-stream-collector.php
  • includes/class-ettic-otc-chat-summarizer.php
  • includes/class-ettic-otc-chat.php
  • includes/class-ettic-otc-cpt.php
  • includes/class-ettic-otc-io.php
  • includes/class-ettic-otc-render.php
  • includes/class-ettic-otc-repository.php
  • includes/class-ettic-otc-version.php
  • includes/class-ettic-otc.php
  • includes/class-opentrust-cpt.php
  • includes/data/certification-catalog.php
  • includes/data/data-practice-catalog.php
  • includes/data/faq-catalog.php
  • includes/data/subprocessor-catalog.php
  • includes/providers/class-ettic-otc-chat-provider-anthropic.php
  • includes/providers/class-ettic-otc-chat-provider-openai.php
  • includes/providers/class-ettic-otc-chat-provider-openrouter.php
  • includes/providers/class-ettic-otc-chat-provider.php
  • languages/open-trust-center-by-ettic-nl_NL.mo
  • languages/open-trust-center-by-ettic-nl_NL.po
  • languages/open-trust-center-by-ettic.pot
  • languages/opentrust-nl_NL.mo
  • languages/opentrust-nl_NL.po
  • open-trust-center-by-ettic.php
  • opentrust.php
  • readme.txt
  • templates/chat.php
  • templates/partials/certifications.php
  • templates/partials/chat-budget-exhausted.php
  • templates/partials/chat-empty-state.php
  • templates/partials/contact.php
  • templates/partials/data-practices.php
  • templates/partials/faq.php
  • templates/partials/hero.php
  • templates/partials/policies.php
  • templates/partials/policy-single.php
  • templates/partials/subprocessors.php
  • templates/trust-center.php
  • uninstall.php
  • wpml-config.xml
💤 Files with no reviewable changes (1)
  • includes/class-opentrust-cpt.php

Comment thread assets/js/admin.js Outdated
Comment thread assets/js/frontend.js
Comment on lines +151 to 153
var card = this.closest('.ettic-otc-dp-card');
var overflow = card.querySelector('.ettic-otc-dp-card__list--overflow');
if (overflow) {
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 17, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@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.

Comment thread includes/class-ettic-otc-admin-questions.php Outdated
Comment on lines 233 to +236
'per_page' => 10000, // hard cap — nobody exports >10k rows per page
];

$result = OpenTrust_Chat_Log::query($filters);
$result = Ettic_OTC_Chat_Log::query($filters);
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 17, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@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?

Comment thread includes/class-ettic-otc-admin-review.php Outdated
Comment thread includes/class-ettic-otc-admin-settings.php
Comment thread includes/class-ettic-otc-chat-secrets.php Outdated
Comment thread includes/class-ettic-otc-cpt.php Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
- 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
Comment thread includes/class-ettic-otc-admin-questions.php Fixed
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.
Comment thread includes/class-ettic-otc-admin-questions.php Fixed
r00bbert added 3 commits May 17, 2026 14:57
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.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 54dd894 and 89a8378.

📒 Files selected for processing (8)
  • README.md
  • assets/js/admin.js
  • includes/class-ettic-otc-admin-questions.php
  • includes/class-ettic-otc-admin-review.php
  • includes/class-ettic-otc-admin-settings.php
  • includes/class-ettic-otc-chat-secrets.php
  • includes/class-ettic-otc-cpt.php
  • languages/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

Comment thread includes/class-ettic-otc-admin-settings.php Outdated
r00bbert added 3 commits May 19, 2026 00:13
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.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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 win

Check 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_sha256 on 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 win

Keep 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 into ettic_otc_settings unchanged. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 89a8378 and 7bc2b9b.

📒 Files selected for processing (3)
  • includes/class-ettic-otc-admin.php
  • includes/class-ettic-otc-io.php
  • languages/open-trust-center-by-ettic.pot
🚧 Files skipped from review as they are similar to previous changes (1)
  • includes/class-ettic-otc-admin.php

r00bbert added 2 commits May 21, 2026 22:40
Rename swapped product name to Ettic_OTC class name in 4 admin strings. Restore Open Trust Center.
@r00bbert r00bbert merged commit 1644b28 into main May 21, 2026
11 checks passed
@r00bbert r00bbert deleted the rename/ettic-otc-namespace branch May 21, 2026 20:49
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.

Rename plugin to Open Trust Center by Ettic and migrate internal namespace to ettic_

2 participants