diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e5b0ed..bdc1df5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,6 +42,7 @@ jobs: - name: Run Plugin Check uses: wordpress/plugin-check-action@v1 with: + slug: open-trust-center-by-ettic ignore-warnings: true exclude-directories: 'dist' exclude-files: '.phpstan-bootstrap.php' @@ -95,16 +96,16 @@ jobs: - name: Regenerate POT run: | - wp i18n make-pot . languages/opentrust.pot \ - --slug=opentrust \ - --domain=opentrust \ + wp i18n make-pot . languages/open-trust-center-by-ettic.pot \ + --slug=open-trust-center-by-ettic \ + --domain=open-trust-center-by-ettic \ --exclude=dist,vendor,node_modules,languages \ --allow-root - name: Verify POT is up to date run: | - if ! git diff --exit-code -I 'POT-Creation-Date' -- languages/opentrust.pot; then - echo "::error::languages/opentrust.pot is stale. Run \`wp i18n make-pot . languages/opentrust.pot --slug=opentrust --domain=opentrust --exclude=dist,vendor,node_modules,languages\` locally and commit the result." + if ! git diff --exit-code -I 'POT-Creation-Date' -- languages/open-trust-center-by-ettic.pot; then + echo "::error::languages/open-trust-center-by-ettic.pot is stale. Run \`wp i18n make-pot . languages/open-trust-center-by-ettic.pot --slug=open-trust-center-by-ettic --domain=open-trust-center-by-ettic --exclude=dist,vendor,node_modules,languages\` locally and commit the result." exit 1 fi echo "OK: POT is in sync with source." @@ -120,12 +121,13 @@ jobs: run: | set -euo pipefail - header_version=$(grep -E '^\s*\*\s*Version:' opentrust.php | head -1 | sed -E 's/.*Version:\s*//' | tr -d '[:space:]') - constant_version=$(grep -E "define\('OPENTRUST_VERSION'" opentrust.php | sed -E "s/.*'OPENTRUST_VERSION',[[:space:]]*'([^']+)'.*/\1/") + plugin_file=open-trust-center-by-ettic.php + header_version=$(grep -E '^\s*\*\s*Version:' "$plugin_file" | head -1 | sed -E 's/.*Version:\s*//' | tr -d '[:space:]') + constant_version=$(grep -E "define\('ETTIC_OTC_VERSION'" "$plugin_file" | sed -E "s/.*'ETTIC_OTC_VERSION',[[:space:]]*'([^']+)'.*/\1/") readme_version=$(grep -E '^Stable tag:' readme.txt | sed -E 's/Stable tag:\s*//' | tr -d '[:space:]') - echo "opentrust.php header: $header_version" - echo "OPENTRUST_VERSION: $constant_version" + echo "$plugin_file header: $header_version" + echo "ETTIC_OTC_VERSION: $constant_version" echo "readme.txt Stable tag: $readme_version" if [[ -z "$header_version" || -z "$constant_version" || -z "$readme_version" ]]; then diff --git a/.phpstan-bootstrap.php b/.phpstan-bootstrap.php index c97c929..5672642 100644 --- a/.phpstan-bootstrap.php +++ b/.phpstan-bootstrap.php @@ -7,18 +7,18 @@ declare(strict_types=1); -if (!defined('OPENTRUST_VERSION')) { - define('OPENTRUST_VERSION', '1.0.0'); +if (!defined('ETTIC_OTC_VERSION')) { + define('ETTIC_OTC_VERSION', '1.0.0'); } -if (!defined('OPENTRUST_PLUGIN_DIR')) { - define('OPENTRUST_PLUGIN_DIR', __DIR__ . '/'); +if (!defined('ETTIC_OTC_PLUGIN_DIR')) { + define('ETTIC_OTC_PLUGIN_DIR', __DIR__ . '/'); } -if (!defined('OPENTRUST_PLUGIN_URL')) { - define('OPENTRUST_PLUGIN_URL', 'https://example.com/wp-content/plugins/opentrust/'); +if (!defined('ETTIC_OTC_PLUGIN_URL')) { + define('ETTIC_OTC_PLUGIN_URL', 'https://example.com/wp-content/plugins/open-trust-center-by-ettic/'); } -if (!defined('OPENTRUST_PLUGIN_FILE')) { - define('OPENTRUST_PLUGIN_FILE', __DIR__ . '/opentrust.php'); +if (!defined('ETTIC_OTC_PLUGIN_FILE')) { + define('ETTIC_OTC_PLUGIN_FILE', __DIR__ . '/open-trust-center-by-ettic.php'); } -if (!defined('OPENTRUST_DB_VERSION')) { - define('OPENTRUST_DB_VERSION', 2); +if (!defined('ETTIC_OTC_DB_VERSION')) { + define('ETTIC_OTC_DB_VERSION', 1); } diff --git a/README.md b/README.md index c79cc4c..d76492f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-# OpenTrust +# Open Trust Center by Ettic **A self-hosted, open-source trust center plugin for WordPress.** @@ -9,15 +9,15 @@ Publish security policies, subprocessors, certifications, and data practices on [![License: GPL v2 or later](https://img.shields.io/badge/License-GPLv2%2B-blue.svg)](LICENSE) [![PHP 8.1+](https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg)](https://www.php.net/) [![WordPress 6.0+](https://img.shields.io/badge/WordPress-6.0%2B-21759B.svg)](https://wordpress.org/) -[![WordPress Plugin Version](https://img.shields.io/wordpress/plugin/v/opentrust?style=flat-square)](https://wordpress.org/plugins/opentrust/) -[![Tested WP Version](https://img.shields.io/wordpress/plugin/tested/opentrust?style=flat-square)](https://wordpress.org/plugins/opentrust/) -[![Downloads](https://img.shields.io/wordpress/plugin/dt/opentrust?style=flat-square)](https://wordpress.org/plugins/opentrust/advanced/) +[![WordPress Plugin Version](https://img.shields.io/wordpress/plugin/v/open-trust-center-by-ettic?style=flat-square)](https://wordpress.org/plugins/open-trust-center-by-ettic/) +[![Tested WP Version](https://img.shields.io/wordpress/plugin/tested/open-trust-center-by-ettic?style=flat-square)](https://wordpress.org/plugins/open-trust-center-by-ettic/) +[![Downloads](https://img.shields.io/wordpress/plugin/dt/open-trust-center-by-ettic?style=flat-square)](https://wordpress.org/plugins/open-trust-center-by-ettic/advanced/)
--- -OpenTrust is a self-hosted, open-source trust center for WordPress. Procurement teams want a URL they can read. Buyers want receipts. Auditors want a version trail. OpenTrust gives you all three on a branded page that lives on your own WordPress site. +Open Trust Center by Ettic is a self-hosted, open-source trust center for WordPress. Procurement teams want a URL they can read. Buyers want receipts. Auditors want a version trail. Open Trust Center by Ettic gives you all three on a branded page that lives on your own WordPress site. ## What's inside @@ -31,14 +31,14 @@ OpenTrust is a self-hosted, open-source trust center for WordPress. Procurement ## Install -**From WordPress.org**: coming soon at https://wordpress.org/plugins/opentrust/ (currently in review). +**From WordPress.org**: coming soon at https://wordpress.org/plugins/open-trust-center-by-ettic/ (currently in review). **Manually:** 1. Download the latest release from [Releases](../../releases). 2. WP Admin → Plugins → Add New → Upload Plugin → upload the zip → Activate. -3. Visit OpenTrust in the admin sidebar to set your accent colour, logo, and company name. -4. Add content under **OpenTrust → Policies / Certifications / Subprocessors / Data Practices**. +3. Visit Open Trust Center in the admin sidebar to set your accent colour, logo, and company name. +4. Add content under **Open Trust Center → Policies / Certifications / Subprocessors / Data Practices**. 5. Visit `/trust-center/` on your site. ## AI chat @@ -49,7 +49,7 @@ It only ever answers from what you've published — it can't retrieve a document To turn it on: -1. **OpenTrust → Settings → AI Chat** +1. **Open Trust Center → Settings → AI Chat** 2. Pick a provider, paste an API key (encrypted at rest with libsodium before it touches the database), and pick a model. 3. Set the daily/monthly token budgets you're comfortable with. 4. Optional: enable Cloudflare Turnstile in the same tab for bot defence. @@ -60,7 +60,7 @@ There's no SaaS subscription. You only pay your AI provider for tokens consumed ## Privacy by design - **Zero telemetry, zero analytics, zero licence checks.** The only outbound HTTP calls the plugin can make are AI provider requests you configure, and they go through an SSRF host allowlist. -- **No PII in logs.** The optional `wp_opentrust_chat_log` table stores only short hashed identifiers — never raw IPs, emails, sessions, user agents, or referers. The privacy posture is enforced by the schema itself. +- **No PII in logs.** The optional `wp_ettic_otc_chat_log` table stores only short hashed identifiers — never raw IPs, emails, sessions, user agents, or referrers. The privacy posture is enforced by the schema itself. - **Encrypted secrets.** API keys and the Cloudflare Turnstile secret are encrypted at rest with libsodium `secretbox`, salted from `wp_salt('auth')`. Rotating `AUTH_KEY` invalidates every stored secret atomically. - **Theme-isolated rendering.** The trust center intercepts at `template_redirect`, outputs a complete standalone HTML document with inlined CSS, and exits. Your theme's stylesheet, header, footer, and JavaScript never load. - **Capability-checked admin actions** with nonce verification on every save handler. @@ -114,7 +114,7 @@ Ships with a `.pot` template and a starter Dutch (nl_NL) translation. WPML and P Translators can regenerate the template from source: ```bash -wp i18n make-pot . languages/opentrust.pot --domain=opentrust +wp i18n make-pot . languages/open-trust-center-by-ettic.pot --domain=open-trust-center-by-ettic ``` Contribute a translation at [translate.wordpress.org](https://translate.wordpress.org/) once the plugin is live there. diff --git a/assets/css/admin.css b/assets/css/admin.css index e4fd9b0..8b68dfd 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -3,12 +3,12 @@ */ /* Server-rendered visibility toggle. jQuery .show()/.hide() override via inline style. */ -.ot-hidden { +.ettic-otc-hidden { display: none; } /* Accent contrast warning */ -.ot-accent-warning { +.ettic-otc-accent-warning { display: flex; gap: 10px; align-items: flex-start; @@ -23,34 +23,34 @@ line-height: 1.5; } -.ot-accent-warning[hidden] { +.ettic-otc-accent-warning[hidden] { display: none; } -.ot-accent-warning__icon { +.ettic-otc-accent-warning__icon { flex-shrink: 0; color: #d97706; margin-top: 1px; } -.ot-accent-warning__body { +.ettic-otc-accent-warning__body { flex: 1; min-width: 0; } -.ot-accent-warning__body strong { +.ettic-otc-accent-warning__body strong { display: block; color: #78350f; font-weight: 600; margin-bottom: 4px; } -.ot-accent-warning__body p { +.ettic-otc-accent-warning__body p { margin: 0 0 8px; color: #78350f; } -.ot-accent-warning__preview { +.ettic-otc-accent-warning__preview { display: flex; align-items: center; gap: 8px; @@ -61,7 +61,7 @@ margin-bottom: 6px; } -.ot-accent-warning__swatch { +.ettic-otc-accent-warning__swatch { display: inline-block; width: 20px; height: 20px; @@ -70,7 +70,7 @@ flex-shrink: 0; } -.ot-accent-warning__hex { +.ettic-otc-accent-warning__hex { font-family: Menlo, Consolas, monospace; font-size: 12px; background: none; @@ -78,13 +78,13 @@ color: #374151; } -.ot-accent-warning__arrow { +.ettic-otc-accent-warning__arrow { color: #9ca3af; font-size: 14px; margin: 0 2px; } -.ot-accent-warning__note { +.ettic-otc-accent-warning__note { font-size: 12px; color: #92400e !important; margin: 0 !important; @@ -92,48 +92,48 @@ /* The heading/copy variants are mutually exclusive — the card shows either the auto-adjustment message or the override acknowledgement, never both. */ -.ot-accent-warning .ot-accent-warning__heading--override, -.ot-accent-warning .ot-accent-warning__copy--override { +.ettic-otc-accent-warning .ettic-otc-accent-warning__heading--override, +.ettic-otc-accent-warning .ettic-otc-accent-warning__copy--override { display: none; } -.ot-accent-warning--override .ot-accent-warning__heading--auto, -.ot-accent-warning--override .ot-accent-warning__copy--auto, -.ot-accent-warning--override .ot-accent-warning__note--auto { +.ettic-otc-accent-warning--override .ettic-otc-accent-warning__heading--auto, +.ettic-otc-accent-warning--override .ettic-otc-accent-warning__copy--auto, +.ettic-otc-accent-warning--override .ettic-otc-accent-warning__note--auto { display: none; } -.ot-accent-warning--override .ot-accent-warning__heading--override, -.ot-accent-warning--override .ot-accent-warning__copy--override { +.ettic-otc-accent-warning--override .ettic-otc-accent-warning__heading--override, +.ettic-otc-accent-warning--override .ettic-otc-accent-warning__copy--override { display: block; } /* Adjusted swatch is struck through when override is active — reminds the user that this variant is NOT being used. */ -.ot-accent-warning--override .ot-accent-warning__swatch--adjusted, -.ot-accent-warning--override .ot-accent-warning__hex--adjusted { +.ettic-otc-accent-warning--override .ettic-otc-accent-warning__swatch--adjusted, +.ettic-otc-accent-warning--override .ettic-otc-accent-warning__hex--adjusted { opacity: 0.45; text-decoration: line-through; } -.ot-accent-warning--override .ot-accent-warning__arrow { +.ettic-otc-accent-warning--override .ettic-otc-accent-warning__arrow { opacity: 0.45; } /* Muted tone — signals "you've acknowledged the tradeoff" rather than alert. */ -.ot-accent-warning--override { +.ettic-otc-accent-warning--override { background: #f9fafb; border-color: #d1d5db; color: #374151; } -.ot-accent-warning--override .ot-accent-warning__icon, -.ot-accent-warning--override .ot-accent-warning__body strong, -.ot-accent-warning--override .ot-accent-warning__body p { +.ettic-otc-accent-warning--override .ettic-otc-accent-warning__icon, +.ettic-otc-accent-warning--override .ettic-otc-accent-warning__body strong, +.ettic-otc-accent-warning--override .ettic-otc-accent-warning__body p { color: #374151; } -.ot-accent-warning--override .ot-accent-warning__preview { +.ettic-otc-accent-warning--override .ettic-otc-accent-warning__preview { border-color: #e5e7eb; } /* Override toggle */ -.ot-accent-warning__override { +.ettic-otc-accent-warning__override { display: flex; align-items: center; gap: 8px; @@ -142,12 +142,12 @@ color: inherit; cursor: pointer; } -.ot-accent-warning__override input { +.ettic-otc-accent-warning__override input { margin: 0; } /* Logo upload */ -.ot-logo-upload { +.ettic-otc-logo-upload { display: flex; align-items: center; gap: 12px; @@ -156,7 +156,7 @@ /* The logo is rendered on the dark hero/nav surface, so the admin preview mirrors that background — otherwise a white-on-white logo vanishes. */ -.ot-logo-preview img { +.ettic-otc-logo-preview img { border: 1px solid #d1d5db; border-radius: 4px; padding: 10px 14px; @@ -166,44 +166,44 @@ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); } -.ot-logo-upload .description { +.ettic-otc-logo-upload .description { flex-basis: 100%; margin: 4px 0 0; } /* Certification meta box */ -.ot-meta-field { +.ettic-otc-meta-field { margin-bottom: 16px; } -.ot-meta-field label { +.ettic-otc-meta-field label { display: block; font-weight: 600; margin-bottom: 4px; } -.ot-meta-field input[type="text"], -.ot-meta-field input[type="url"], -.ot-meta-field input[type="number"], -.ot-meta-field input[type="date"], -.ot-meta-field select, -.ot-meta-field textarea { +.ettic-otc-meta-field input[type="text"], +.ettic-otc-meta-field input[type="url"], +.ettic-otc-meta-field input[type="number"], +.ettic-otc-meta-field input[type="date"], +.ettic-otc-meta-field select, +.ettic-otc-meta-field textarea { width: 100%; max-width: 400px; } -.ot-meta-field textarea { +.ettic-otc-meta-field textarea { min-height: 80px; } -.ot-meta-field .description { +.ettic-otc-meta-field .description { color: #666; font-style: italic; margin-top: 4px; } /* Badge upload in meta box */ -.ot-badge-preview { +.ettic-otc-badge-preview { max-width: 80px; max-height: 80px; border: 1px solid #ddd; @@ -213,7 +213,7 @@ } /* Artifact (certificate / report) upload in cert meta box */ -.ot-artifact-preview { +.ettic-otc-artifact-preview { display: inline-flex; align-items: center; gap: 8px; @@ -225,44 +225,44 @@ max-width: 100%; } -.ot-artifact-preview__icon { +.ettic-otc-artifact-preview__icon { font-size: 16px; line-height: 1; } -.ot-artifact-preview__link { +.ettic-otc-artifact-preview__link { font-weight: 500; text-decoration: none; word-break: break-all; } /* Version history meta box */ -.ot-version-history { +.ettic-otc-version-history { max-height: 300px; overflow-y: auto; } -.ot-version-history table { +.ettic-otc-version-history table { width: 100%; border-collapse: collapse; } -.ot-version-history th, -.ot-version-history td { +.ettic-otc-version-history th, +.ettic-otc-version-history td { padding: 6px 8px; text-align: left; border-bottom: 1px solid #eee; font-size: 13px; } -.ot-version-history th { +.ettic-otc-version-history th { font-weight: 600; background: #f9f9f9; position: sticky; top: 0; } -.ot-version-badge { +.ettic-otc-version-badge { display: inline-block; background: #2563eb; color: #fff; @@ -273,7 +273,7 @@ } /* Tag input — pill-style repeater */ -.ot-tags { +.ettic-otc-tags { display: flex; flex-wrap: wrap; align-items: center; @@ -287,12 +287,12 @@ min-height: 38px; } -.ot-tags:focus-within { +.ettic-otc-tags:focus-within { border-color: #2271b1; box-shadow: 0 0 0 1px #2271b1; } -.ot-tag { +.ettic-otc-tag { display: inline-flex; align-items: center; gap: 4px; @@ -304,19 +304,19 @@ line-height: 1.6; color: #1d2327; white-space: nowrap; - animation: ot-tag-in 0.15s ease; + animation: ettic-otc-tag-in 0.15s ease; } -@keyframes ot-tag-in { +@keyframes ettic-otc-tag-in { from { opacity: 0; transform: scale(0.85); } to { opacity: 1; transform: scale(1); } } -.ot-tag__text { +.ettic-otc-tag__text { pointer-events: none; } -.ot-tag__remove { +.ettic-otc-tag__remove { display: inline-flex; align-items: center; justify-content: center; @@ -333,12 +333,12 @@ transition: background 0.1s, color 0.1s; } -.ot-tag__remove:hover { +.ettic-otc-tag__remove:hover { background: #d63638; color: #fff; } -.ot-tags__input { +.ettic-otc-tags__input { flex: 1 1 120px; min-width: 120px; border: none !important; @@ -352,12 +352,12 @@ } /* Two-column field layout */ -.ot-meta-row { +.ettic-otc-meta-row { display: flex; gap: 16px; } -.ot-meta-row .ot-meta-field { +.ettic-otc-meta-row .ettic-otc-meta-field { flex: 1; } @@ -372,7 +372,7 @@ * AI tab — provider picker * ────────────────────────────────────────────── */ -.ot-ai-intro { +.ettic-otc-ai-intro { max-width: 760px; margin: 20px 0 8px; color: #1d2327; @@ -380,11 +380,11 @@ line-height: 1.6; } -.ot-ai-intro strong { +.ettic-otc-ai-intro strong { color: #1d2327; } -.ot-ai-rationale { +.ettic-otc-ai-rationale { max-width: 760px; margin: 12px 0 24px; padding: 0; @@ -392,7 +392,7 @@ background: transparent; } -.ot-ai-rationale > summary { +.ettic-otc-ai-rationale > summary { display: inline-block; cursor: pointer; color: #2271b1; @@ -402,21 +402,21 @@ padding: 4px 0; } -.ot-ai-rationale > summary::-webkit-details-marker { +.ettic-otc-ai-rationale > summary::-webkit-details-marker { display: none; } -.ot-ai-rationale > summary::after { +.ettic-otc-ai-rationale > summary::after { content: " →"; transition: transform 0.15s ease; display: inline-block; } -.ot-ai-rationale[open] > summary::after { +.ettic-otc-ai-rationale[open] > summary::after { transform: rotate(90deg); } -.ot-ai-rationale__body { +.ettic-otc-ai-rationale__body { margin-top: 12px; padding: 16px 20px; background: #f6f7f7; @@ -427,15 +427,15 @@ color: #3c434a; } -.ot-ai-rationale__body p { +.ettic-otc-ai-rationale__body p { margin: 0 0 10px; } -.ot-ai-rationale__body p:last-child { +.ettic-otc-ai-rationale__body p:last-child { margin-bottom: 0; } -.ot-ai-active-warning { +.ettic-otc-ai-active-warning { max-width: 900px; margin: 20px 0 0; padding: 14px 18px; @@ -444,21 +444,21 @@ border-radius: 0 4px 4px 0; } -.ot-ai-active-warning strong { +.ettic-otc-ai-active-warning strong { display: block; font-size: 13px; color: #8a1f21; margin-bottom: 4px; } -.ot-ai-active-warning p { +.ettic-otc-ai-active-warning p { margin: 0; font-size: 13px; line-height: 1.55; color: #3c434a; } -.ot-ai-section-heading { +.ettic-otc-ai-section-heading { margin: 32px 0 8px; font-size: 15px; font-weight: 600; @@ -466,7 +466,7 @@ } /* Provider card — shared */ -.ot-ai-card { +.ettic-otc-ai-card { border-radius: 8px; padding: 18px 20px; background: #fff; @@ -474,7 +474,7 @@ transition: border-color 0.15s ease, background 0.15s ease; } -.ot-ai-card__header { +.ettic-otc-ai-card__header { display: flex; align-items: center; justify-content: space-between; @@ -482,14 +482,14 @@ margin-bottom: 6px; } -.ot-ai-card__title { +.ettic-otc-ai-card__title { font-size: 16px; font-weight: 700; color: #1d2327; margin: 0; } -.ot-ai-card__badge { +.ettic-otc-ai-card__badge { display: inline-block; padding: 3px 9px; background: #dbeafe; @@ -501,13 +501,13 @@ letter-spacing: 0.5px; } -.ot-ai-card__keylink { +.ettic-otc-ai-card__keylink { margin: 0 0 12px; font-size: 12px; color: #646970; } -.ot-ai-card__saved { +.ettic-otc-ai-card__saved { padding: 8px 12px; background: #f0fdf4; border: 1px solid #bbf7d0; @@ -518,34 +518,34 @@ color: #166534; } -.ot-ai-card__input { +.ettic-otc-ai-card__input { width: 100%; margin-bottom: 10px; } -.ot-ai-card__submit { +.ettic-otc-ai-card__submit { width: 100%; } -.ot-ai-card__forget { +.ettic-otc-ai-card__forget { color: #b91c1c; font-size: 12px; } /* Primary variant — Anthropic */ -.ot-ai-card--primary { +.ettic-otc-ai-card--primary { max-width: 560px; border-color: #2563eb; background: linear-gradient(180deg, #eff6ff 0%, #ffffff 100%); box-shadow: 0 1px 2px rgba(37, 99, 235, 0.08); } -.ot-ai-card--primary.is-active { +.ettic-otc-ai-card--primary.is-active { border-color: #16a34a; background: linear-gradient(180deg, #f0fdf4 0%, #ffffff 100%); } -.ot-ai-card--primary .ot-ai-card__description { +.ettic-otc-ai-card--primary .ettic-otc-ai-card__description { margin: 4px 0 14px; font-size: 13px; line-height: 1.55; @@ -553,7 +553,7 @@ } /* Advanced disclosure */ -.ot-ai-advanced { +.ettic-otc-ai-advanced { max-width: 900px; margin: 28px 0 0; padding: 0; @@ -561,7 +561,7 @@ background: transparent; } -.ot-ai-advanced > summary { +.ettic-otc-ai-advanced > summary { display: inline-block; cursor: pointer; padding: 6px 0; @@ -571,22 +571,22 @@ list-style: none; } -.ot-ai-advanced > summary::-webkit-details-marker { +.ettic-otc-ai-advanced > summary::-webkit-details-marker { display: none; } -.ot-ai-advanced > summary::before { +.ettic-otc-ai-advanced > summary::before { content: "▸"; display: inline-block; margin-right: 6px; transition: transform 0.15s ease; } -.ot-ai-advanced[open] > summary::before { +.ettic-otc-ai-advanced[open] > summary::before { transform: rotate(90deg); } -.ot-ai-advanced__warning { +.ettic-otc-ai-advanced__warning { margin: 12px 0 20px; padding: 16px 20px; background: #fff4f4; @@ -594,7 +594,7 @@ border-radius: 0 4px 4px 0; } -.ot-ai-advanced__warning strong { +.ettic-otc-ai-advanced__warning strong { display: block; font-size: 13px; font-weight: 700; @@ -604,37 +604,37 @@ letter-spacing: 0.4px; } -.ot-ai-advanced__warning p { +.ettic-otc-ai-advanced__warning p { margin: 0 0 8px; font-size: 13px; line-height: 1.6; color: #3c434a; } -.ot-ai-advanced__warning p:last-child { +.ettic-otc-ai-advanced__warning p:last-child { margin-bottom: 0; } -.ot-ai-advanced__grid { +.ettic-otc-ai-advanced__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; } /* Advanced card variant — muted */ -.ot-ai-card--advanced { +.ettic-otc-ai-card--advanced { background: #fafafa; border-color: #d1d5db; filter: grayscale(0.25); } -.ot-ai-card--advanced.is-active { +.ettic-otc-ai-card--advanced.is-active { background: #fff; border-color: #d63638; filter: none; } -.ot-ai-card--advanced .ot-ai-card__title { +.ettic-otc-ai-card--advanced .ettic-otc-ai-card__title { color: #50575e; } @@ -642,12 +642,12 @@ * Catalog typeahead (subprocessor / data practice) * ────────────────────────────────────────────── */ -.ot-typeahead { +.ettic-otc-typeahead { position: relative; max-width: 100%; } -.ot-typeahead__panel { +.ettic-otc-typeahead__panel { position: absolute; top: 2px; left: 0; @@ -664,11 +664,11 @@ overflow-y: auto; } -.ot-typeahead__panel[hidden] { +.ettic-otc-typeahead__panel[hidden] { display: none; } -.ot-typeahead__option { +.ettic-otc-typeahead__option { display: flex; align-items: baseline; gap: 8px; @@ -681,29 +681,29 @@ transition: background 0.1s ease; } -.ot-typeahead__option:hover, -.ot-typeahead__option.is-active { +.ettic-otc-typeahead__option:hover, +.ettic-otc-typeahead__option.is-active { background: #f0f6ff; color: #0b4dad; } -.ot-typeahead__option-name { +.ettic-otc-typeahead__option-name { font-weight: 500; } -.ot-typeahead__option-hint { +.ettic-otc-typeahead__option-hint { flex-shrink: 0; font-size: 12px; font-weight: 400; color: #646970; } -.ot-typeahead__option:hover .ot-typeahead__option-hint, -.ot-typeahead__option.is-active .ot-typeahead__option-hint { +.ettic-otc-typeahead__option:hover .ettic-otc-typeahead__option-hint, +.ettic-otc-typeahead__option.is-active .ettic-otc-typeahead__option-hint { color: #2271b1; } -.ot-typeahead__hint { +.ettic-otc-typeahead__hint { padding: 10px 12px; font-size: 12px; color: #646970; @@ -714,72 +714,72 @@ /* Prefilled field — single-line colored border all around, matching-tint * helper text appended below. Facts use green, review-tier uses amber. * A brief flash animation plays on (re)fill so users can see what changed. */ -.ot-meta-field.ot-typeahead-filled.is-fact input[type="text"], -.ot-meta-field.ot-typeahead-filled.is-fact input[type="url"], -.ot-meta-field.ot-typeahead-filled.is-fact input[type="number"], -.ot-meta-field.ot-typeahead-filled.is-fact textarea, -.ot-meta-field.ot-typeahead-filled.is-fact select, -.ot-meta-field.ot-typeahead-filled.is-fact .ot-tags { +.ettic-otc-meta-field.ettic-otc-typeahead-filled.is-fact input[type="text"], +.ettic-otc-meta-field.ettic-otc-typeahead-filled.is-fact input[type="url"], +.ettic-otc-meta-field.ettic-otc-typeahead-filled.is-fact input[type="number"], +.ettic-otc-meta-field.ettic-otc-typeahead-filled.is-fact textarea, +.ettic-otc-meta-field.ettic-otc-typeahead-filled.is-fact select, +.ettic-otc-meta-field.ettic-otc-typeahead-filled.is-fact .ettic-otc-tags { border: 1px solid #16a34a; box-shadow: none; - animation: ot-typeahead-flash-fact 650ms ease-out; + animation: ettic-otc-typeahead-flash-fact 650ms ease-out; } -.ot-meta-field.ot-typeahead-filled.is-review input[type="text"], -.ot-meta-field.ot-typeahead-filled.is-review input[type="url"], -.ot-meta-field.ot-typeahead-filled.is-review input[type="number"], -.ot-meta-field.ot-typeahead-filled.is-review textarea, -.ot-meta-field.ot-typeahead-filled.is-review select, -.ot-meta-field.ot-typeahead-filled.is-review .ot-tags { +.ettic-otc-meta-field.ettic-otc-typeahead-filled.is-review input[type="text"], +.ettic-otc-meta-field.ettic-otc-typeahead-filled.is-review input[type="url"], +.ettic-otc-meta-field.ettic-otc-typeahead-filled.is-review input[type="number"], +.ettic-otc-meta-field.ettic-otc-typeahead-filled.is-review textarea, +.ettic-otc-meta-field.ettic-otc-typeahead-filled.is-review select, +.ettic-otc-meta-field.ettic-otc-typeahead-filled.is-review .ettic-otc-tags { border: 1px solid #d97706; box-shadow: none; - animation: ot-typeahead-flash-review 650ms ease-out; + animation: ettic-otc-typeahead-flash-review 650ms ease-out; } -@keyframes ot-typeahead-flash-fact { +@keyframes ettic-otc-typeahead-flash-fact { 0% { background-color: #dcfce7; box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.25); } 60% { background-color: #f0fdf4; box-shadow: 0 0 0 2px rgba(22, 163, 74, 0.08); } 100% { background-color: #fff; box-shadow: none; } } -@keyframes ot-typeahead-flash-review { +@keyframes ettic-otc-typeahead-flash-review { 0% { background-color: #fef3c7; box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.28); } 60% { background-color: #fffbeb; box-shadow: 0 0 0 2px rgba(217, 119, 6, 0.08); } 100% { background-color: #fff; box-shadow: none; } } @media (prefers-reduced-motion: reduce) { - .ot-meta-field.ot-typeahead-filled.is-fact input, - .ot-meta-field.ot-typeahead-filled.is-fact textarea, - .ot-meta-field.ot-typeahead-filled.is-fact select, - .ot-meta-field.ot-typeahead-filled.is-fact .ot-tags, - .ot-meta-field.ot-typeahead-filled.is-review input, - .ot-meta-field.ot-typeahead-filled.is-review textarea, - .ot-meta-field.ot-typeahead-filled.is-review select, - .ot-meta-field.ot-typeahead-filled.is-review .ot-tags { + .ettic-otc-meta-field.ettic-otc-typeahead-filled.is-fact input, + .ettic-otc-meta-field.ettic-otc-typeahead-filled.is-fact textarea, + .ettic-otc-meta-field.ettic-otc-typeahead-filled.is-fact select, + .ettic-otc-meta-field.ettic-otc-typeahead-filled.is-fact .ettic-otc-tags, + .ettic-otc-meta-field.ettic-otc-typeahead-filled.is-review input, + .ettic-otc-meta-field.ettic-otc-typeahead-filled.is-review textarea, + .ettic-otc-meta-field.ettic-otc-typeahead-filled.is-review select, + .ettic-otc-meta-field.ettic-otc-typeahead-filled.is-review .ettic-otc-tags { animation: none; } } -.ot-typeahead-help { +.ettic-otc-typeahead-help { margin: 6px 0 0; font-size: 12px; line-height: 1.45; color: #166534; } -.ot-typeahead-help.is-review { +.ettic-otc-typeahead-help.is-review { color: #b45309; font-weight: 600; } /* Tools page — Import / Export */ -.ot-tools-intro { +.ettic-otc-tools-intro { color: #50575e; max-width: 760px; } -.ot-tools-grid { +.ettic-otc-tools-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; @@ -787,23 +787,23 @@ } @media (max-width: 960px) { - .ot-tools-grid { + .ettic-otc-tools-grid { grid-template-columns: 1fr; } } -.ot-tools-panel { +.ettic-otc-tools-panel { background: #fff; border: 1px solid #dcdcde; border-radius: 6px; padding: 20px; } -.ot-tools-panel h2 { +.ettic-otc-tools-panel h2 { margin-top: 0; } -.ot-tools-warn { +.ettic-otc-tools-warn { background: #fef9c3; border-left: 3px solid #854d0e; padding: 10px 14px; @@ -814,39 +814,39 @@ border-radius: 3px; } -.ot-tools-warn strong { +.ettic-otc-tools-warn strong { display: block; margin-bottom: 2px; } -.ot-tools-fieldset { +.ettic-otc-tools-fieldset { margin-bottom: 16px; } -.ot-tools-fieldset legend { +.ettic-otc-tools-fieldset legend { font-weight: 600; margin-bottom: 6px; } -.ot-tools-radio { +.ettic-otc-tools-radio { display: block; margin-bottom: 6px; } -.ot-tools-cpt-group { +.ettic-otc-tools-cpt-group { border: 1px solid #e0e0e0; border-radius: 4px; padding: 10px 12px; margin-bottom: 10px; } -.ot-tools-cpt-group summary { +.ettic-otc-tools-cpt-group summary { font-weight: 600; cursor: pointer; padding: 2px 0; } -.ot-tools-cpt-group ul { +.ettic-otc-tools-cpt-group ul { margin: 8px 0 0 4px; padding: 0; list-style: none; @@ -854,48 +854,48 @@ overflow-y: auto; } -.ot-tools-cpt-group li { +.ettic-otc-tools-cpt-group li { padding: 2px 0; font-size: 13px; } -.ot-tools-count { +.ettic-otc-tools-count { color: #6b7280; font-weight: normal; } -.ot-tools-status { +.ettic-otc-tools-status { color: #6b7280; font-style: italic; } -.ot-tools-hint { +.ettic-otc-tools-hint { color: #6b7280; font-size: 12px; } -.ot-tools-preview-table { +.ettic-otc-tools-preview-table { width: 100%; border-collapse: collapse; margin-top: 10px; } -.ot-tools-preview-table th, -.ot-tools-preview-table td { +.ettic-otc-tools-preview-table th, +.ettic-otc-tools-preview-table td { text-align: left; padding: 6px 8px; border-bottom: 1px solid #f0f0f0; font-size: 13px; } -.ot-tools-uuid { +.ettic-otc-tools-uuid { font-family: monospace; font-size: 11px; color: #6b7280; } -.ot-tools-action--create { color: #166534; font-weight: 600; } -.ot-tools-action--update { color: #854d0e; font-weight: 600; } +.ettic-otc-tools-action--create { color: #166534; font-weight: 600; } +.ettic-otc-tools-action--update { color: #854d0e; font-weight: 600; } /* Active-model dropdown — provider-deprecated id marker. */ .opentrust-ai-model-unavailable { @@ -910,4 +910,4 @@ margin: 0; color: #b91c1c; } -.ot-tools-action--skip { color: #6b7280; } +.ettic-otc-tools-action--skip { color: #6b7280; } diff --git a/assets/css/chat.css b/assets/css/chat.css index e09e02d..fda3828 100644 --- a/assets/css/chat.css +++ b/assets/css/chat.css @@ -1,18 +1,18 @@ /** * OpenTrust AI chat page styles. * Inlined into /trust-center/ask/ via template_redirect. - * Layered into @layer opentrust to avoid theme bleed. + * Layered into @layer ettic-otc to avoid theme bleed. * * Scales in use: * Spacing : 4 / 8 / 12 / 16 / 24 / 32 / 48 / 64 px * Radius : 8 / 12 / 16 / 999 px * Weights : 400 / 500 / 600 / 700 */ -@layer opentrust { +@layer ettic-otc { /* ─── Page chrome ───────────────────────────── */ - .ot-chat-body { + .ettic-otc-chat-body { background: #f9fafb; color: #111827; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; @@ -21,11 +21,11 @@ flex-direction: column; } - .ot-chat-main { + .ettic-otc-chat-main { flex: 1; padding: 48px 16px 200px; /* bottom: clear the fixed dock */ } - .ot-chat-container { + .ettic-otc-chat-container { max-width: 720px; margin: 0 auto; width: 100%; @@ -33,17 +33,17 @@ /* ─── Shell (single unified layout) ─────────── */ - .ot-chat-shell { + .ettic-otc-chat-shell { display: flex; flex-direction: column; } /* Intro visible only when NOT chatting. */ - .ot-chat-intro { + .ettic-otc-chat-intro { text-align: center; padding: 32px 0 48px; } - .ot-chat-intro__title { + .ettic-otc-chat-intro__title { font-size: clamp(24px, 3vw, 32px); font-weight: 600; letter-spacing: -0.02em; @@ -51,45 +51,45 @@ color: #111827; line-height: 1.2; } - .ot-chat-intro__help { + .ettic-otc-chat-intro__help { font-size: 13px; color: #6b7280; margin: 0; line-height: 1.5; } - .ot-chat-intro__help strong { + .ettic-otc-chat-intro__help strong { color: #374151; font-weight: 600; } /* Thread — plain flow container, no visual chrome. Messages live directly on the page background. */ - .ot-chat-thread { + .ettic-otc-chat-thread { background: transparent; border: 0; padding: 0; } - .ot-chat-body:not(.is-chatting) .ot-chat-thread { display: none; } - .ot-chat-body.is-chatting .ot-chat-intro { display: none; } + .ettic-otc-chat-body:not(.is-chatting) .ettic-otc-chat-thread { display: none; } + .ettic-otc-chat-body.is-chatting .ettic-otc-chat-intro { display: none; } /* ─── Messages ──────────────────────────────── */ - .ot-chat-messages { + .ettic-otc-chat-messages { display: flex; flex-direction: column; } - .ot-chat-msg { + .ettic-otc-chat-msg { display: grid; grid-template-columns: 40px 1fr; gap: 12px; padding: 24px 0; border-bottom: 1px solid #f3f4f6; } - .ot-chat-msg:last-child { border-bottom: 0; } + .ettic-otc-chat-msg:last-child { border-bottom: 0; } - .ot-chat-msg__avatar { + .ettic-otc-chat-msg__avatar { width: 36px; height: 36px; border-radius: 999px; @@ -98,18 +98,18 @@ justify-content: center; flex-shrink: 0; } - .ot-chat-msg--user .ot-chat-msg__avatar { + .ettic-otc-chat-msg--user .ettic-otc-chat-msg__avatar { background: #111827; color: #ffffff; } - .ot-chat-msg--assistant .ot-chat-msg__avatar { + .ettic-otc-chat-msg--assistant .ettic-otc-chat-msg__avatar { background: #ffffff; - color: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)); + color: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)); border: 1px solid #e5e7eb; overflow: hidden; } - .ot-chat-msg__avatar-img { + .ettic-otc-chat-msg__avatar-img { width: 100%; height: 100%; object-fit: cover; @@ -118,17 +118,17 @@ /* Kill the SVG baseline gap inside flex-centered parents — ensures mathematically-centered icons also render visually centered. */ - .ot-chat-msg__avatar > svg, - .ot-chat-msg__avatar-icon, - .ot-chat-msg__avatar-warning { + .ettic-otc-chat-msg__avatar > svg, + .ettic-otc-chat-msg__avatar-icon, + .ettic-otc-chat-msg__avatar-warning { display: block; } - .ot-chat-msg__content { + .ettic-otc-chat-msg__content { min-width: 0; } - .ot-chat-msg__header { + .ettic-otc-chat-msg__header { display: flex; align-items: baseline; gap: 8px; @@ -136,21 +136,21 @@ font-size: 13px; line-height: 1.4; } - .ot-chat-msg__name { + .ettic-otc-chat-msg__name { font-size: 14px; font-weight: 600; color: #111827; margin: 0; } - .ot-chat-msg__separator { + .ettic-otc-chat-msg__separator { color: #d1d5db; font-size: 12px; } - .ot-chat-msg__time { + .ettic-otc-chat-msg__time { color: #9ca3af; font-size: 12px; } - .ot-chat-msg__menu { + .ettic-otc-chat-msg__menu { margin-left: auto; background: transparent; border: 0; @@ -161,9 +161,9 @@ padding: 4px 8px; border-radius: 8px; } - .ot-chat-msg__menu:hover { color: #6b7280; background: #f9fafb; } + .ettic-otc-chat-msg__menu:hover { color: #6b7280; background: #f9fafb; } - .ot-chat-msg__body { + .ettic-otc-chat-msg__body { font-size: 15px; line-height: 1.65; color: #1f2937; @@ -171,74 +171,74 @@ overflow-wrap: break-word; } - /* Markdown inside .ot-chat-msg__body */ - .ot-chat-msg__body p { margin: 0 0 12px; } - .ot-chat-msg__body p:last-child { margin-bottom: 0; } - .ot-chat-msg__body strong { font-weight: 700; color: #111827; } - .ot-chat-msg__body em { font-style: italic; } + /* Markdown inside .ettic-otc-chat-msg__body */ + .ettic-otc-chat-msg__body p { margin: 0 0 12px; } + .ettic-otc-chat-msg__body p:last-child { margin-bottom: 0; } + .ettic-otc-chat-msg__body strong { font-weight: 700; color: #111827; } + .ettic-otc-chat-msg__body em { font-style: italic; } /* Inline hyperlinks inside assistant answers (auto-linked URLs or explicit markdown links). Explicit link-state rules so browser UA visited/hover colors never override the accent. */ - .ot-chat-msg__body a, - .ot-chat-msg__body a:link, - .ot-chat-msg__body a:visited { - color: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)); + .ettic-otc-chat-msg__body a, + .ettic-otc-chat-msg__body a:link, + .ettic-otc-chat-msg__body a:visited { + color: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)); text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; word-break: break-word; } - .ot-chat-msg__body a:hover, - .ot-chat-msg__body a:focus { - color: hsl(var(--ot-accent-h), var(--ot-accent-s), calc(var(--ot-accent-l) - 8%)); + .ettic-otc-chat-msg__body a:hover, + .ettic-otc-chat-msg__body a:focus { + color: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), calc(var(--ettic-otc-accent-l) - 8%)); text-decoration-thickness: 2px; } /* The contact CTA inside refused messages is already styled as a filled button elsewhere — don't let the generic link rule fight it. */ - .ot-chat-msg__body a.ot-chat-contact-btn, - .ot-chat-msg__body a.ot-chat-contact-btn:link, - .ot-chat-msg__body a.ot-chat-contact-btn:visited, - .ot-chat-msg__body a.ot-chat-contact-btn:hover, - .ot-chat-msg__body a.ot-chat-contact-btn:focus { + .ettic-otc-chat-msg__body a.ettic-otc-chat-contact-btn, + .ettic-otc-chat-msg__body a.ettic-otc-chat-contact-btn:link, + .ettic-otc-chat-msg__body a.ettic-otc-chat-contact-btn:visited, + .ettic-otc-chat-msg__body a.ettic-otc-chat-contact-btn:hover, + .ettic-otc-chat-msg__body a.ettic-otc-chat-contact-btn:focus { text-decoration: none; } - .ot-chat-msg__body code { + .ettic-otc-chat-msg__body code { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 13px; padding: 1px 6px; background: #f3f4f6; border-radius: 4px; } - .ot-chat-msg__body ol, - .ot-chat-msg__body ul { + .ettic-otc-chat-msg__body ol, + .ettic-otc-chat-msg__body ul { margin: 0 0 12px; padding: 0 0 0 24px; } - .ot-chat-msg__body ol:last-child, - .ot-chat-msg__body ul:last-child { margin-bottom: 0; } - .ot-chat-msg__body li { margin-bottom: 8px; } - .ot-chat-msg__body li:last-child { margin-bottom: 0; } + .ettic-otc-chat-msg__body ol:last-child, + .ettic-otc-chat-msg__body ul:last-child { margin-bottom: 0; } + .ettic-otc-chat-msg__body li { margin-bottom: 8px; } + .ettic-otc-chat-msg__body li:last-child { margin-bottom: 0; } - .ot-chat-msg__body h3, - .ot-chat-msg__body h4, - .ot-chat-msg__body h5, - .ot-chat-msg__body h6 { + .ettic-otc-chat-msg__body h3, + .ettic-otc-chat-msg__body h4, + .ettic-otc-chat-msg__body h5, + .ettic-otc-chat-msg__body h6 { margin: 16px 0 8px; font-weight: 650; color: #111827; line-height: 1.3; } - .ot-chat-msg__body h3 { font-size: 1.05em; } - .ot-chat-msg__body h4 { font-size: 1em; } - .ot-chat-msg__body h5, - .ot-chat-msg__body h6 { font-size: 0.95em; } - .ot-chat-msg__body > h3:first-child, - .ot-chat-msg__body > h4:first-child, - .ot-chat-msg__body > h5:first-child, - .ot-chat-msg__body > h6:first-child { margin-top: 0; } + .ettic-otc-chat-msg__body h3 { font-size: 1.05em; } + .ettic-otc-chat-msg__body h4 { font-size: 1em; } + .ettic-otc-chat-msg__body h5, + .ettic-otc-chat-msg__body h6 { font-size: 0.95em; } + .ettic-otc-chat-msg__body > h3:first-child, + .ettic-otc-chat-msg__body > h4:first-child, + .ettic-otc-chat-msg__body > h5:first-child, + .ettic-otc-chat-msg__body > h6:first-child { margin-top: 0; } - .ot-chat-msg__body hr { + .ettic-otc-chat-msg__body hr { border: 0; border-top: 1px solid #e5e7eb; margin: 16px 0; @@ -247,22 +247,22 @@ /* Refusal state — signal is on the AVATAR, not the body. The body renders as a normal message. The assistant's circle becomes a soft yellow with a dark-orange warning triangle. */ - .ot-chat-msg--refused.ot-chat-msg--assistant .ot-chat-msg__avatar { + .ettic-otc-chat-msg--refused.ettic-otc-chat-msg--assistant .ettic-otc-chat-msg__avatar { background: #fef3c7; border-color: #fcd34d; color: #b45309; } - .ot-chat-msg--refused .ot-chat-msg__avatar .ot-chat-msg__avatar-img, - .ot-chat-msg--refused .ot-chat-msg__avatar .ot-chat-msg__avatar-icon { + .ettic-otc-chat-msg--refused .ettic-otc-chat-msg__avatar .ettic-otc-chat-msg__avatar-img, + .ettic-otc-chat-msg--refused .ettic-otc-chat-msg__avatar .ettic-otc-chat-msg__avatar-icon { display: none; } - .ot-chat-msg--refused .ot-chat-msg__avatar-warning { + .ettic-otc-chat-msg--refused .ettic-otc-chat-msg__avatar-warning { display: block; color: #b45309; } /* Citation superscript chips */ - .ot-chat-msg .ot-chat-cite { + .ettic-otc-chat-msg .ettic-otc-chat-cite { display: inline-block; min-width: 18px; height: 18px; @@ -274,21 +274,21 @@ line-height: 18px; text-align: center; vertical-align: 1px; - background: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)); + background: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)); color: #ffffff; border-radius: 4px; text-decoration: none; transition: opacity 0.15s; } - .ot-chat-msg .ot-chat-cite:hover, - .ot-chat-msg .ot-chat-cite:focus, - .ot-chat-msg .ot-chat-cite:visited { + .ettic-otc-chat-msg .ettic-otc-chat-cite:hover, + .ettic-otc-chat-msg .ettic-otc-chat-cite:focus, + .ettic-otc-chat-msg .ettic-otc-chat-cite:visited { color: #ffffff; text-decoration: none; } - .ot-chat-msg .ot-chat-cite:hover { opacity: 0.85; } + .ettic-otc-chat-msg .ettic-otc-chat-cite:hover { opacity: 0.85; } - .ot-chat-msg__sources { + .ettic-otc-chat-msg__sources { margin-top: 16px; padding: 12px 16px; background: #f9fafb; @@ -296,7 +296,7 @@ border-radius: 12px; font-size: 13px; } - .ot-chat-msg__sources h4 { + .ettic-otc-chat-msg__sources h4 { margin: 0 0 8px; font-size: 11px; font-weight: 700; @@ -304,23 +304,23 @@ text-transform: uppercase; letter-spacing: 0.6px; } - .ot-chat-msg__sources ol { + .ettic-otc-chat-msg__sources ol { margin: 0; padding: 0 0 0 16px; } - .ot-chat-msg__sources li { + .ettic-otc-chat-msg__sources li { margin-bottom: 4px; color: #4b5563; font-size: 13px; } - .ot-chat-msg__sources li:last-child { margin-bottom: 0; } - .ot-chat-msg__sources a { - color: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)); + .ettic-otc-chat-msg__sources li:last-child { margin-bottom: 0; } + .ettic-otc-chat-msg__sources a { + color: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)); text-decoration: none; } - .ot-chat-msg__sources a:hover { text-decoration: underline; } + .ettic-otc-chat-msg__sources a:hover { text-decoration: underline; } - .ot-chat-msg__disclaimer { + .ettic-otc-chat-msg__disclaimer { margin: 14px 0 0; padding-top: 10px; border-top: 1px dashed #e5e7eb; @@ -332,19 +332,19 @@ } @media print { - .ot-chat-msg__disclaimer { + .ettic-otc-chat-msg__disclaimer { color: #6b7280 !important; border-top-color: #d1d5db !important; } } - .ot-chat-msg__actions { + .ettic-otc-chat-msg__actions { display: flex; gap: 4px; align-items: center; margin-top: 12px; } - .ot-chat-msg__actions button { + .ettic-otc-chat-msg__actions button { display: inline-flex; align-items: center; gap: 6px; @@ -357,15 +357,15 @@ cursor: pointer; border-radius: 8px; } - .ot-chat-msg__actions button:hover { + .ettic-otc-chat-msg__actions button:hover { color: #374151; background: #f3f4f6; } - .ot-chat-msg__actions button.is-success { + .ettic-otc-chat-msg__actions button.is-success { color: #15803d; background: #dcfce7; } - .ot-chat-msg__actions svg { + .ettic-otc-chat-msg__actions svg { width: 14px; height: 14px; flex-shrink: 0; @@ -373,35 +373,35 @@ /* ─── Thinking indicator ────────────────────── */ - .ot-chat-thinking { + .ettic-otc-chat-thinking { display: inline-flex; align-items: center; gap: 6px; color: #9ca3af; font-size: 14px; } - .ot-chat-thinking-dot { + .ettic-otc-chat-thinking-dot { display: inline-block; width: 6px; height: 6px; border-radius: 999px; background: currentColor; - animation: ot-chat-pulse 1.4s infinite ease-in-out both; + animation: ettic-otc-chat-pulse 1.4s infinite ease-in-out both; } - .ot-chat-thinking-dot:nth-child(2) { animation-delay: 0.16s; } - .ot-chat-thinking-dot:nth-child(3) { animation-delay: 0.32s; } - @keyframes ot-chat-pulse { + .ettic-otc-chat-thinking-dot:nth-child(2) { animation-delay: 0.16s; } + .ettic-otc-chat-thinking-dot:nth-child(3) { animation-delay: 0.32s; } + @keyframes ettic-otc-chat-pulse { 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; } 40% { transform: scale(1); opacity: 1; } } @media (prefers-reduced-motion: reduce) { - .ot-chat-thinking-dot { animation: none; opacity: 0.6; } + .ettic-otc-chat-thinking-dot { animation: none; opacity: 0.6; } } /* Tool-use label appears next to the dots when the AI is mid-retrieval — e.g. "Searching for incident response" or "Reading Privacy Policy". Set by the chat.js tool_call SSE handler; cleared when the first answer token arrives. */ - .ot-chat-thinking__label { + .ettic-otc-chat-thinking__label { margin-left: 4px; color: #6b7280; font-size: 13px; @@ -414,7 +414,7 @@ - .is-static: settled. Dots stop animating; the pill becomes a permanent record of "the model looked this up here", like a tool-use chip in Claude / ChatGPT / Perplexity. */ - .ot-chat-tool-status { + .ettic-otc-chat-tool-status { display: inline-flex; align-items: center; gap: 6px; @@ -424,26 +424,26 @@ background: rgba(0, 0, 0, 0.04); color: #9ca3af; font-size: 13px; - animation: ot-chat-tool-status-in 180ms ease-out; + animation: ettic-otc-chat-tool-status-in 180ms ease-out; } - .ot-chat-tool-status .ot-chat-thinking-dot { background: #9ca3af; } - .ot-chat-tool-status .ot-chat-thinking__label { + .ettic-otc-chat-tool-status .ettic-otc-chat-thinking-dot { background: #9ca3af; } + .ettic-otc-chat-tool-status .ettic-otc-chat-thinking__label { margin-left: 4px; color: #6b7280; font-style: normal; } /* Settled state — dots freeze, color shifts toward "completed" without a checkmark glyph. Stays a permanent inline log of what was looked up. */ - .ot-chat-tool-status.is-static .ot-chat-thinking-dot { + .ettic-otc-chat-tool-status.is-static .ettic-otc-chat-thinking-dot { animation: none; opacity: 0.55; } - @keyframes ot-chat-tool-status-in { + @keyframes ettic-otc-chat-tool-status-in { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: none; } } @media (prefers-reduced-motion: reduce) { - .ot-chat-tool-status { animation: none; } + .ettic-otc-chat-tool-status { animation: none; } } /* ─── Post-stream reveal ────────────────────── */ @@ -452,43 +452,43 @@ staggered cascade rather than appearing instantly. Only applied to live-streamed bubbles — history replay renders fully-formed. Per-element animation-delay is set inline by chat.js. */ - @keyframes ot-chat-reveal { + @keyframes ettic-otc-chat-reveal { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } } - .ot-chat-msg__reveal { - animation: ot-chat-reveal 360ms cubic-bezier(0.2, 0.9, 0.3, 1) both; + .ettic-otc-chat-msg__reveal { + animation: ettic-otc-chat-reveal 360ms cubic-bezier(0.2, 0.9, 0.3, 1) both; } @media (prefers-reduced-motion: reduce) { - .ot-chat-msg__reveal { animation: none; } + .ettic-otc-chat-msg__reveal { animation: none; } } /* Contact CTA inside refused messages. Explicit link-state colors so browser UA visited/hover styles can never override the contrast. */ - .ot-chat-msg .ot-chat-contact-btn, - .ot-chat-msg .ot-chat-contact-btn:link, - .ot-chat-msg .ot-chat-contact-btn:visited, - .ot-chat-msg .ot-chat-contact-btn:hover, - .ot-chat-msg .ot-chat-contact-btn:focus, - .ot-chat-msg .ot-chat-contact-btn:active { + .ettic-otc-chat-msg .ettic-otc-chat-contact-btn, + .ettic-otc-chat-msg .ettic-otc-chat-contact-btn:link, + .ettic-otc-chat-msg .ettic-otc-chat-contact-btn:visited, + .ettic-otc-chat-msg .ettic-otc-chat-contact-btn:hover, + .ettic-otc-chat-msg .ettic-otc-chat-contact-btn:focus, + .ettic-otc-chat-msg .ettic-otc-chat-contact-btn:active { display: inline-block; margin-top: 16px; padding: 10px 16px; - background: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)); - color: var(--ot-accent-contrast, #ffffff); + background: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)); + color: var(--ettic-otc-accent-contrast, #ffffff); font-size: 13px; font-weight: 600; text-decoration: none; border-radius: 8px; } - .ot-chat-msg .ot-chat-contact-btn:hover, - .ot-chat-msg .ot-chat-contact-btn:focus { - background: hsl(var(--ot-accent-h), var(--ot-accent-s), calc(var(--ot-accent-l) - 6%)); + .ettic-otc-chat-msg .ettic-otc-chat-contact-btn:hover, + .ettic-otc-chat-msg .ettic-otc-chat-contact-btn:focus { + background: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), calc(var(--ettic-otc-accent-l) - 6%)); } /* ─── Docked input (fixed at viewport bottom) ─ */ - .ot-chat-dock { + .ettic-otc-chat-dock { position: fixed; left: 0; right: 0; @@ -498,7 +498,7 @@ background: linear-gradient(to top, #f9fafb 70%, rgba(249, 250, 251, 0) 100%); pointer-events: none; } - .ot-chat-dock__inner { + .ettic-otc-chat-dock__inner { max-width: 720px; margin: 0 auto; width: 100%; @@ -506,17 +506,17 @@ } /* Suggested question chips — visible when NOT chatting. */ - .ot-chat-chips { + .ettic-otc-chat-chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-bottom: 12px; } - .ot-chat-shell.is-chatting ~ .ot-chat-dock .ot-chat-chips, - body:has(.ot-chat-shell.is-chatting) .ot-chat-chips { display: none; } + .ettic-otc-chat-shell.is-chatting ~ .ettic-otc-chat-dock .ettic-otc-chat-chips, + body:has(.ettic-otc-chat-shell.is-chatting) .ettic-otc-chat-chips { display: none; } - .ot-chat-chip { + .ettic-otc-chat-chip { display: inline-flex; align-items: center; gap: 8px; @@ -532,34 +532,34 @@ transition: border-color 0.15s, background-color 0.15s, transform 0.1s, box-shadow 0.15s; box-shadow: 0 1px 2px rgba(17, 24, 39, 0.04); } - .ot-chat-chip:hover { - border-color: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)); + .ettic-otc-chat-chip:hover { + border-color: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)); background: #ffffff; box-shadow: 0 2px 8px rgba(17, 24, 39, 0.06); } - .ot-chat-chip:active { transform: scale(0.98); } - .ot-chat-chip__icon { + .ettic-otc-chat-chip:active { transform: scale(0.98); } + .ettic-otc-chat-chip__icon { display: inline-flex; align-items: center; justify-content: center; color: #9ca3af; flex-shrink: 0; } - .ot-chat-chip:hover .ot-chat-chip__icon { - color: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)); + .ettic-otc-chat-chip:hover .ettic-otc-chat-chip__icon { + color: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)); } /* Input bar — single card, no nested container. */ - .ot-chat-inputbar { - /* Transparent wrapper. The visible card is .ot-chat-inputbar__field. */ + .ettic-otc-chat-inputbar { + /* Transparent wrapper. The visible card is .ettic-otc-chat-inputbar__field. */ background: transparent; border: 0; padding: 0; } - .ot-chat-form { display: block; } + .ettic-otc-chat-form { display: block; } - .ot-chat-inputbar__field { + .ettic-otc-chat-inputbar__field { display: flex; align-items: center; gap: 10px; @@ -572,14 +572,14 @@ 0 8px 32px rgba(17, 24, 39, 0.08); transition: border-color 0.15s, box-shadow 0.15s; } - .ot-chat-inputbar:focus-within .ot-chat-inputbar__field { - border-color: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)); + .ettic-otc-chat-inputbar:focus-within .ettic-otc-chat-inputbar__field { + border-color: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)); box-shadow: - 0 0 0 3px hsla(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l), 0.1), + 0 0 0 3px hsla(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l), 0.1), 0 8px 32px rgba(17, 24, 39, 0.08); } - .ot-chat-form textarea { + .ettic-otc-chat-form textarea { flex: 1; min-width: 0; border: 0; @@ -594,12 +594,12 @@ max-height: 180px; overflow-y: auto; } - .ot-chat-form textarea::placeholder { + .ettic-otc-chat-form textarea::placeholder { color: #9ca3af; } /* Reset button — ghost, disabled by default. */ - .ot-chat-reset { + .ettic-otc-chat-reset { flex: 0 0 auto; display: inline-flex; align-items: center; @@ -613,86 +613,86 @@ cursor: pointer; transition: color 0.15s, background-color 0.15s, opacity 0.15s; } - .ot-chat-reset:hover:not(:disabled) { - color: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)); + .ettic-otc-chat-reset:hover:not(:disabled) { + color: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)); background: #f3f4f6; } - .ot-chat-reset:disabled { + .ettic-otc-chat-reset:disabled { opacity: 0.35; cursor: default; } /* Send button — filled accent, icon only. */ - .ot-chat-send { + .ettic-otc-chat-send { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; - background: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)); + background: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)); color: #ffffff; border: 0; border-radius: 8px; cursor: pointer; transition: background-color 0.15s, transform 0.1s; } - .ot-chat-send:hover:not(:disabled) { - background: hsl(var(--ot-accent-h), var(--ot-accent-s), calc(var(--ot-accent-l) - 6%)); + .ettic-otc-chat-send:hover:not(:disabled) { + background: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), calc(var(--ettic-otc-accent-l) - 6%)); } - .ot-chat-send:active { transform: scale(0.96); } - .ot-chat-send:disabled { + .ettic-otc-chat-send:active { transform: scale(0.96); } + .ettic-otc-chat-send:disabled { background: #e5e7eb; color: #9ca3af; cursor: not-allowed; } - .ot-chat-send svg, - .ot-chat-reset svg { + .ettic-otc-chat-send svg, + .ettic-otc-chat-reset svg { flex-shrink: 0; display: block; } /* Optical-centering nudge for the paper plane — its mathematical center and its visual center of mass are not the same (all the ink is in the upper-right quadrant). 1px down-left restores perceived center. */ - .ot-chat-send svg { + .ettic-otc-chat-send svg { transform: translate(-1px, 1px); } - .ot-chat-send--stop { background: #ef4444; } - .ot-chat-send--stop:hover { background: #dc2626; } + .ettic-otc-chat-send--stop { background: #ef4444; } + .ettic-otc-chat-send--stop:hover { background: #dc2626; } /* Legal line below input. */ - .ot-chat-dock__legal { + .ettic-otc-chat-dock__legal { margin: 8px 0 0; text-align: center; font-size: 11px; color: #9ca3af; line-height: 1.5; } - .ot-chat-dock__legal a { + .ettic-otc-chat-dock__legal a { color: #6b7280; text-decoration: underline; } /* ─── Inline banners / hints ─────────────────── */ - .ot-chat-banner { + .ettic-otc-chat-banner { padding: 12px 16px; border-radius: 12px; margin: 16px 0; font-size: 14px; } - .ot-chat-banner--error { + .ettic-otc-chat-banner--error { background: #fef2f2; border: 1px solid #fecaca; color: #991b1b; } - .ot-chat-banner--warn { + .ettic-otc-chat-banner--warn { background: #fefce8; border: 1px solid #fef08a; color: #854d0e; } - .ot-chat-banner button { + .ettic-otc-chat-banner button { margin-left: 8px; padding: 4px 12px; background: #ffffff; @@ -704,16 +704,16 @@ border-radius: 8px; } - .ot-chat-long-hint { + .ettic-otc-chat-long-hint { font-size: 12px; color: #9ca3af; text-align: center; margin: 16px 0; } - .ot-chat-long-hint button { + .ettic-otc-chat-long-hint button { background: transparent; border: 0; - color: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)); + color: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)); cursor: pointer; font: inherit; text-decoration: underline; @@ -721,11 +721,11 @@ /* ─── State pages (unavailable / exhausted) ─── */ - .ot-chat-state { + .ettic-otc-chat-state { text-align: center; padding: 64px 16px; } - .ot-chat-state__icon { + .ettic-otc-chat-state__icon { display: inline-flex; align-items: center; justify-content: center; @@ -736,17 +736,17 @@ color: #6b7280; margin-bottom: 16px; } - .ot-chat-state h1 { + .ettic-otc-chat-state h1 { font-size: 22px; font-weight: 600; margin: 0 0 8px; } - .ot-chat-state p { + .ettic-otc-chat-state p { color: #6b7280; margin: 0 auto 24px; max-width: 440px; } - .ot-chat-state__actions { + .ettic-otc-chat-state__actions { display: flex; gap: 8px; justify-content: center; @@ -755,26 +755,26 @@ /* ─── No-JS fallback ─────────────────────────── */ - .ot-chat-noscript { + .ettic-otc-chat-noscript { margin-top: 32px; padding: 24px; background: #fffbeb; border: 1px solid #fde68a; border-radius: 12px; } - .ot-chat-noscript p { + .ettic-otc-chat-noscript p { margin: 0 0 12px; font-size: 14px; color: #713f12; } - .ot-chat-noscript label { + .ettic-otc-chat-noscript label { display: block; margin-bottom: 8px; font-size: 13px; font-weight: 600; color: #713f12; } - .ot-chat-noscript textarea { + .ettic-otc-chat-noscript textarea { width: 100%; padding: 12px; border: 1px solid #fde68a; @@ -782,10 +782,10 @@ font: inherit; font-size: 14px; } - .ot-chat-noscript button { + .ettic-otc-chat-noscript button { margin-top: 8px; padding: 10px 16px; - background: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)); + background: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)); color: #ffffff; border: 0; border-radius: 8px; @@ -795,7 +795,7 @@ /* ─── Utility ───────────────────────────────── */ - .ot-visually-hidden { + .ettic-otc-visually-hidden { position: absolute !important; width: 1px; height: 1px; @@ -807,7 +807,7 @@ border: 0; } - .ot-button { + .ettic-otc-button { display: inline-block; padding: 10px 18px; border-radius: 8px; @@ -818,19 +818,19 @@ border: 0; transition: background-color 0.15s, transform 0.1s; } - .ot-button--primary { - background: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)); + .ettic-otc-button--primary { + background: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)); color: #ffffff; } - .ot-button--primary:hover { - background: hsl(var(--ot-accent-h), var(--ot-accent-s), calc(var(--ot-accent-l) - 6%)); + .ettic-otc-button--primary:hover { + background: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), calc(var(--ettic-otc-accent-l) - 6%)); } - .ot-button--ghost { + .ettic-otc-button--ghost { background: #ffffff; color: #374151; border: 1px solid #e5e7eb; } - .ot-button--ghost:hover { + .ettic-otc-button--ghost:hover { border-color: #d1d5db; background: #f9fafb; } @@ -838,21 +838,21 @@ /* ─── Responsive ────────────────────────────── */ @media (max-width: 720px) { - .ot-chat-main { padding: 32px 16px 220px; } + .ettic-otc-chat-main { padding: 32px 16px 220px; } } @media (max-width: 480px) { - .ot-chat-intro { padding: 16px 0 32px; } - .ot-chat-intro__title { font-size: 22px; } - .ot-chat-chips { gap: 6px; } - .ot-chat-chip { font-size: 12px; padding: 6px 12px; } - .ot-chat-dock__legal { display: none; } + .ettic-otc-chat-intro { padding: 16px 0 32px; } + .ettic-otc-chat-intro__title { font-size: 22px; } + .ettic-otc-chat-chips { gap: 6px; } + .ettic-otc-chat-chip { font-size: 12px; padding: 6px 12px; } + .ettic-otc-chat-dock__legal { display: none; } } /* ─── Print ─────────────────────────────────── */ @media print { - .ot-nav, .ot-chat-dock, .ot-chat-chips, - .ot-chat-msg__actions, .ot-chat-noscript { + .ettic-otc-nav, .ettic-otc-chat-dock, .ettic-otc-chat-chips, + .ettic-otc-chat-msg__actions, .ettic-otc-chat-noscript { display: none !important; } } diff --git a/assets/css/frontend.css b/assets/css/frontend.css index e280537..8a28bf3 100644 --- a/assets/css/frontend.css +++ b/assets/css/frontend.css @@ -15,13 +15,13 @@ U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } -@layer opentrust { +@layer ettic-otc { /* ═══════════════════════════════════════════════ - RESET (scoped to .ot-body) + RESET (scoped to .ettic-otc-body) ═══════════════════════════════════════════════ */ - .ot-body { + .ettic-otc-body { margin: 0; padding: 0; -webkit-font-smoothing: antialiased; @@ -29,9 +29,9 @@ text-rendering: optimizeLegibility; } - .ot-body *, - .ot-body *::before, - .ot-body *::after { + .ettic-otc-body *, + .ettic-otc-body *::before, + .ettic-otc-body *::after { box-sizing: border-box; } @@ -39,140 +39,140 @@ DESIGN TOKENS ═══════════════════════════════════════════════ */ - .ot-body { - /* Accent — injected from PHP as --ot-accent-h, --ot-accent-s, --ot-accent-l. - --ot-accent-l-safe is the same lightness clamped to ≤48% so pale picks stay - readable on white surfaces. Raw --ot-accent-l is still used on dark + .ettic-otc-body { + /* Accent — injected from PHP as --ettic-otc-accent-h, --ettic-otc-accent-s, --ettic-otc-accent-l. + --ettic-otc-accent-l-safe is the same lightness clamped to ≤48% so pale picks stay + readable on white surfaces. Raw --ettic-otc-accent-l is still used on dark backgrounds (hero, nav, ask button) where a bright accent reads fine. */ - --ot-accent: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l-safe)); - --ot-accent-hover: hsl(var(--ot-accent-h), var(--ot-accent-s), calc(var(--ot-accent-l-safe) - 6%)); - --ot-accent-light: hsl(var(--ot-accent-h), calc(var(--ot-accent-s) * 0.6), 95%); - --ot-accent-subtle: hsl(var(--ot-accent-h), calc(var(--ot-accent-s) * 0.4), 97.5%); - --ot-accent-10: hsl(var(--ot-accent-h), calc(var(--ot-accent-s) * 0.5), 92%); - --ot-accent-border: hsl(var(--ot-accent-h), calc(var(--ot-accent-s) * 0.35), 88%); - --ot-accent-text: hsl(var(--ot-accent-h), calc(var(--ot-accent-s) * 0.9), calc(var(--ot-accent-l-safe) - 5%)); + --ettic-otc-accent: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l-safe)); + --ettic-otc-accent-hover: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), calc(var(--ettic-otc-accent-l-safe) - 6%)); + --ettic-otc-accent-light: hsl(var(--ettic-otc-accent-h), calc(var(--ettic-otc-accent-s) * 0.6), 95%); + --ettic-otc-accent-subtle: hsl(var(--ettic-otc-accent-h), calc(var(--ettic-otc-accent-s) * 0.4), 97.5%); + --ettic-otc-accent-10: hsl(var(--ettic-otc-accent-h), calc(var(--ettic-otc-accent-s) * 0.5), 92%); + --ettic-otc-accent-border: hsl(var(--ettic-otc-accent-h), calc(var(--ettic-otc-accent-s) * 0.35), 88%); + --ettic-otc-accent-text: hsl(var(--ettic-otc-accent-h), calc(var(--ettic-otc-accent-s) * 0.9), calc(var(--ettic-otc-accent-l-safe) - 5%)); /* Neutrals — slightly warm undertone for premium feel */ - --ot-white: #ffffff; - --ot-gray-25: #fcfcfd; - --ot-gray-50: #f8f9fb; - --ot-gray-100: #f1f3f5; - --ot-gray-200: #e4e7ec; - --ot-gray-300: #cdd3db; - --ot-gray-400: #98a2b3; - --ot-gray-500: #667085; - --ot-gray-600: #475467; - --ot-gray-700: #344054; - --ot-gray-800: #1d2939; - --ot-gray-900: #101828; + --ettic-otc-white: #ffffff; + --ettic-otc-gray-25: #fcfcfd; + --ettic-otc-gray-50: #f8f9fb; + --ettic-otc-gray-100: #f1f3f5; + --ettic-otc-gray-200: #e4e7ec; + --ettic-otc-gray-300: #cdd3db; + --ettic-otc-gray-400: #98a2b3; + --ettic-otc-gray-500: #667085; + --ettic-otc-gray-600: #475467; + --ettic-otc-gray-700: #344054; + --ettic-otc-gray-800: #1d2939; + --ettic-otc-gray-900: #101828; /* Semantic */ - --ot-success: #12b76a; - --ot-success-bg: #ecfdf3; - --ot-success-fg: #027a48; - --ot-success-ring: #a6f4c5; - --ot-warning: #f79009; - --ot-warning-bg: #fffaeb; - --ot-warning-fg: #b54708; - --ot-warning-ring: #fedf89; - --ot-error: #dc2626; - --ot-error-fg: #b91c1c; - --ot-muted-bg: #f2f4f7; - --ot-muted-fg: #667085; + --ettic-otc-success: #12b76a; + --ettic-otc-success-bg: #ecfdf3; + --ettic-otc-success-fg: #027a48; + --ettic-otc-success-ring: #a6f4c5; + --ettic-otc-warning: #f79009; + --ettic-otc-warning-bg: #fffaeb; + --ettic-otc-warning-fg: #b54708; + --ettic-otc-warning-ring: #fedf89; + --ettic-otc-error: #dc2626; + --ettic-otc-error-fg: #b91c1c; + --ettic-otc-muted-bg: #f2f4f7; + --ettic-otc-muted-fg: #667085; /* Typography */ - --ot-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', + --ettic-otc-font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - --ot-font-mono: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Consolas, monospace; - - --ot-text-xs: 0.75rem; - --ot-text-sm: 0.8125rem; - --ot-text-base: 0.9375rem; - --ot-text-md: 1rem; - --ot-text-lg: 1.125rem; - --ot-text-xl: 1.25rem; - --ot-text-2xl: 1.5rem; - --ot-text-3xl: 1.875rem; - --ot-text-4xl: 2.375rem; - --ot-text-5xl: 3rem; - - --ot-leading-tight: 1.2; - --ot-leading-snug: 1.375; - --ot-leading-normal: 1.625; + --ettic-otc-font-mono: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Consolas, monospace; + + --ettic-otc-text-xs: 0.75rem; + --ettic-otc-text-sm: 0.8125rem; + --ettic-otc-text-base: 0.9375rem; + --ettic-otc-text-md: 1rem; + --ettic-otc-text-lg: 1.125rem; + --ettic-otc-text-xl: 1.25rem; + --ettic-otc-text-2xl: 1.5rem; + --ettic-otc-text-3xl: 1.875rem; + --ettic-otc-text-4xl: 2.375rem; + --ettic-otc-text-5xl: 3rem; + + --ettic-otc-leading-tight: 1.2; + --ettic-otc-leading-snug: 1.375; + --ettic-otc-leading-normal: 1.625; /* Spacing scale */ - --ot-space-0: 0; - --ot-space-1: 0.25rem; - --ot-space-1h: 0.375rem; - --ot-space-2: 0.5rem; - --ot-space-3: 0.75rem; - --ot-space-4: 1rem; - --ot-space-5: 1.25rem; - --ot-space-6: 1.5rem; - --ot-space-8: 2rem; - --ot-space-10: 2.5rem; - --ot-space-12: 3rem; - --ot-space-16: 4rem; - --ot-space-20: 5rem; - --ot-space-24: 6rem; - --ot-space-32: 8rem; + --ettic-otc-space-0: 0; + --ettic-otc-space-1: 0.25rem; + --ettic-otc-space-1h: 0.375rem; + --ettic-otc-space-2: 0.5rem; + --ettic-otc-space-3: 0.75rem; + --ettic-otc-space-4: 1rem; + --ettic-otc-space-5: 1.25rem; + --ettic-otc-space-6: 1.5rem; + --ettic-otc-space-8: 2rem; + --ettic-otc-space-10: 2.5rem; + --ettic-otc-space-12: 3rem; + --ettic-otc-space-16: 4rem; + --ettic-otc-space-20: 5rem; + --ettic-otc-space-24: 6rem; + --ettic-otc-space-32: 8rem; /* Radii */ - --ot-radius-sm: 8px; - --ot-radius-md: 12px; - --ot-radius-lg: 16px; - --ot-radius-xl: 20px; - --ot-radius-2xl: 24px; - --ot-radius-full: 9999px; + --ettic-otc-radius-sm: 8px; + --ettic-otc-radius-md: 12px; + --ettic-otc-radius-lg: 16px; + --ettic-otc-radius-xl: 20px; + --ettic-otc-radius-2xl: 24px; + --ettic-otc-radius-full: 9999px; /* Shadows — layered for depth */ - --ot-shadow-xs: 0 1px 2px rgba(16, 24, 40, 0.05); - --ot-shadow-sm: 0 1px 3px rgba(16, 24, 40, 0.06), + --ettic-otc-shadow-xs: 0 1px 2px rgba(16, 24, 40, 0.05); + --ettic-otc-shadow-sm: 0 1px 3px rgba(16, 24, 40, 0.06), 0 1px 2px rgba(16, 24, 40, 0.04); - --ot-shadow-md: 0 4px 8px -2px rgba(16, 24, 40, 0.06), + --ettic-otc-shadow-md: 0 4px 8px -2px rgba(16, 24, 40, 0.06), 0 2px 4px -2px rgba(16, 24, 40, 0.04); - --ot-shadow-lg: 0 12px 16px -4px rgba(16, 24, 40, 0.08), + --ettic-otc-shadow-lg: 0 12px 16px -4px rgba(16, 24, 40, 0.08), 0 4px 6px -2px rgba(16, 24, 40, 0.03); - --ot-shadow-xl: 0 20px 24px -4px rgba(16, 24, 40, 0.08), + --ettic-otc-shadow-xl: 0 20px 24px -4px rgba(16, 24, 40, 0.08), 0 8px 8px -4px rgba(16, 24, 40, 0.03); - --ot-ring-accent: 0 0 0 4px var(--ot-accent-light); + --ettic-otc-ring-accent: 0 0 0 4px var(--ettic-otc-accent-light); /* Transitions */ - --ot-transition-fast: 120ms ease; - --ot-transition: 200ms ease; - --ot-transition-slow: 300ms ease; + --ettic-otc-transition-fast: 120ms ease; + --ettic-otc-transition: 200ms ease; + --ettic-otc-transition-slow: 300ms ease; /* Layout */ - --ot-container: 1200px; - --ot-container-narrow: 780px; - --ot-nav-height: 56px; + --ettic-otc-container: 1200px; + --ettic-otc-container-narrow: 780px; + --ettic-otc-nav-height: 56px; } /* ═══════════════════════════════════════════════ BASE ═══════════════════════════════════════════════ */ - .ot-body { - font-family: var(--ot-font); - font-size: var(--ot-text-base); - line-height: var(--ot-leading-normal); - color: var(--ot-gray-700); - background: var(--ot-white); + .ettic-otc-body { + font-family: var(--ettic-otc-font); + font-size: var(--ettic-otc-text-base); + line-height: var(--ettic-otc-leading-normal); + color: var(--ettic-otc-gray-700); + background: var(--ettic-otc-white); font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11'; } - .ot-body a { - color: var(--ot-accent-text); + .ettic-otc-body a { + color: var(--ettic-otc-accent-text); text-decoration: none; - transition: color var(--ot-transition-fast); + transition: color var(--ettic-otc-transition-fast); } - .ot-body a:hover { - color: var(--ot-accent-hover); + .ettic-otc-body a:hover { + color: var(--ettic-otc-accent-hover); } - .ot-body a:focus-visible { - outline: 2px solid var(--ot-accent); + .ettic-otc-body a:focus-visible { + outline: 2px solid var(--ettic-otc-accent); outline-offset: 2px; border-radius: 4px; } @@ -181,39 +181,39 @@ SKIP LINK (accessibility) ═══════════════════════════════════════════════ */ - .ot-skip-link { + .ettic-otc-skip-link { position: absolute; top: -9999px; left: 0; z-index: 10000; - background: var(--ot-accent); - color: var(--ot-white); - padding: var(--ot-space-3) var(--ot-space-6); + background: var(--ettic-otc-accent); + color: var(--ettic-otc-white); + padding: var(--ettic-otc-space-3) var(--ettic-otc-space-6); font-weight: 600; - border-radius: 0 0 var(--ot-radius-sm) 0; + border-radius: 0 0 var(--ettic-otc-radius-sm) 0; } - .ot-skip-link:focus { + .ettic-otc-skip-link:focus { top: 0; - color: var(--ot-white); + color: var(--ettic-otc-white); } /* ═══════════════════════════════════════════════ LAYOUT ═══════════════════════════════════════════════ */ - .ot-container { + .ettic-otc-container { width: 100%; - max-width: var(--ot-container); + max-width: var(--ettic-otc-container); margin-inline: auto; - padding-inline: var(--ot-space-8); + padding-inline: var(--ettic-otc-space-8); } - .ot-container--narrow { - max-width: var(--ot-container-narrow); + .ettic-otc-container--narrow { + max-width: var(--ettic-otc-container-narrow); } - .ot-main { + .ettic-otc-main { padding-block: 0; } @@ -221,86 +221,86 @@ NAVIGATION — dark, integrated with hero ═══════════════════════════════════════════════ */ - .ot-nav { - background: hsl(var(--ot-accent-h), 30%, 7%); + .ettic-otc-nav { + background: hsl(var(--ettic-otc-accent-h), 30%, 7%); position: sticky; top: 0; z-index: 100; - height: var(--ot-nav-height); + height: var(--ettic-otc-nav-height); } - .ot-nav__inner { + .ettic-otc-nav__inner { display: flex; align-items: center; - gap: var(--ot-space-6); + gap: var(--ettic-otc-space-6); height: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; } - .ot-nav__inner::-webkit-scrollbar { + .ettic-otc-nav__inner::-webkit-scrollbar { display: none; } - .ot-body a.ot-nav__brand { + .ettic-otc-body a.ettic-otc-nav__brand { display: flex; align-items: center; - gap: var(--ot-space-3); + gap: var(--ettic-otc-space-3); text-decoration: none; flex-shrink: 0; - color: var(--ot-white); + color: var(--ettic-otc-white); } - .ot-body a.ot-nav__brand:hover { - color: var(--ot-white); + .ettic-otc-body a.ettic-otc-nav__brand:hover { + color: var(--ettic-otc-white); } - .ot-nav__brand-logo { + .ettic-otc-nav__brand-logo { max-height: 22px; width: auto; object-fit: contain; } - .ot-nav__brand-name { - font-size: var(--ot-text-md); + .ettic-otc-nav__brand-name { + font-size: var(--ettic-otc-text-md); font-weight: 650; letter-spacing: -0.01em; white-space: nowrap; } - .ot-nav__links { + .ettic-otc-nav__links { display: flex; align-items: center; - gap: var(--ot-space-1); + gap: var(--ettic-otc-space-1); } - .ot-body a.ot-nav__link { + .ettic-otc-body a.ettic-otc-nav__link { display: flex; align-items: center; - padding: var(--ot-space-1h) var(--ot-space-3); - font-size: var(--ot-text-sm); + padding: var(--ettic-otc-space-1h) var(--ettic-otc-space-3); + font-size: var(--ettic-otc-text-sm); font-weight: 400; color: rgba(255, 255, 255, 0.7); white-space: nowrap; - border-radius: var(--ot-radius-sm); - transition: color var(--ot-transition-fast), background var(--ot-transition-fast); + border-radius: var(--ettic-otc-radius-sm); + transition: color var(--ettic-otc-transition-fast), background var(--ettic-otc-transition-fast); } - .ot-body a.ot-nav__link:hover { + .ettic-otc-body a.ettic-otc-nav__link:hover { color: rgba(255, 255, 255, 0.95); background: rgba(255, 255, 255, 0.08); } - .ot-body a.ot-nav__link--active { - color: var(--ot-white); + .ettic-otc-body a.ettic-otc-nav__link--active { + color: var(--ettic-otc-white); background: rgba(255, 255, 255, 0.12); font-weight: 500; } - .ot-nav__cta { + .ettic-otc-nav__cta { margin-inline-start: auto; - padding: var(--ot-space-2) 0; + padding: var(--ettic-otc-space-2) 0; flex-shrink: 0; } @@ -310,19 +310,19 @@ * inset highlight for "carved" dimension, tight neutral drop shadow. * Intentionally quiet — matches the rest of the system. * ────────────────────────────────────────────── */ - .ot-body a.ot-nav__ask { + .ettic-otc-body a.ettic-otc-nav__ask { display: inline-flex; align-items: center; gap: 7px; padding: 8px 16px; - border: 1px solid hsla(var(--ot-accent-h), 80%, 70%, 0.25); + border: 1px solid hsla(var(--ettic-otc-accent-h), 80%, 70%, 0.25); border-radius: 999px; background: linear-gradient(180deg, - hsl(var(--ot-accent-h), var(--ot-accent-s), calc(var(--ot-accent-l) + 6%)) 0%, - hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)) 100% + hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), calc(var(--ettic-otc-accent-l) + 6%)) 0%, + hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)) 100% ); - color: var(--ot-accent-contrast, #ffffff); - font-size: var(--ot-text-sm); + color: var(--ettic-otc-accent-contrast, #ffffff); + font-size: var(--ettic-otc-text-sm); font-weight: 600; text-decoration: none; white-space: nowrap; @@ -333,48 +333,48 @@ background 180ms ease; } - .ot-body a.ot-nav__ask:hover, - .ot-body a.ot-nav__ask:focus-visible { - color: var(--ot-accent-contrast, #ffffff); + .ettic-otc-body a.ettic-otc-nav__ask:hover, + .ettic-otc-body a.ettic-otc-nav__ask:focus-visible { + color: var(--ettic-otc-accent-contrast, #ffffff); transform: translateY(-1px); background: linear-gradient(180deg, - hsl(var(--ot-accent-h), var(--ot-accent-s), calc(var(--ot-accent-l) + 9%)) 0%, - hsl(var(--ot-accent-h), var(--ot-accent-s), calc(var(--ot-accent-l) + 3%)) 100% + hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), calc(var(--ettic-otc-accent-l) + 9%)) 0%, + hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), calc(var(--ettic-otc-accent-l) + 3%)) 100% ); box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4); } - .ot-body a.ot-nav__ask:active { + .ettic-otc-body a.ettic-otc-nav__ask:active { transform: translateY(0); } - .ot-nav__ask-icon { + .ettic-otc-nav__ask-icon { flex-shrink: 0; transition: transform 240ms cubic-bezier(0.2, 0.8, 0.2, 1); } - .ot-body a.ot-nav__ask:hover .ot-nav__ask-icon, - .ot-body a.ot-nav__ask:focus-visible .ot-nav__ask-icon { + .ettic-otc-body a.ettic-otc-nav__ask:hover .ettic-otc-nav__ask-icon, + .ettic-otc-body a.ettic-otc-nav__ask:focus-visible .ettic-otc-nav__ask-icon { transform: rotate(8deg); } - .ot-nav__ask-label { + .ettic-otc-nav__ask-label { line-height: 1; } - .ot-body .ot-nav__ask--active { + .ettic-otc-body .ettic-otc-nav__ask--active { display: inline-flex; align-items: center; gap: 7px; padding: 8px 16px; - border: 1px solid hsla(var(--ot-accent-h), 80%, 70%, 0.35); + border: 1px solid hsla(var(--ettic-otc-accent-h), 80%, 70%, 0.35); border-radius: 999px; background: linear-gradient(180deg, - hsl(var(--ot-accent-h), var(--ot-accent-s), calc(var(--ot-accent-l) + 6%)) 0%, - hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l)) 100% + hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), calc(var(--ettic-otc-accent-l) + 6%)) 0%, + hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l)) 100% ); - color: var(--ot-accent-contrast, #ffffff); - font-size: var(--ot-text-sm); + color: var(--ettic-otc-accent-contrast, #ffffff); + font-size: var(--ettic-otc-text-sm); font-weight: 600; white-space: nowrap; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35); @@ -382,11 +382,11 @@ } @media (prefers-reduced-motion: reduce) { - .ot-body a.ot-nav__ask, - .ot-nav__ask-icon { + .ettic-otc-body a.ettic-otc-nav__ask, + .ettic-otc-nav__ask-icon { transition: none; } - .ot-body a.ot-nav__ask:hover .ot-nav__ask-icon { + .ettic-otc-body a.ettic-otc-nav__ask:hover .ettic-otc-nav__ask-icon { transform: none; } } @@ -395,43 +395,43 @@ HERO — dark, accent-tinted with subtle gradient ═══════════════════════════════════════════════ */ - .ot-hero { + .ettic-otc-hero { background: - radial-gradient(ellipse 70% 50% at 50% 100%, hsla(var(--ot-accent-h), 80%, 30%, 0.35) 0%, transparent 70%), - hsl(var(--ot-accent-h), 30%, 7%); - padding-block: var(--ot-space-20) var(--ot-space-16); + radial-gradient(ellipse 70% 50% at 50% 100%, hsla(var(--ettic-otc-accent-h), 80%, 30%, 0.35) 0%, transparent 70%), + hsl(var(--ettic-otc-accent-h), 30%, 7%); + padding-block: var(--ettic-otc-space-20) var(--ettic-otc-space-16); } - .ot-hero__inner { + .ettic-otc-hero__inner { display: flex; flex-direction: column; align-items: center; text-align: center; - gap: var(--ot-space-5); + gap: var(--ettic-otc-space-5); } - .ot-hero__badge { + .ettic-otc-hero__badge { display: inline-flex; align-items: center; - gap: var(--ot-space-2); - font-size: var(--ot-text-xs); + gap: var(--ettic-otc-space-2); + font-size: var(--ettic-otc-text-xs); font-weight: 500; color: rgba(255, 255, 255, 0.6); letter-spacing: 0.08em; text-transform: uppercase; } - .ot-hero__badge-dot { + .ettic-otc-hero__badge-dot { width: 8px; height: 8px; border-radius: 2px; - background: var(--ot-success); + background: var(--ettic-otc-success); } - .ot-hero__title { - font-size: var(--ot-text-5xl); + .ettic-otc-hero__title { + font-size: var(--ettic-otc-text-5xl); font-weight: 500; - color: var(--ot-white); + color: var(--ettic-otc-white); margin: 0; line-height: 1.15; letter-spacing: -0.025em; @@ -439,49 +439,49 @@ text-wrap: balance; } - .ot-hero__tagline { - font-size: var(--ot-text-lg); + .ettic-otc-hero__tagline { + font-size: var(--ettic-otc-text-lg); color: rgba(255, 255, 255, 0.55); margin: 0; max-width: 540px; - line-height: var(--ot-leading-normal); + line-height: var(--ettic-otc-leading-normal); font-weight: 400; } - .ot-hero__stats { + .ettic-otc-hero__stats { display: flex; - gap: var(--ot-space-6); - margin-top: var(--ot-space-8); + gap: var(--ettic-otc-space-6); + margin-top: var(--ettic-otc-space-8); flex-wrap: wrap; justify-content: center; align-items: baseline; } - .ot-hero__stat { + .ettic-otc-hero__stat { display: flex; align-items: baseline; - gap: var(--ot-space-2); + gap: var(--ettic-otc-space-2); } - .ot-hero__stat + .ot-hero__stat::before { + .ettic-otc-hero__stat + .ettic-otc-hero__stat::before { content: '\00b7'; color: rgba(255, 255, 255, 0.2); - font-size: var(--ot-text-2xl); + font-size: var(--ettic-otc-text-2xl); font-weight: 700; line-height: 1; - margin-inline-end: var(--ot-space-4); + margin-inline-end: var(--ettic-otc-space-4); } - .ot-hero__stat-value { - font-size: var(--ot-text-2xl); + .ettic-otc-hero__stat-value { + font-size: var(--ettic-otc-text-2xl); font-weight: 600; - color: var(--ot-white); + color: var(--ettic-otc-white); letter-spacing: -0.02em; line-height: 1; } - .ot-hero__stat-label { - font-size: var(--ot-text-sm); + .ettic-otc-hero__stat-label { + font-size: var(--ettic-otc-text-sm); color: rgba(255, 255, 255, 0.45); font-weight: 400; } @@ -490,66 +490,66 @@ SECTIONS — generous spacing, alternating bg ═══════════════════════════════════════════════ */ - .ot-section { - padding-block: var(--ot-space-20); - scroll-margin-top: var(--ot-nav-height); + .ettic-otc-section { + padding-block: var(--ettic-otc-space-20); + scroll-margin-top: var(--ettic-otc-nav-height); } - .ot-section:nth-child(odd) { - background: var(--ot-gray-100); + .ettic-otc-section:nth-child(odd) { + background: var(--ettic-otc-gray-100); } - .ot-section__header { - margin-bottom: var(--ot-space-8); + .ettic-otc-section__header { + margin-bottom: var(--ettic-otc-space-8); text-align: center; max-width: 640px; margin-inline: auto; } - .ot-section__label { + .ettic-otc-section__label { display: inline-flex; align-items: center; - gap: var(--ot-space-2); - font-size: var(--ot-text-xs); + gap: var(--ettic-otc-space-2); + font-size: var(--ettic-otc-text-xs); font-weight: 600; - color: var(--ot-accent-text); + color: var(--ettic-otc-accent-text); text-transform: uppercase; letter-spacing: 0.08em; - margin-bottom: var(--ot-space-3); + margin-bottom: var(--ettic-otc-space-3); } - .ot-section__label-icon { + .ettic-otc-section__label-icon { width: 16px; height: 16px; fill: currentColor; opacity: 0.7; } - .ot-section__title { - font-size: var(--ot-text-3xl); + .ettic-otc-section__title { + font-size: var(--ettic-otc-text-3xl); font-weight: 500; - color: var(--ot-gray-900); - margin: 0 0 var(--ot-space-3) 0; + color: var(--ettic-otc-gray-900); + margin: 0 0 var(--ettic-otc-space-3) 0; letter-spacing: -0.02em; - line-height: var(--ot-leading-tight); + line-height: var(--ettic-otc-leading-tight); text-wrap: balance; } - .ot-section__description { - font-size: var(--ot-text-md); - color: var(--ot-gray-500); + .ettic-otc-section__description { + font-size: var(--ettic-otc-text-md); + color: var(--ettic-otc-gray-500); margin: 0; - line-height: var(--ot-leading-normal); + line-height: var(--ettic-otc-leading-normal); } - .ot-section__subtitle { - font-size: var(--ot-text-lg); + .ettic-otc-section__subtitle { + font-size: var(--ettic-otc-text-lg); font-weight: 600; - color: var(--ot-gray-800); - margin: var(--ot-space-12) 0 var(--ot-space-6) 0; + color: var(--ettic-otc-gray-800); + margin: var(--ettic-otc-space-12) 0 var(--ettic-otc-space-6) 0; } - .ot-section__subtitle:first-of-type { + .ettic-otc-section__subtitle:first-of-type { margin-top: 0; } @@ -557,83 +557,83 @@ CARD GRID ═══════════════════════════════════════════════ */ - .ot-card-grid { + .ettic-otc-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); - gap: var(--ot-space-5); + gap: var(--ettic-otc-space-5); } - .ot-card-grid--3col { + .ettic-otc-card-grid--3col { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); } - .ot-card { - background: var(--ot-white); - border: 1px solid var(--ot-gray-200); - border-radius: var(--ot-radius-lg); - padding: var(--ot-space-6); - box-shadow: var(--ot-shadow-sm); - transition: box-shadow var(--ot-transition), border-color var(--ot-transition), transform var(--ot-transition); + .ettic-otc-card { + background: var(--ettic-otc-white); + border: 1px solid var(--ettic-otc-gray-200); + border-radius: var(--ettic-otc-radius-lg); + padding: var(--ettic-otc-space-6); + box-shadow: var(--ettic-otc-shadow-sm); + transition: box-shadow var(--ettic-otc-transition), border-color var(--ettic-otc-transition), transform var(--ettic-otc-transition); } - .ot-card:hover { - box-shadow: var(--ot-shadow-lg); - border-color: var(--ot-gray-300); + .ettic-otc-card:hover { + box-shadow: var(--ettic-otc-shadow-lg); + border-color: var(--ettic-otc-gray-300); } - .ot-card--clickable { + .ettic-otc-card--clickable { cursor: pointer; } - .ot-card--clickable:hover { - border-color: var(--ot-accent-border); - box-shadow: var(--ot-shadow-lg); + .ettic-otc-card--clickable:hover { + border-color: var(--ettic-otc-accent-border); + box-shadow: var(--ettic-otc-shadow-lg); transform: translateY(-2px); } - .ot-card--clickable:active { + .ettic-otc-card--clickable:active { transform: translateY(0); - box-shadow: var(--ot-shadow-sm); + box-shadow: var(--ettic-otc-shadow-sm); } /* ═══════════════════════════════════════════════ PILLS / BADGES ═══════════════════════════════════════════════ */ - .ot-pill { + .ettic-otc-pill { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; font-size: 11px; font-weight: 500; - border-radius: var(--ot-radius-full); + border-radius: var(--ettic-otc-radius-full); line-height: 1.5; white-space: nowrap; } - .ot-pill--active { - background: var(--ot-success-bg); - color: var(--ot-success-fg); + .ettic-otc-pill--active { + background: var(--ettic-otc-success-bg); + color: var(--ettic-otc-success-fg); } - .ot-pill--in_progress, - .ot-pill--in-progress { - background: var(--ot-warning-bg); - color: var(--ot-warning-fg); + .ettic-otc-pill--in_progress, + .ettic-otc-pill--in-progress { + background: var(--ettic-otc-warning-bg); + color: var(--ettic-otc-warning-fg); } - .ot-pill--expired { - background: var(--ot-muted-bg); - color: var(--ot-muted-fg); + .ettic-otc-pill--expired { + background: var(--ettic-otc-muted-bg); + color: var(--ettic-otc-muted-fg); } - .ot-pill--category { - background: var(--ot-accent-light); - color: var(--ot-accent-text); + .ettic-otc-pill--category { + background: var(--ettic-otc-accent-light); + color: var(--ettic-otc-accent-text); } - .ot-pill__dot { + .ettic-otc-pill__dot { width: 6px; height: 6px; border-radius: 50%; @@ -645,123 +645,123 @@ CERTIFICATION TILES — compact horizontal cards ═══════════════════════════════════════════════ */ - .ot-cert-grid { + .ettic-otc-cert-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); - gap: var(--ot-space-4); + gap: var(--ettic-otc-space-4); } - .ot-cert-tile { + .ettic-otc-cert-tile { display: flex; - gap: var(--ot-space-4); - padding: var(--ot-space-5); - border: 1px solid var(--ot-gray-200); - border-radius: var(--ot-radius-md); - background: var(--ot-white); + gap: var(--ettic-otc-space-4); + padding: var(--ettic-otc-space-5); + border: 1px solid var(--ettic-otc-gray-200); + border-radius: var(--ettic-otc-radius-md); + background: var(--ettic-otc-white); } - .ot-cert-tile__badge { + .ettic-otc-cert-tile__badge { flex-shrink: 0; width: 44px; height: 44px; } - .ot-cert-tile__badge img { + .ettic-otc-cert-tile__badge img { width: 44px; height: 44px; object-fit: contain; - border-radius: var(--ot-radius-sm); + border-radius: var(--ettic-otc-radius-sm); } - .ot-cert-tile__badge-placeholder { + .ettic-otc-cert-tile__badge-placeholder { width: 44px; height: 44px; - border-radius: var(--ot-radius-sm); - background: var(--ot-accent-light); + border-radius: var(--ettic-otc-radius-sm); + background: var(--ettic-otc-accent-light); display: flex; align-items: center; justify-content: center; } - .ot-cert-tile__badge-placeholder svg { + .ettic-otc-cert-tile__badge-placeholder svg { width: 22px; height: 22px; - fill: var(--ot-accent); + fill: var(--ettic-otc-accent); } - .ot-cert-tile__body { + .ettic-otc-cert-tile__body { flex: 1; min-width: 0; display: flex; flex-direction: column; } - .ot-cert-tile__header { + .ettic-otc-cert-tile__header { display: flex; align-items: flex-start; justify-content: space-between; - gap: var(--ot-space-3); + gap: var(--ettic-otc-space-3); } - .ot-cert-tile__name { - font-size: var(--ot-text-md); + .ettic-otc-cert-tile__name { + font-size: var(--ettic-otc-text-md); font-weight: 600; - color: var(--ot-gray-900); + color: var(--ettic-otc-gray-900); margin: 0; line-height: 1.3; } - .ot-cert-tile__issuer { - font-size: var(--ot-text-sm); - color: var(--ot-gray-500); - margin: var(--ot-space-1) 0 0; + .ettic-otc-cert-tile__issuer { + font-size: var(--ettic-otc-text-sm); + color: var(--ettic-otc-gray-500); + margin: var(--ettic-otc-space-1) 0 0; } - .ot-cert-tile__description { - font-size: var(--ot-text-sm); - color: var(--ot-gray-600, #4b5563); + .ettic-otc-cert-tile__description { + font-size: var(--ettic-otc-text-sm); + color: var(--ettic-otc-gray-600, #4b5563); line-height: 1.55; - margin: var(--ot-space-2) 0 0; + margin: var(--ettic-otc-space-2) 0 0; } - .ot-cert-tile__dates { - font-size: var(--ot-text-xs); - color: var(--ot-gray-400); - margin: var(--ot-space-2) 0 0; + .ettic-otc-cert-tile__dates { + font-size: var(--ettic-otc-text-xs); + color: var(--ettic-otc-gray-400); + margin: var(--ettic-otc-space-2) 0 0; } - .ot-cert-tile__artifact { + .ettic-otc-cert-tile__artifact { display: inline-flex; align-items: center; gap: 6px; margin-top: auto; - padding-top: var(--ot-space-3); - font-size: var(--ot-text-xs); + padding-top: var(--ettic-otc-space-3); + font-size: var(--ettic-otc-text-xs); font-weight: 600; - color: var(--ot-gray-700, #374151); + color: var(--ettic-otc-gray-700, #374151); text-decoration: none; } - .ot-cert-tile__artifact svg { + .ettic-otc-cert-tile__artifact svg { fill: currentColor; } - .ot-cert-tile__artifact:hover { - color: var(--ot-accent); + .ettic-otc-cert-tile__artifact:hover { + color: var(--ettic-otc-accent); } /* Status indicators (shared with other sections) */ - .ot-status-indicator { + .ettic-otc-status-indicator { display: inline-flex; align-items: center; gap: 6px; - font-size: var(--ot-text-xs); + font-size: var(--ettic-otc-text-xs); font-weight: 500; white-space: nowrap; } - .ot-status-indicator__dot { + .ettic-otc-status-indicator__dot { width: 7px; height: 7px; border-radius: 50%; @@ -772,79 +772,79 @@ neutral (gray). Cert tier is conveyed by the pill wording, not a separate color or marker. */ - .ot-status-indicator--active { - color: var(--ot-success-fg); + .ettic-otc-status-indicator--active { + color: var(--ettic-otc-success-fg); } - .ot-status-indicator--active .ot-status-indicator__dot { - background: var(--ot-success); + .ettic-otc-status-indicator--active .ettic-otc-status-indicator__dot { + background: var(--ettic-otc-success); } - .ot-status-indicator--in_progress, - .ot-status-indicator--in-progress { - color: var(--ot-warning-fg); + .ettic-otc-status-indicator--in_progress, + .ettic-otc-status-indicator--in-progress { + color: var(--ettic-otc-warning-fg); } - .ot-status-indicator--in_progress .ot-status-indicator__dot, - .ot-status-indicator--in-progress .ot-status-indicator__dot { - background: var(--ot-warning); + .ettic-otc-status-indicator--in_progress .ettic-otc-status-indicator__dot, + .ettic-otc-status-indicator--in-progress .ettic-otc-status-indicator__dot { + background: var(--ettic-otc-warning); } - .ot-status-indicator--expired, - .ot-status-indicator--neutral { - color: var(--ot-gray-500); + .ettic-otc-status-indicator--expired, + .ettic-otc-status-indicator--neutral { + color: var(--ettic-otc-gray-500); } - .ot-status-indicator--expired .ot-status-indicator__dot, - .ot-status-indicator--neutral .ot-status-indicator__dot { - background: var(--ot-gray-400); + .ettic-otc-status-indicator--expired .ettic-otc-status-indicator__dot, + .ettic-otc-status-indicator--neutral .ettic-otc-status-indicator__dot { + background: var(--ettic-otc-gray-400); } /* ═══════════════════════════════════════════════ POLICY TABLE ═══════════════════════════════════════════════ */ - .ot-body a.ot-policy-link { + .ettic-otc-body a.ettic-otc-policy-link { display: inline-flex; align-items: center; - gap: var(--ot-space-2); - color: var(--ot-gray-900); + gap: var(--ettic-otc-space-2); + color: var(--ettic-otc-gray-900); text-decoration: none; } - .ot-body a.ot-policy-link:hover { - color: var(--ot-accent); + .ettic-otc-body a.ettic-otc-policy-link:hover { + color: var(--ettic-otc-accent); } - .ot-policy-link__icon { + .ettic-otc-policy-link__icon { width: 16px; height: 16px; - fill: var(--ot-gray-400); + fill: var(--ettic-otc-gray-400); flex-shrink: 0; } /* Policy ID cell — mono reference code, same visual as on the single-policy eyebrow */ - .ot-policy-ref { + .ettic-otc-policy-ref { display: inline-block; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; font-weight: 500; - color: var(--ot-gray-600); - background: var(--ot-gray-100); + color: var(--ettic-otc-gray-600); + background: var(--ettic-otc-gray-100); padding: 2px 6px; border-radius: 3px; white-space: nowrap; letter-spacing: 0.02em; } - .ot-policy-ref--empty { + .ettic-otc-policy-ref--empty { background: transparent; - color: var(--ot-gray-300); + color: var(--ettic-otc-gray-300); padding: 0; } /* Framework citations under the policy title in the listing table */ - .ot-policy-citations { + .ettic-otc-policy-citations { display: flex; flex-wrap: wrap; gap: 4px; @@ -853,13 +853,13 @@ padding: 0; } - .ot-policy-citation { + .ettic-otc-policy-citation { display: inline-flex; align-items: center; padding: 1px 6px; - background: var(--ot-accent-subtle); - color: var(--ot-accent-text); - border: 1px solid var(--ot-accent-border); + background: var(--ettic-otc-accent-subtle); + color: var(--ettic-otc-accent-text); + border: 1px solid var(--ettic-otc-accent-border); border-radius: 3px; font-size: 10px; font-weight: 500; @@ -867,35 +867,35 @@ white-space: nowrap; } - .ot-policy-actions { + .ettic-otc-policy-actions { display: flex; align-items: center; - gap: var(--ot-space-3); + gap: var(--ettic-otc-space-3); justify-content: flex-end; white-space: nowrap; } - .ot-body a.ot-policy-actions__pdf { + .ettic-otc-body a.ettic-otc-policy-actions__pdf { display: flex; align-items: center; - color: var(--ot-gray-400); - transition: color var(--ot-transition-fast); + color: var(--ettic-otc-gray-400); + transition: color var(--ettic-otc-transition-fast); } - .ot-body a.ot-policy-actions__pdf:hover { - color: var(--ot-accent); + .ettic-otc-body a.ettic-otc-policy-actions__pdf:hover { + color: var(--ettic-otc-accent); } - .ot-body a.ot-policy-actions__view { + .ettic-otc-body a.ettic-otc-policy-actions__view { display: flex; align-items: center; - color: var(--ot-gray-300); - font-size: var(--ot-text-md); - transition: color var(--ot-transition-fast); + color: var(--ettic-otc-gray-300); + font-size: var(--ettic-otc-text-md); + transition: color var(--ettic-otc-transition-fast); } - .ot-body a.ot-policy-actions__view:hover { - color: var(--ot-accent); + .ettic-otc-body a.ettic-otc-policy-actions__view:hover { + color: var(--ettic-otc-accent); } .screen-reader-text { @@ -914,104 +914,104 @@ POLICY SINGLE VIEW ═══════════════════════════════════════════════ */ - .ot-policy-single { - padding-block: var(--ot-space-12); + .ettic-otc-policy-single { + padding-block: var(--ettic-otc-space-12); } - .ot-policy-single__back { + .ettic-otc-policy-single__back { display: inline-flex; align-items: center; - gap: var(--ot-space-2); - font-size: var(--ot-text-sm); + gap: var(--ettic-otc-space-2); + font-size: var(--ettic-otc-text-sm); font-weight: 500; - color: var(--ot-gray-500); - margin-bottom: var(--ot-space-8); - transition: color var(--ot-transition-fast); + color: var(--ettic-otc-gray-500); + margin-bottom: var(--ettic-otc-space-8); + transition: color var(--ettic-otc-transition-fast); } - .ot-policy-single__back:hover { - color: var(--ot-accent); + .ettic-otc-policy-single__back:hover { + color: var(--ettic-otc-accent); } - .ot-policy-single__header { - margin-bottom: var(--ot-space-10); - padding-bottom: var(--ot-space-8); - border-bottom: 1px solid var(--ot-gray-200); + .ettic-otc-policy-single__header { + margin-bottom: var(--ettic-otc-space-10); + padding-bottom: var(--ettic-otc-space-8); + border-bottom: 1px solid var(--ettic-otc-gray-200); } - .ot-policy-single__title { - font-size: var(--ot-text-4xl); + .ettic-otc-policy-single__title { + font-size: var(--ettic-otc-text-4xl); font-weight: 600; - color: var(--ot-gray-900); - margin: 0 0 var(--ot-space-5) 0; + color: var(--ettic-otc-gray-900); + margin: 0 0 var(--ettic-otc-space-5) 0; letter-spacing: -0.03em; - line-height: var(--ot-leading-tight); + line-height: var(--ettic-otc-leading-tight); text-wrap: balance; } - .ot-policy-single__meta { + .ettic-otc-policy-single__meta { display: flex; flex-wrap: wrap; - gap: var(--ot-space-2) var(--ot-space-5); + gap: var(--ettic-otc-space-2) var(--ettic-otc-space-5); align-items: center; - font-size: var(--ot-text-sm); - color: var(--ot-gray-500); + font-size: var(--ettic-otc-text-sm); + color: var(--ettic-otc-gray-500); } - .ot-policy-single__meta-item { + .ettic-otc-policy-single__meta-item { display: flex; align-items: center; - gap: var(--ot-space-2); + gap: var(--ettic-otc-space-2); } /* Separator dots between meta items */ - .ot-policy-single__meta-item + .ot-policy-single__meta-item::before { + .ettic-otc-policy-single__meta-item + .ettic-otc-policy-single__meta-item::before { content: ''; width: 3px; height: 3px; border-radius: 50%; - background: var(--ot-gray-300); - margin-inline-end: var(--ot-space-3); + background: var(--ettic-otc-gray-300); + margin-inline-end: var(--ettic-otc-space-3); } - .ot-policy-single__actions { + .ettic-otc-policy-single__actions { display: flex; - gap: var(--ot-space-3); - margin-top: var(--ot-space-5); + gap: var(--ettic-otc-space-3); + margin-top: var(--ettic-otc-space-5); } - .ot-version-banner { - background: var(--ot-warning-bg); - color: var(--ot-warning-fg); - padding: var(--ot-space-3) var(--ot-space-5); - border-radius: var(--ot-radius-md); - font-size: var(--ot-text-sm); - margin-bottom: var(--ot-space-8); + .ettic-otc-version-banner { + background: var(--ettic-otc-warning-bg); + color: var(--ettic-otc-warning-fg); + padding: var(--ettic-otc-space-3) var(--ettic-otc-space-5); + border-radius: var(--ettic-otc-radius-md); + font-size: var(--ettic-otc-text-sm); + margin-bottom: var(--ettic-otc-space-8); display: flex; align-items: center; - gap: var(--ot-space-3); - border: 1px solid var(--ot-warning-ring); + gap: var(--ettic-otc-space-3); + border: 1px solid var(--ettic-otc-warning-ring); } - .ot-version-banner a { - color: var(--ot-warning-fg); + .ettic-otc-version-banner a { + color: var(--ettic-otc-warning-fg); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; } - .ot-version-banner--pending { + .ettic-otc-version-banner--pending { background: hsl(210, 50%, 95%); color: hsl(210, 60%, 35%); border-color: hsl(210, 50%, 85%); } - .ot-version-banner--pending a { + .ettic-otc-version-banner--pending a { color: hsl(210, 60%, 35%); } /* Upcoming pill (policy list) */ - .ot-pill--pending { + .ettic-otc-pill--pending { background: hsl(210, 50%, 95%); color: hsl(210, 60%, 40%); font-size: 10px; @@ -1019,230 +1019,230 @@ text-transform: uppercase; letter-spacing: 0.04em; padding: 2px 8px; - border-radius: var(--ot-radius-full); + border-radius: var(--ettic-otc-radius-full); margin-left: 6px; } /* Version history (public) */ - .ot-version-history-public { - margin-bottom: var(--ot-space-6); + .ettic-otc-version-history-public { + margin-bottom: var(--ettic-otc-space-6); } - .ot-version-history-toggle { + .ettic-otc-version-history-toggle { display: inline-flex; align-items: center; gap: 6px; - font-family: var(--ot-font); - font-size: var(--ot-text-sm); + font-family: var(--ettic-otc-font); + font-size: var(--ettic-otc-text-sm); font-weight: 500; - color: var(--ot-gray-500); + color: var(--ettic-otc-gray-500); background: none; - border: 1px solid var(--ot-gray-200); - border-radius: var(--ot-radius-full); + border: 1px solid var(--ettic-otc-gray-200); + border-radius: var(--ettic-otc-radius-full); padding: 6px 14px; cursor: pointer; transition: all 0.15s ease; } - .ot-version-history-toggle:hover { - color: var(--ot-gray-700); - border-color: var(--ot-gray-300); - background: var(--ot-gray-50); + .ettic-otc-version-history-toggle:hover { + color: var(--ettic-otc-gray-700); + border-color: var(--ettic-otc-gray-300); + background: var(--ettic-otc-gray-50); } - .ot-version-history-chevron { + .ettic-otc-version-history-chevron { transition: transform 0.2s ease; } - .ot-version-history-toggle[aria-expanded="true"] .ot-version-history-chevron { + .ettic-otc-version-history-toggle[aria-expanded="true"] .ettic-otc-version-history-chevron { transform: rotate(180deg); } - .ot-version-list { + .ettic-otc-version-list { list-style: none; - margin: var(--ot-space-3) 0 0; + margin: var(--ettic-otc-space-3) 0 0; padding: 0; - border: 1px solid var(--ot-gray-200); - border-radius: var(--ot-radius-md); + border: 1px solid var(--ettic-otc-gray-200); + border-radius: var(--ettic-otc-radius-md); overflow: hidden; } - .ot-version-list[hidden] { + .ettic-otc-version-list[hidden] { display: none; } - .ot-version-list__item { - border-bottom: 1px solid var(--ot-gray-100); + .ettic-otc-version-list__item { + border-bottom: 1px solid var(--ettic-otc-gray-100); } - .ot-version-list__item:last-child { + .ettic-otc-version-list__item:last-child { border-bottom: none; } - .ot-version-list__link { + .ettic-otc-version-list__link { display: flex; align-items: center; gap: 6px; padding: 10px 16px; text-decoration: none; - color: var(--ot-gray-700); - font-size: var(--ot-text-sm); + color: var(--ettic-otc-gray-700); + font-size: var(--ettic-otc-text-sm); transition: background 0.12s ease; white-space: nowrap; overflow: hidden; } - a.ot-version-list__link:hover { - background: var(--ot-gray-50); + a.ettic-otc-version-list__link:hover { + background: var(--ettic-otc-gray-50); } - .ot-version-list__link--current { - background: var(--ot-gray-50); + .ettic-otc-version-list__link--current { + background: var(--ettic-otc-gray-50); } - .ot-version-list__item--active .ot-version-list__link { - border-left: 3px solid hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l-safe)); + .ettic-otc-version-list__item--active .ettic-otc-version-list__link { + border-left: 3px solid hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l-safe)); padding-left: 13px; } - .ot-version-list__number { + .ettic-otc-version-list__number { font-weight: 600; - color: var(--ot-gray-900); + color: var(--ettic-otc-gray-900); flex-shrink: 0; } - .ot-version-list__sep { - color: var(--ot-gray-300); + .ettic-otc-version-list__sep { + color: var(--ettic-otc-gray-300); flex-shrink: 0; } - .ot-version-list__date { - color: var(--ot-gray-500); + .ettic-otc-version-list__date { + color: var(--ettic-otc-gray-500); flex-shrink: 0; } - .ot-version-list__summary { - color: var(--ot-gray-400); + .ettic-otc-version-list__summary { + color: var(--ettic-otc-gray-400); overflow: hidden; text-overflow: ellipsis; } - .ot-version-list__badge { + .ettic-otc-version-list__badge { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; padding: 2px 8px; - border-radius: var(--ot-radius-full); + border-radius: var(--ettic-otc-radius-full); background: hsl(142, 50%, 94%); color: hsl(142, 50%, 30%); margin-left: auto; } /* Policy content (rendered blocks) */ - .ot-policy-content { - font-size: var(--ot-text-md); + .ettic-otc-policy-content { + font-size: var(--ettic-otc-text-md); line-height: 1.8; - color: var(--ot-gray-700); + color: var(--ettic-otc-gray-700); } - .ot-policy-content h1, - .ot-policy-content h2, - .ot-policy-content h3, - .ot-policy-content h4 { - color: var(--ot-gray-900); - margin-top: var(--ot-space-10); - margin-bottom: var(--ot-space-4); - line-height: var(--ot-leading-tight); + .ettic-otc-policy-content h1, + .ettic-otc-policy-content h2, + .ettic-otc-policy-content h3, + .ettic-otc-policy-content h4 { + color: var(--ettic-otc-gray-900); + margin-top: var(--ettic-otc-space-10); + margin-bottom: var(--ettic-otc-space-4); + line-height: var(--ettic-otc-leading-tight); font-weight: 650; letter-spacing: -0.01em; } - .ot-policy-content h2 { font-size: var(--ot-text-2xl); } - .ot-policy-content h3 { font-size: var(--ot-text-xl); } - .ot-policy-content h4 { font-size: var(--ot-text-lg); } + .ettic-otc-policy-content h2 { font-size: var(--ettic-otc-text-2xl); } + .ettic-otc-policy-content h3 { font-size: var(--ettic-otc-text-xl); } + .ettic-otc-policy-content h4 { font-size: var(--ettic-otc-text-lg); } - .ot-policy-content p { + .ettic-otc-policy-content p { margin-top: 0; - margin-bottom: var(--ot-space-5); + margin-bottom: var(--ettic-otc-space-5); } - .ot-policy-content ul, - .ot-policy-content ol { + .ettic-otc-policy-content ul, + .ettic-otc-policy-content ol { margin-top: 0; - margin-bottom: var(--ot-space-5); - padding-inline-start: var(--ot-space-6); + margin-bottom: var(--ettic-otc-space-5); + padding-inline-start: var(--ettic-otc-space-6); } - .ot-policy-content li { - margin-bottom: var(--ot-space-2); + .ettic-otc-policy-content li { + margin-bottom: var(--ettic-otc-space-2); } - .ot-policy-content li::marker { - color: var(--ot-gray-400); + .ettic-otc-policy-content li::marker { + color: var(--ettic-otc-gray-400); } - .ot-policy-content table { + .ettic-otc-policy-content table { width: 100%; border-collapse: collapse; - margin-bottom: var(--ot-space-8); - border-radius: var(--ot-radius-md); + margin-bottom: var(--ettic-otc-space-8); + border-radius: var(--ettic-otc-radius-md); overflow: hidden; - border: 1px solid var(--ot-gray-200); + border: 1px solid var(--ettic-otc-gray-200); } - .ot-policy-content th, - .ot-policy-content td { - padding: var(--ot-space-3) var(--ot-space-5); + .ettic-otc-policy-content th, + .ettic-otc-policy-content td { + padding: var(--ettic-otc-space-3) var(--ettic-otc-space-5); text-align: left; } - .ot-policy-content th { - background: var(--ot-gray-50); + .ettic-otc-policy-content th { + background: var(--ettic-otc-gray-50); font-weight: 600; - font-size: var(--ot-text-sm); - color: var(--ot-gray-700); - border-bottom: 1px solid var(--ot-gray-200); + font-size: var(--ettic-otc-text-sm); + color: var(--ettic-otc-gray-700); + border-bottom: 1px solid var(--ettic-otc-gray-200); } - .ot-policy-content td { - border-bottom: 1px solid var(--ot-gray-100); + .ettic-otc-policy-content td { + border-bottom: 1px solid var(--ettic-otc-gray-100); } - .ot-policy-content tr:last-child td { + .ettic-otc-policy-content tr:last-child td { border-bottom: none; } - .ot-policy-content blockquote { - border-inline-start: 3px solid var(--ot-accent); - margin: 0 0 var(--ot-space-5) 0; - padding: var(--ot-space-4) var(--ot-space-6); - color: var(--ot-gray-600); - background: var(--ot-gray-50); - border-radius: 0 var(--ot-radius-sm) var(--ot-radius-sm) 0; + .ettic-otc-policy-content blockquote { + border-inline-start: 3px solid var(--ettic-otc-accent); + margin: 0 0 var(--ettic-otc-space-5) 0; + padding: var(--ettic-otc-space-4) var(--ettic-otc-space-6); + color: var(--ettic-otc-gray-600); + background: var(--ettic-otc-gray-50); + border-radius: 0 var(--ettic-otc-radius-sm) var(--ettic-otc-radius-sm) 0; font-style: italic; } - .ot-policy-content code { - font-family: var(--ot-font-mono); + .ettic-otc-policy-content code { + font-family: var(--ettic-otc-font-mono); font-size: 0.875em; - background: var(--ot-gray-100); + background: var(--ettic-otc-gray-100); padding: 0.125em 0.4em; border-radius: 5px; - color: var(--ot-gray-800); + color: var(--ettic-otc-gray-800); } - .ot-policy-content pre { - background: var(--ot-gray-900); - color: var(--ot-gray-100); - padding: var(--ot-space-5); - border-radius: var(--ot-radius-md); + .ettic-otc-policy-content pre { + background: var(--ettic-otc-gray-900); + color: var(--ettic-otc-gray-100); + padding: var(--ettic-otc-space-5); + border-radius: var(--ettic-otc-radius-md); overflow-x: auto; - margin-bottom: var(--ot-space-5); - font-size: var(--ot-text-sm); + margin-bottom: var(--ettic-otc-space-5); + font-size: var(--ettic-otc-text-sm); } - .ot-policy-content pre code { + .ettic-otc-policy-content pre code { background: none; padding: 0; color: inherit; @@ -1252,130 +1252,130 @@ SUBPROCESSOR TABLE ═══════════════════════════════════════════════ */ - .ot-table-wrapper { + .ettic-otc-table-wrapper { overflow-x: auto; -webkit-overflow-scrolling: touch; - border: 1px solid var(--ot-gray-200); - border-radius: var(--ot-radius-lg); - background: var(--ot-white); - box-shadow: var(--ot-shadow-xs); + border: 1px solid var(--ettic-otc-gray-200); + border-radius: var(--ettic-otc-radius-lg); + background: var(--ettic-otc-white); + box-shadow: var(--ettic-otc-shadow-xs); } - .ot-table { + .ettic-otc-table { width: 100%; border-collapse: collapse; - font-size: var(--ot-text-sm); + font-size: var(--ettic-otc-text-sm); table-layout: fixed; } - .ot-table thead { - background: var(--ot-gray-50); + .ettic-otc-table thead { + background: var(--ettic-otc-gray-50); } - .ot-table th { - padding: var(--ot-space-3) var(--ot-space-5); + .ettic-otc-table th { + padding: var(--ettic-otc-space-3) var(--ettic-otc-space-5); text-align: left; font-weight: 600; - color: var(--ot-gray-600); - font-size: var(--ot-text-xs); + color: var(--ettic-otc-gray-600); + font-size: var(--ettic-otc-text-xs); text-transform: uppercase; letter-spacing: 0.05em; - border-bottom: 1px solid var(--ot-gray-200); + border-bottom: 1px solid var(--ettic-otc-gray-200); white-space: nowrap; } - .ot-table th[data-ot-sort] { + .ettic-otc-table th[data-ettic-otc-sort] { cursor: pointer; user-select: none; - transition: color var(--ot-transition-fast); + transition: color var(--ettic-otc-transition-fast); } - .ot-table th[data-ot-sort]:hover { - color: var(--ot-accent); + .ettic-otc-table th[data-ettic-otc-sort]:hover { + color: var(--ettic-otc-accent); } - .ot-table th[data-ot-sort]::after { + .ettic-otc-table th[data-ettic-otc-sort]::after { content: ' \2195'; font-size: 0.75em; opacity: 0.35; } - .ot-table th[data-ot-sort-dir="asc"]::after { + .ettic-otc-table th[data-ettic-otc-sort-dir="asc"]::after { content: ' \2191'; opacity: 1; - color: var(--ot-accent); + color: var(--ettic-otc-accent); } - .ot-table th[data-ot-sort-dir="desc"]::after { + .ettic-otc-table th[data-ettic-otc-sort-dir="desc"]::after { content: ' \2193'; opacity: 1; - color: var(--ot-accent); + color: var(--ettic-otc-accent); } - .ot-table td { - padding: var(--ot-space-4) var(--ot-space-5); - border-bottom: 1px solid var(--ot-gray-100); - color: var(--ot-gray-700); + .ettic-otc-table td { + padding: var(--ettic-otc-space-4) var(--ettic-otc-space-5); + border-bottom: 1px solid var(--ettic-otc-gray-100); + color: var(--ettic-otc-gray-700); } - .ot-table td strong { - color: var(--ot-gray-900); + .ettic-otc-table td strong { + color: var(--ettic-otc-gray-900); font-weight: 600; } - .ot-table tbody tr:last-child td { + .ettic-otc-table tbody tr:last-child td { border-bottom: none; } - .ot-table tbody tr { - transition: background var(--ot-transition-fast); + .ettic-otc-table tbody tr { + transition: background var(--ettic-otc-transition-fast); } - .ot-table tbody tr:hover { - background: var(--ot-gray-25); + .ettic-otc-table tbody tr:hover { + background: var(--ettic-otc-gray-25); } - .ot-table .ot-dpa-badge { + .ettic-otc-table .ettic-otc-dpa-badge { display: inline-flex; align-items: center; - gap: var(--ot-space-1); - font-size: var(--ot-text-xs); + gap: var(--ettic-otc-space-1); + font-size: var(--ettic-otc-text-xs); font-weight: 500; } - .ot-table .ot-dpa-badge--signed { - color: var(--ot-success-fg); + .ettic-otc-table .ettic-otc-dpa-badge--signed { + color: var(--ettic-otc-success-fg); } - .ot-table .ot-dpa-badge--signed svg { - fill: var(--ot-success); + .ettic-otc-table .ettic-otc-dpa-badge--signed svg { + fill: var(--ettic-otc-success); } - .ot-table .ot-dpa-badge--pending { - color: var(--ot-gray-400); + .ettic-otc-table .ettic-otc-dpa-badge--pending { + color: var(--ettic-otc-gray-400); } /* Clampable text cells — 3-line max with "more" toggle */ - .ot-table__clamp { + .ettic-otc-table__clamp { position: relative; } - .ot-table__clamp-text { + .ettic-otc-table__clamp-text { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; } - .ot-table__clamp-text--expanded { + .ettic-otc-table__clamp-text--expanded { -webkit-line-clamp: unset; } - .ot-table__more { - font-family: var(--ot-font); - font-size: var(--ot-text-xs); + .ettic-otc-table__more { + font-family: var(--ettic-otc-font); + font-size: var(--ettic-otc-text-xs); font-weight: 500; - color: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l-safe)); + color: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l-safe)); background: none; border: none; cursor: pointer; @@ -1384,7 +1384,7 @@ display: none; } - .ot-table__more:hover { + .ettic-otc-table__more:hover { text-decoration: underline; } @@ -1392,22 +1392,22 @@ UPDATED PILL — "Updated X ago" badge ═══════════════════════════════════════════════ */ - .ot-updated-pill { + .ettic-otc-updated-pill { display: inline-flex; align-items: center; gap: 5px; - font-size: var(--ot-text-xs); + font-size: var(--ettic-otc-text-xs); font-weight: 500; padding: 4px 12px; - border-radius: var(--ot-radius-full); + border-radius: var(--ettic-otc-radius-full); background: hsl(142, 50%, 94%); color: hsl(142, 50%, 30%); white-space: nowrap; line-height: 1.4; - margin-bottom: var(--ot-space-3); + margin-bottom: var(--ettic-otc-space-3); } - .ot-updated-pill svg { + .ettic-otc-updated-pill svg { flex-shrink: 0; color: hsl(142, 60%, 40%); } @@ -1417,152 +1417,152 @@ ═══════════════════════════════════════════════ */ /* Card grid */ - .ot-dp-cards { + .ettic-otc-dp-cards { display: grid; grid-template-columns: repeat(2, 1fr); - gap: var(--ot-space-5); + gap: var(--ettic-otc-space-5); } /* Card */ - .ot-dp-card { - border: 1px solid var(--ot-gray-200); - border-radius: var(--ot-radius-lg); - background: var(--ot-white); - padding: var(--ot-space-6); + .ettic-otc-dp-card { + border: 1px solid var(--ettic-otc-gray-200); + border-radius: var(--ettic-otc-radius-lg); + background: var(--ettic-otc-white); + padding: var(--ettic-otc-space-6); transition: box-shadow 0.15s ease, border-color 0.15s ease; } - .ot-dp-card:hover { - box-shadow: var(--ot-shadow-sm); - border-color: var(--ot-gray-300); + .ettic-otc-dp-card:hover { + box-shadow: var(--ettic-otc-shadow-sm); + border-color: var(--ettic-otc-gray-300); } /* Card header — title + arrow */ - .ot-dp-card__head { + .ettic-otc-dp-card__head { display: flex; align-items: center; justify-content: space-between; - margin-bottom: var(--ot-space-4); + margin-bottom: var(--ettic-otc-space-4); } - .ot-dp-card__head[role="button"] { + .ettic-otc-dp-card__head[role="button"] { cursor: pointer; } - .ot-dp-card__head[role="button"]:focus-visible { - outline: 2px solid var(--ot-accent); + .ettic-otc-dp-card__head[role="button"]:focus-visible { + outline: 2px solid var(--ettic-otc-accent); outline-offset: 2px; - border-radius: var(--ot-radius-sm); + border-radius: var(--ettic-otc-radius-sm); } - .ot-dp-card__title { - font-size: var(--ot-text-lg); + .ettic-otc-dp-card__title { + font-size: var(--ettic-otc-text-lg); font-weight: 500; - color: var(--ot-gray-900); + color: var(--ettic-otc-gray-900); margin: 0; letter-spacing: -0.01em; } - .ot-dp-card__arrow { + .ettic-otc-dp-card__arrow { flex-shrink: 0; - color: var(--ot-gray-400); + color: var(--ettic-otc-gray-400); transition: transform 0.2s ease, color 0.15s ease; } - .ot-dp-card__head:hover .ot-dp-card__arrow { - color: var(--ot-gray-600); + .ettic-otc-dp-card__head:hover .ettic-otc-dp-card__arrow { + color: var(--ettic-otc-gray-600); } - .ot-dp-card__head[aria-expanded="true"] .ot-dp-card__arrow { + .ettic-otc-dp-card__head[aria-expanded="true"] .ettic-otc-dp-card__arrow { transform: rotate(90deg); - color: var(--ot-gray-600); + color: var(--ettic-otc-gray-600); } /* Checkmark list */ - .ot-dp-card__list { + .ettic-otc-dp-card__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; - gap: var(--ot-space-3); + gap: var(--ettic-otc-space-3); } - .ot-dp-card__list--overflow[hidden] { + .ettic-otc-dp-card__list--overflow[hidden] { display: none; } - .ot-dp-card__list--overflow { - margin-top: var(--ot-space-3); + .ettic-otc-dp-card__list--overflow { + margin-top: var(--ettic-otc-space-3); } - .ot-dp-card__item { + .ettic-otc-dp-card__item { display: flex; align-items: center; - gap: var(--ot-space-3); - font-size: var(--ot-text-sm); - color: var(--ot-gray-700); + gap: var(--ettic-otc-space-3); + font-size: var(--ettic-otc-text-sm); + color: var(--ettic-otc-gray-700); line-height: 1.4; } - .ot-dp-card__check { + .ettic-otc-dp-card__check { flex-shrink: 0; color: hsl(142, 71%, 40%); } /* "View N more" link */ - .ot-dp-card__more { - font-family: var(--ot-font); - font-size: var(--ot-text-sm); + .ettic-otc-dp-card__more { + font-family: var(--ettic-otc-font); + font-size: var(--ettic-otc-text-sm); font-weight: 500; - color: hsl(var(--ot-accent-h), var(--ot-accent-s), var(--ot-accent-l-safe)); + color: hsl(var(--ettic-otc-accent-h), var(--ettic-otc-accent-s), var(--ettic-otc-accent-l-safe)); background: none; border: none; cursor: pointer; padding: 0; - margin-top: var(--ot-space-4); + margin-top: var(--ettic-otc-space-4); display: block; } - .ot-dp-card__more:hover { + .ettic-otc-dp-card__more:hover { text-decoration: underline; } - .ot-dp-card__more--hidden { + .ettic-otc-dp-card__more--hidden { display: none; } /* Expandable detail panel */ - .ot-dp-card__details[hidden] { + .ettic-otc-dp-card__details[hidden] { display: none; } - .ot-dp-card__details-inner { - margin-top: var(--ot-space-4); - padding-top: var(--ot-space-4); - border-top: 1px solid var(--ot-gray-100); + .ettic-otc-dp-card__details-inner { + margin-top: var(--ettic-otc-space-4); + padding-top: var(--ettic-otc-space-4); + border-top: 1px solid var(--ettic-otc-gray-100); display: flex; flex-direction: column; - gap: var(--ot-space-3); + gap: var(--ettic-otc-space-3); } - .ot-dp-card__detail { + .ettic-otc-dp-card__detail { display: flex; flex-direction: column; gap: 2px; } - .ot-dp-card__detail dt { + .ettic-otc-dp-card__detail dt { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; - color: var(--ot-gray-400); + color: var(--ettic-otc-gray-400); } - .ot-dp-card__detail dd { - font-size: var(--ot-text-sm); - color: var(--ot-gray-700); + .ettic-otc-dp-card__detail dd { + font-size: var(--ettic-otc-text-sm); + color: var(--ettic-otc-gray-700); margin: 0; line-height: 1.5; } @@ -1571,52 +1571,52 @@ BUTTONS ═══════════════════════════════════════════════ */ - .ot-btn { + .ettic-otc-btn { display: inline-flex; align-items: center; - gap: var(--ot-space-2); - font-family: var(--ot-font); - font-size: var(--ot-text-sm); + gap: var(--ettic-otc-space-2); + font-family: var(--ettic-otc-font); + font-size: var(--ettic-otc-text-sm); font-weight: 500; - padding: var(--ot-space-2) var(--ot-space-4); - border-radius: var(--ot-radius-sm); + padding: var(--ettic-otc-space-2) var(--ettic-otc-space-4); + border-radius: var(--ettic-otc-radius-sm); border: 1px solid transparent; cursor: pointer; - transition: all var(--ot-transition-fast); + transition: all var(--ettic-otc-transition-fast); text-decoration: none; line-height: 1.5; } - .ot-body .ot-btn--primary, - .ot-btn--primary { - background: var(--ot-accent); - color: var(--ot-white); - border-color: var(--ot-accent); + .ettic-otc-body .ettic-otc-btn--primary, + .ettic-otc-btn--primary { + background: var(--ettic-otc-accent); + color: var(--ettic-otc-white); + border-color: var(--ettic-otc-accent); } - .ot-body .ot-btn--primary:hover, - .ot-btn--primary:hover { - background: var(--ot-accent-hover); - color: var(--ot-white); + .ettic-otc-body .ettic-otc-btn--primary:hover, + .ettic-otc-btn--primary:hover { + background: var(--ettic-otc-accent-hover); + color: var(--ettic-otc-white); } - .ot-btn--outline { - background: var(--ot-white); - color: var(--ot-gray-700); - border-color: var(--ot-gray-300); + .ettic-otc-btn--outline { + background: var(--ettic-otc-white); + color: var(--ettic-otc-gray-700); + border-color: var(--ettic-otc-gray-300); } - .ot-btn--outline:hover { - border-color: var(--ot-gray-400); - background: var(--ot-gray-50); + .ettic-otc-btn--outline:hover { + border-color: var(--ettic-otc-gray-400); + background: var(--ettic-otc-gray-50); } - .ot-btn:focus-visible { - outline: 2px solid var(--ot-accent); + .ettic-otc-btn:focus-visible { + outline: 2px solid var(--ettic-otc-accent); outline-offset: 2px; } - .ot-btn svg { + .ettic-otc-btn svg { width: 16px; height: 16px; fill: currentColor; @@ -1626,47 +1626,47 @@ EMPTY STATE ═══════════════════════════════════════════════ */ - .ot-empty { + .ettic-otc-empty { text-align: center; - padding: var(--ot-space-24) var(--ot-space-8); - color: var(--ot-gray-400); + padding: var(--ettic-otc-space-24) var(--ettic-otc-space-8); + color: var(--ettic-otc-gray-400); } - .ot-empty__icon { - margin-bottom: var(--ot-space-4); - color: var(--ot-gray-300); + .ettic-otc-empty__icon { + margin-bottom: var(--ettic-otc-space-4); + color: var(--ettic-otc-gray-300); } - .ot-empty__text { - font-size: var(--ot-text-md); + .ettic-otc-empty__text { + font-size: var(--ettic-otc-text-md); margin: 0; - color: var(--ot-gray-500); + color: var(--ettic-otc-gray-500); } /* ═══════════════════════════════════════════════ FOOTER ═══════════════════════════════════════════════ */ - .ot-footer { - border-top: 1px solid var(--ot-gray-200); - padding-block: var(--ot-space-10); + .ettic-otc-footer { + border-top: 1px solid var(--ettic-otc-gray-200); + padding-block: var(--ettic-otc-space-10); text-align: center; - font-size: var(--ot-text-xs); - color: var(--ot-gray-400); - background: var(--ot-white); + font-size: var(--ettic-otc-text-xs); + color: var(--ettic-otc-gray-400); + background: var(--ettic-otc-white); } - .ot-footer p { + .ettic-otc-footer p { margin: 0; } - .ot-footer a { - color: var(--ot-gray-400); - transition: color var(--ot-transition-fast); + .ettic-otc-footer a { + color: var(--ettic-otc-gray-400); + transition: color var(--ettic-otc-transition-fast); } - .ot-footer a:hover { - color: var(--ot-gray-600); + .ettic-otc-footer a:hover { + color: var(--ettic-otc-gray-600); } /* ═══════════════════════════════════════════════ @@ -1674,41 +1674,41 @@ ═══════════════════════════════════════════════ */ @media print { - .ot-nav, - .ot-footer, - .ot-policy-single__actions, - .ot-policy-single__back { + .ettic-otc-nav, + .ettic-otc-footer, + .ettic-otc-policy-single__actions, + .ettic-otc-policy-single__back { display: none !important; } - .ot-hero { + .ettic-otc-hero { background: #fff !important; - padding-block: var(--ot-space-8); + padding-block: var(--ettic-otc-space-8); } - .ot-hero__title { + .ettic-otc-hero__title { color: #111 !important; } - .ot-hero__tagline { + .ettic-otc-hero__tagline { color: #555 !important; } - .ot-hero__badge { + .ettic-otc-hero__badge { color: #333 !important; } - .ot-hero__stats { + .ettic-otc-hero__stats { display: none; } - .ot-card { + .ettic-otc-card { break-inside: avoid; box-shadow: none; border: 1px solid #ddd; } - .ot-section:nth-child(odd) { + .ettic-otc-section:nth-child(odd) { background: #fff; } } @@ -1718,9 +1718,9 @@ ═══════════════════════════════════════════════ */ @media (prefers-reduced-motion: reduce) { - .ot-body *, - .ot-body *::before, - .ot-body *::after { + .ettic-otc-body *, + .ettic-otc-body *::before, + .ettic-otc-body *::after { transition-duration: 0.01ms !important; } } @@ -1730,84 +1730,84 @@ ═══════════════════════════════════════════════ */ @media (max-width: 768px) { - .ot-hero { - padding-block: var(--ot-space-12) var(--ot-space-10); + .ettic-otc-hero { + padding-block: var(--ettic-otc-space-12) var(--ettic-otc-space-10); } - .ot-hero__title { - font-size: var(--ot-text-3xl); + .ettic-otc-hero__title { + font-size: var(--ettic-otc-text-3xl); } - .ot-hero__stats { - gap: var(--ot-space-4); + .ettic-otc-hero__stats { + gap: var(--ettic-otc-space-4); } - .ot-hero__stat-value { - font-size: var(--ot-text-xl); + .ettic-otc-hero__stat-value { + font-size: var(--ettic-otc-text-xl); } - .ot-nav__brand-name { - font-size: var(--ot-text-sm); + .ettic-otc-nav__brand-name { + font-size: var(--ettic-otc-text-sm); } - .ot-nav__links { + .ettic-otc-nav__links { overflow-x: auto; scrollbar-width: none; } - .ot-nav__links::-webkit-scrollbar { + .ettic-otc-nav__links::-webkit-scrollbar { display: none; } - .ot-card-grid { + .ettic-otc-card-grid { grid-template-columns: 1fr; } - .ot-cert-grid { + .ettic-otc-cert-grid { grid-template-columns: 1fr; } - .ot-container { - padding-inline: var(--ot-space-5); + .ettic-otc-container { + padding-inline: var(--ettic-otc-space-5); } - .ot-section { - padding-block: var(--ot-space-10); + .ettic-otc-section { + padding-block: var(--ettic-otc-space-10); } - .ot-section__header { - margin-bottom: var(--ot-space-8); + .ettic-otc-section__header { + margin-bottom: var(--ettic-otc-space-8); } - .ot-section__title { - font-size: var(--ot-text-2xl); + .ettic-otc-section__title { + font-size: var(--ettic-otc-text-2xl); } - .ot-policy-single__title { - font-size: var(--ot-text-2xl); + .ettic-otc-policy-single__title { + font-size: var(--ettic-otc-text-2xl); } /* Data practices cards — single column on mobile */ - .ot-dp-cards { + .ettic-otc-dp-cards { grid-template-columns: 1fr; } } @media (max-width: 480px) { - .ot-hero__stats { + .ettic-otc-hero__stats { flex-direction: column; align-items: center; - gap: var(--ot-space-2); + gap: var(--ettic-otc-space-2); } /* Hide dot separators when stats stack vertically */ - .ot-hero__stat + .ot-hero__stat::before { + .ettic-otc-hero__stat + .ettic-otc-hero__stat::before { display: none; } - .ot-table th, - .ot-table td { - padding: var(--ot-space-3) var(--ot-space-3); + .ettic-otc-table th, + .ettic-otc-table td { + padding: var(--ettic-otc-space-3) var(--ettic-otc-space-3); } } @@ -1815,47 +1815,47 @@ POLICY SINGLE — eyebrow, ref, citations ═══════════════════════════════════════════════ */ - .ot-policy-single__eyebrow { + .ettic-otc-policy-single__eyebrow { display: flex; flex-wrap: wrap; align-items: center; - gap: var(--ot-space-3); - margin-bottom: var(--ot-space-4); + gap: var(--ettic-otc-space-3); + margin-bottom: var(--ettic-otc-space-4); } - .ot-policy-single__ref { + .ettic-otc-policy-single__ref { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; font-weight: 500; - color: var(--ot-gray-500); - background: var(--ot-gray-100); + color: var(--ettic-otc-gray-500); + background: var(--ettic-otc-gray-100); padding: 3px 8px; border-radius: 4px; letter-spacing: 0.02em; } - .ot-policy-single__version { + .ettic-otc-policy-single__version { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; - font-size: var(--ot-text-sm); - color: var(--ot-gray-500); + font-size: var(--ettic-otc-text-sm); + color: var(--ettic-otc-gray-500); } - .ot-policy-single__citations { + .ettic-otc-policy-single__citations { display: flex; flex-wrap: wrap; gap: 8px; list-style: none; - margin: var(--ot-space-5) 0 0; + margin: var(--ettic-otc-space-5) 0 0; padding: 0; } - .ot-policy-single__citation { + .ettic-otc-policy-single__citation { display: inline-flex; align-items: center; padding: 4px 10px; - background: var(--ot-accent-subtle); - color: var(--ot-accent-text); - border: 1px solid var(--ot-accent-border); + background: var(--ettic-otc-accent-subtle); + color: var(--ettic-otc-accent-text); + border: 1px solid var(--ettic-otc-accent-border); border-radius: 4px; font-size: 12px; font-weight: 500; @@ -1866,9 +1866,9 @@ POLICY SINGLE HERO (compact) ═══════════════════════════════════════════════ */ - .ot-hero--compact { - padding-block: var(--ot-space-6); - background: hsl(var(--ot-accent-h), 30%, 7%); + .ettic-otc-hero--compact { + padding-block: var(--ettic-otc-space-6); + background: hsl(var(--ettic-otc-accent-h), 30%, 7%); } @@ -1880,143 +1880,143 @@ surface (no container), company description above them. ═══════════════════════════════════════════════ */ - /* Two-class body selector beats `.ot-section:nth-child(odd)` specificity. */ - .ot-body .ot-section--getintouch { + /* Two-class body selector beats `.ettic-otc-section:nth-child(odd)` specificity. */ + .ettic-otc-body .ettic-otc-section--getintouch { background: radial-gradient(ellipse 60% 50% at 50% 0%, - hsla(var(--ot-accent-h), 85%, 40%, 0.25) 0%, + hsla(var(--ettic-otc-accent-h), 85%, 40%, 0.25) 0%, transparent 70%), - hsl(var(--ot-accent-h), 75%, 9%); + hsl(var(--ettic-otc-accent-h), 75%, 9%); color: rgba(255, 255, 255, 0.82); - padding-block: var(--ot-space-20); + padding-block: var(--ettic-otc-space-20); } /* Two-column grid: header on the left, contact rows on the right. Both columns left-aligned inside their half. */ - .ot-get-inner { + .ettic-otc-get-inner { display: grid; grid-template-columns: 1fr 1fr; - gap: var(--ot-space-16); + gap: var(--ettic-otc-space-16); align-items: start; } /* Section header — left column, left-aligned */ - .ot-get-header { + .ettic-otc-get-header { text-align: left; margin: 0; max-width: 520px; } - .ot-get-header__title { - font-size: var(--ot-text-4xl); + .ettic-otc-get-header__title { + font-size: var(--ettic-otc-text-4xl); font-weight: 500; - color: var(--ot-white); - margin: 0 0 var(--ot-space-5) 0; + color: var(--ettic-otc-white); + margin: 0 0 var(--ettic-otc-space-5) 0; letter-spacing: -0.025em; line-height: 1.15; } /* Company description — promoted to the previously-"intro" prominence. This is now the primary supporting copy under the title. */ - .ot-get-header__description { - font-size: var(--ot-text-lg); + .ettic-otc-get-header__description { + font-size: var(--ettic-otc-text-lg); color: rgba(255, 255, 255, 0.68); margin: 0; - line-height: var(--ot-leading-normal); + line-height: var(--ettic-otc-leading-normal); max-width: 500px; } /* Row list — editorial definition list. Label sits in a fixed-width left column, value stacks on the right at the same size as the label. No dividers, no icons, no uppercase eyebrows. */ - .ot-get-list { + .ettic-otc-get-list { margin: 0; padding: 0; } - .ot-get-row { + .ettic-otc-get-row { display: grid; grid-template-columns: 180px 1fr; - gap: var(--ot-space-5); + gap: var(--ettic-otc-space-5); align-items: baseline; - padding: var(--ot-space-3) 0; + padding: var(--ettic-otc-space-3) 0; } - .ot-get-row__label { + .ettic-otc-get-row__label { margin: 0; - font-size: var(--ot-text-sm); + font-size: var(--ettic-otc-text-sm); font-weight: 400; letter-spacing: 0; text-transform: none; color: rgba(255, 255, 255, 0.5); - line-height: var(--ot-leading-normal); + line-height: var(--ettic-otc-leading-normal); } - .ot-get-row__lines { + .ettic-otc-get-row__lines { display: flex; flex-direction: column; - gap: var(--ot-space-1); + gap: var(--ettic-otc-space-1); margin: 0; - font-size: var(--ot-text-sm); - color: var(--ot-white); - line-height: var(--ot-leading-normal); + font-size: var(--ettic-otc-text-sm); + color: var(--ettic-otc-white); + line-height: var(--ettic-otc-leading-normal); word-break: break-word; } - .ot-get-row__strong { - color: var(--ot-white); + .ettic-otc-get-row__strong { + color: var(--ettic-otc-white); font-weight: 500; } - .ot-get-row__lines a { + .ettic-otc-get-row__lines a { /* align-self: start stops the anchor from stretching across the whole flex column (a flex-item column-direction gotcha). */ align-self: flex-start; /* All contact-block values share one color for visual consistency; link affordance lives in the hover-opacity transition below. */ - color: var(--ot-white); + color: var(--ettic-otc-white); font-weight: 500; text-decoration: none; - transition: opacity var(--ot-transition-fast); + transition: opacity var(--ettic-otc-transition-fast); } - .ot-get-row__lines a:visited { - color: var(--ot-white); + .ettic-otc-get-row__lines a:visited { + color: var(--ettic-otc-white); } - .ot-get-row__lines a:hover { + .ettic-otc-get-row__lines a:hover { opacity: 0.75; } @media (max-width: 860px) { - .ot-get-inner { + .ettic-otc-get-inner { grid-template-columns: 1fr; - gap: var(--ot-space-10); + gap: var(--ettic-otc-space-10); } - .ot-get-header, - .ot-get-header__description { + .ettic-otc-get-header, + .ettic-otc-get-header__description { max-width: none; } } @media (max-width: 640px) { - .ot-body .ot-section--getintouch { - padding-block: var(--ot-space-12); + .ettic-otc-body .ettic-otc-section--getintouch { + padding-block: var(--ettic-otc-space-12); } - .ot-get-header__title { - font-size: var(--ot-text-3xl); + .ettic-otc-get-header__title { + font-size: var(--ettic-otc-text-3xl); } - .ot-get-header__description { - font-size: var(--ot-text-md); + .ettic-otc-get-header__description { + font-size: var(--ettic-otc-text-md); } - .ot-get-row { + .ettic-otc-get-row { grid-template-columns: 1fr; - gap: var(--ot-space-1); - padding: var(--ot-space-4) 0; + gap: var(--ettic-otc-space-1); + padding: var(--ettic-otc-space-4) 0; } } @@ -2026,111 +2026,111 @@ floating cards. ═══════════════════════════════════════════════ */ - .ot-faq-list { + .ettic-otc-faq-list { max-width: 780px; margin: 0 auto; - background: var(--ot-white); - border: 1px solid var(--ot-gray-200); - border-radius: var(--ot-radius-lg); - box-shadow: var(--ot-shadow-sm); + background: var(--ettic-otc-white); + border: 1px solid var(--ettic-otc-gray-200); + border-radius: var(--ettic-otc-radius-lg); + box-shadow: var(--ettic-otc-shadow-sm); overflow: hidden; } - .ot-faq-item { - border-bottom: 1px solid var(--ot-gray-200); + .ettic-otc-faq-item { + border-bottom: 1px solid var(--ettic-otc-gray-200); } - .ot-faq-item:last-child { + .ettic-otc-faq-item:last-child { border-bottom: none; } - .ot-faq-item__question { + .ettic-otc-faq-item__question { display: flex; align-items: center; justify-content: space-between; - gap: var(--ot-space-4); - padding: var(--ot-space-4) var(--ot-space-6); + gap: var(--ettic-otc-space-4); + padding: var(--ettic-otc-space-4) var(--ettic-otc-space-6); cursor: pointer; list-style: none; - font-size: var(--ot-text-md); + font-size: var(--ettic-otc-text-md); font-weight: 600; - color: var(--ot-gray-900); - line-height: var(--ot-leading-snug); - transition: background-color var(--ot-transition-fast); + color: var(--ettic-otc-gray-900); + line-height: var(--ettic-otc-leading-snug); + transition: background-color var(--ettic-otc-transition-fast); } - .ot-faq-item__question::-webkit-details-marker { display: none; } - .ot-faq-item__question::marker { display: none; content: ''; } + .ettic-otc-faq-item__question::-webkit-details-marker { display: none; } + .ettic-otc-faq-item__question::marker { display: none; content: ''; } - .ot-faq-item__question:hover { - background: var(--ot-gray-25); + .ettic-otc-faq-item__question:hover { + background: var(--ettic-otc-gray-25); } - .ot-faq-item[open] > .ot-faq-item__question { - background: var(--ot-gray-25); + .ettic-otc-faq-item[open] > .ettic-otc-faq-item__question { + background: var(--ettic-otc-gray-25); } - .ot-faq-item__question-text { + .ettic-otc-faq-item__question-text { flex: 1 1 auto; } - .ot-faq-item__chevron { + .ettic-otc-faq-item__chevron { flex: 0 0 auto; - color: var(--ot-gray-400); - transition: transform var(--ot-transition), color var(--ot-transition); + color: var(--ettic-otc-gray-400); + transition: transform var(--ettic-otc-transition), color var(--ettic-otc-transition); } - .ot-faq-item[open] .ot-faq-item__chevron { + .ettic-otc-faq-item[open] .ettic-otc-faq-item__chevron { transform: rotate(180deg); - color: var(--ot-gray-600); + color: var(--ettic-otc-gray-600); } - .ot-faq-item__question:focus-visible { - outline: 2px solid var(--ot-accent); + .ettic-otc-faq-item__question:focus-visible { + outline: 2px solid var(--ettic-otc-accent); outline-offset: -2px; } - .ot-faq-item__answer { - padding: 0 var(--ot-space-6) var(--ot-space-5) var(--ot-space-6); - color: var(--ot-gray-600); - font-size: var(--ot-text-sm); - line-height: var(--ot-leading-normal); + .ettic-otc-faq-item__answer { + padding: 0 var(--ettic-otc-space-6) var(--ettic-otc-space-5) var(--ettic-otc-space-6); + color: var(--ettic-otc-gray-600); + font-size: var(--ettic-otc-text-sm); + line-height: var(--ettic-otc-leading-normal); } - .ot-faq-item__answer > *:first-child { margin-top: 0; } - .ot-faq-item__answer > *:last-child { margin-bottom: 0; } + .ettic-otc-faq-item__answer > *:first-child { margin-top: 0; } + .ettic-otc-faq-item__answer > *:last-child { margin-bottom: 0; } - .ot-faq-item__answer p { margin: 0 0 var(--ot-space-3) 0; } + .ettic-otc-faq-item__answer p { margin: 0 0 var(--ettic-otc-space-3) 0; } - .ot-faq-item__answer a { - color: var(--ot-accent-text); + .ettic-otc-faq-item__answer a { + color: var(--ettic-otc-accent-text); font-weight: 500; text-decoration: underline; text-underline-offset: 2px; } - .ot-faq-item__related { - margin-top: var(--ot-space-3); - padding-top: var(--ot-space-3); - border-top: 1px dashed var(--ot-gray-200); - font-size: var(--ot-text-xs); - color: var(--ot-gray-500); + .ettic-otc-faq-item__related { + margin-top: var(--ettic-otc-space-3); + padding-top: var(--ettic-otc-space-3); + border-top: 1px dashed var(--ettic-otc-gray-200); + font-size: var(--ettic-otc-text-xs); + color: var(--ettic-otc-gray-500); } - .ot-faq-item__related a { - color: var(--ot-accent-text); + .ettic-otc-faq-item__related a { + color: var(--ettic-otc-accent-text); font-weight: 500; margin-left: 4px; } @media (max-width: 640px) { - .ot-faq-item__question { - padding: var(--ot-space-4) var(--ot-space-5); - font-size: var(--ot-text-base); + .ettic-otc-faq-item__question { + padding: var(--ettic-otc-space-4) var(--ettic-otc-space-5); + font-size: var(--ettic-otc-text-base); } - .ot-faq-item__answer { - padding: 0 var(--ot-space-5) var(--ot-space-4) var(--ot-space-5); + .ettic-otc-faq-item__answer { + padding: 0 var(--ettic-otc-space-5) var(--ettic-otc-space-4) var(--ettic-otc-space-5); } } -} /* end @layer opentrust */ +} /* end @layer ettic-otc */ diff --git a/assets/js/admin.js b/assets/js/admin.js index 10ccce7..fb77071 100644 --- a/assets/js/admin.js +++ b/assets/js/admin.js @@ -137,10 +137,10 @@ var chosenHex = (String(hex).charAt(0) === '#' ? String(hex) : '#' + hex).toUpperCase(); var adjustedHex = result.adjustedHex.toUpperCase(); - $warning.find('.ot-accent-warning__swatch--chosen').css('background', chosenHex); - $warning.find('.ot-accent-warning__swatch--adjusted').css('background', adjustedHex); - $warning.find('.ot-accent-warning__hex--chosen').text(chosenHex); - $warning.find('.ot-accent-warning__hex--adjusted').text(adjustedHex); + $warning.find('.ettic-otc-accent-warning__swatch--chosen').css('background', chosenHex); + $warning.find('.ettic-otc-accent-warning__swatch--adjusted').css('background', adjustedHex); + $warning.find('.ettic-otc-accent-warning__hex--chosen').text(chosenHex); + $warning.find('.ettic-otc-accent-warning__hex--adjusted').text(adjustedHex); $warning.removeAttr('hidden'); } @@ -150,7 +150,7 @@ var $forceExact = $('#opentrust_accent_force_exact'); var $accentWarning = $('#opentrust-accent-warning'); - $('.ot-color-picker').wpColorPicker({ + $('.ettic-otc-color-picker').wpColorPicker({ change: function (event, ui) { // wpColorPicker fires `change` before the input is updated, // so defer a tick before reading the value. @@ -169,7 +169,7 @@ // a page reload. The actual clamping still happens server-side — the // class only drives the admin copy/colour swap. $forceExact.on('change', function () { - $accentWarning.toggleClass('ot-accent-warning--override', this.checked); + $accentWarning.toggleClass('ettic-otc-accent-warning--override', this.checked); }); // Initial check on page load. @@ -178,12 +178,12 @@ } // ── Media uploader (logo + avatar) ───────── - $('[data-ot-media-field]').each(function () { + $('[data-ettic-otc-media-field]').each(function () { var $field = $(this); - var $input = $field.find('[data-ot-media-input]'); - var $preview = $field.find('.ot-logo-preview'); - var $uploadBtn = $field.find('[data-ot-media-upload]'); - var $removeBtn = $field.find('[data-ot-media-remove]'); + var $input = $field.find('[data-ettic-otc-media-input]'); + var $preview = $field.find('.ettic-otc-logo-preview'); + var $uploadBtn = $field.find('[data-ettic-otc-media-upload]'); + var $removeBtn = $field.find('[data-ettic-otc-media-remove]'); var frame; $uploadBtn.on('click', function (e) { @@ -221,14 +221,14 @@ }); // ── Certification badge uploader ─────────── - $('.ot-upload-badge').on('click', function (e) { + $('.ettic-otc-upload-badge').on('click', function (e) { e.preventDefault(); var $btn = $(this); - var $input = $btn.siblings('.ot-badge-input'); - var $img = $btn.siblings('.ot-badge-preview'); - var $rm = $btn.siblings('.ot-remove-badge'); + var $input = $btn.siblings('.ettic-otc-badge-input'); + var $img = $btn.siblings('.ettic-otc-badge-preview'); + var $rm = $btn.siblings('.ettic-otc-remove-badge'); - var adminI18n = (window.OpenTrustAdmin && window.OpenTrustAdmin.i18n) || {}; + var adminI18n = (window.EtticOTCAdmin && window.EtticOTCAdmin.i18n) || {}; var frame = wp.media({ title: adminI18n.selectBadgeImage || 'Select Badge Image', multiple: false, @@ -248,26 +248,26 @@ frame.open(); }); - $(document).on('click', '.ot-remove-badge', function (e) { + $(document).on('click', '.ettic-otc-remove-badge', function (e) { e.preventDefault(); - $(this).siblings('.ot-badge-input').val('0'); - $(this).siblings('.ot-badge-preview').hide(); + $(this).siblings('.ettic-otc-badge-input').val('0'); + $(this).siblings('.ettic-otc-badge-preview').hide(); $(this).hide(); }); // ── Certification artifact uploader (PDF report / certificate) ── // Scoped to the parent meta field so the selectors don't collide // with the badge uploader above. Accepts any attachment type. - $(document).on('click', '.ot-upload-artifact', function (e) { + $(document).on('click', '.ettic-otc-upload-artifact', function (e) { e.preventDefault(); var $btn = $(this); - var $wrap = $btn.closest('[data-ot-cert-artifact]'); - var $input = $wrap.find('.ot-artifact-input'); - var $preview = $wrap.find('.ot-artifact-preview'); - var $link = $preview.find('.ot-artifact-preview__link'); - var $remove = $wrap.find('.ot-remove-artifact'); + var $wrap = $btn.closest('[data-ettic-otc-cert-artifact]'); + var $input = $wrap.find('.ettic-otc-artifact-input'); + var $preview = $wrap.find('.ettic-otc-artifact-preview'); + var $link = $preview.find('.ettic-otc-artifact-preview__link'); + var $remove = $wrap.find('.ettic-otc-remove-artifact'); - var adminI18n = (window.OpenTrustAdmin && window.OpenTrustAdmin.i18n) || {}; + var adminI18n = (window.EtticOTCAdmin && window.EtticOTCAdmin.i18n) || {}; var frame = wp.media({ title: adminI18n.selectArtifact || 'Select Proof Artifact', multiple: false, @@ -286,29 +286,29 @@ frame.open(); }); - $(document).on('click', '.ot-remove-artifact', function (e) { + $(document).on('click', '.ettic-otc-remove-artifact', function (e) { e.preventDefault(); - var $wrap = $(this).closest('[data-ot-cert-artifact]'); - $wrap.find('.ot-artifact-input').val('0'); - $wrap.find('.ot-artifact-preview').hide(); - var adminI18n = (window.OpenTrustAdmin && window.OpenTrustAdmin.i18n) || {}; - $wrap.find('.ot-upload-artifact').text(adminI18n.uploadArtifact || 'Upload File'); + var $wrap = $(this).closest('[data-ettic-otc-cert-artifact]'); + $wrap.find('.ettic-otc-artifact-input').val('0'); + $wrap.find('.ettic-otc-artifact-preview').hide(); + var adminI18n = (window.EtticOTCAdmin && window.EtticOTCAdmin.i18n) || {}; + $wrap.find('.ettic-otc-upload-artifact').text(adminI18n.uploadArtifact || 'Upload File'); $(this).hide(); }); // ── Policy PDF attachment uploader ── // Mirrors the certification artifact uploader, scoped via the - // [data-ot-policy-attachment] wrapper so the selectors don't collide. - $(document).on('click', '.ot-upload-policy-attachment', function (e) { + // [data-ettic-otc-policy-attachment] wrapper so the selectors don't collide. + $(document).on('click', '.ettic-otc-upload-policy-attachment', function (e) { e.preventDefault(); var $btn = $(this); - var $wrap = $btn.closest('[data-ot-policy-attachment]'); - var $input = $wrap.find('.ot-policy-attachment-input'); - var $preview = $wrap.find('.ot-artifact-preview'); - var $link = $preview.find('.ot-artifact-preview__link'); - var $remove = $wrap.find('.ot-remove-policy-attachment'); + var $wrap = $btn.closest('[data-ettic-otc-policy-attachment]'); + var $input = $wrap.find('.ettic-otc-policy-attachment-input'); + var $preview = $wrap.find('.ettic-otc-artifact-preview'); + var $link = $preview.find('.ettic-otc-artifact-preview__link'); + var $remove = $wrap.find('.ettic-otc-remove-policy-attachment'); - var adminI18n = (window.OpenTrustAdmin && window.OpenTrustAdmin.i18n) || {}; + var adminI18n = (window.EtticOTCAdmin && window.EtticOTCAdmin.i18n) || {}; var frame = wp.media({ title: adminI18n.selectPolicyPdf || 'Select Policy PDF', multiple: false, @@ -328,20 +328,20 @@ frame.open(); }); - $(document).on('click', '.ot-remove-policy-attachment', function (e) { + $(document).on('click', '.ettic-otc-remove-policy-attachment', function (e) { e.preventDefault(); - var $wrap = $(this).closest('[data-ot-policy-attachment]'); - $wrap.find('.ot-policy-attachment-input').val('0'); - $wrap.find('.ot-artifact-preview').hide(); - var adminI18n = (window.OpenTrustAdmin && window.OpenTrustAdmin.i18n) || {}; - $wrap.find('.ot-upload-policy-attachment').text(adminI18n.uploadPolicyPdf || 'Upload PDF'); + var $wrap = $(this).closest('[data-ettic-otc-policy-attachment]'); + $wrap.find('.ettic-otc-policy-attachment-input').val('0'); + $wrap.find('.ettic-otc-artifact-preview').hide(); + var adminI18n = (window.EtticOTCAdmin && window.EtticOTCAdmin.i18n) || {}; + $wrap.find('.ettic-otc-upload-policy-attachment').text(adminI18n.uploadPolicyPdf || 'Upload PDF'); $(this).hide(); }); // ── Tag input for Data Practice repeaters ── function otReindexTags($container) { - var fieldName = $container.data('ot-tags'); - $container.find('.ot-tag').each(function (i) { + var fieldName = $container.data('ettic-otc-tags'); + $container.find('.ettic-otc-tag').each(function (i) { $(this).find('input[type="hidden"]').attr('name', fieldName + '[' + i + '][name]'); }); } @@ -352,7 +352,7 @@ // Prevent duplicates. var exists = false; - $container.find('.ot-tag__text').each(function () { + $container.find('.ettic-otc-tag__text').each(function () { if ($(this).text().toLowerCase() === text.toLowerCase()) { exists = true; return false; @@ -360,31 +360,31 @@ }); if (exists) return; - var fieldName = $container.data('ot-tags'); - var idx = $container.find('.ot-tag').length; + var fieldName = $container.data('ettic-otc-tags'); + var idx = $container.find('.ettic-otc-tag').length; var $tag = $( - '' + - '' + + '' + + '' + '' + - '' + + '' + '' ); - $tag.find('.ot-tag__text').text(text); + $tag.find('.ettic-otc-tag__text').text(text); $tag.find('input').val(text); - $container.find('.ot-tags__input').before($tag); + $container.find('.ettic-otc-tags__input').before($tag); } // Click container to focus input. - $(document).on('click', '.ot-tags', function (e) { - if ($(e.target).hasClass('ot-tags')) { - $(this).find('.ot-tags__input').trigger('focus'); + $(document).on('click', '.ettic-otc-tags', function (e) { + if ($(e.target).hasClass('ettic-otc-tags')) { + $(this).find('.ettic-otc-tags__input').trigger('focus'); } }); // Add tag on Enter or comma. - $(document).on('keydown', '.ot-tags__input', function (e) { + $(document).on('keydown', '.ettic-otc-tags__input', function (e) { var $input = $(this); - var $container = $input.closest('.ot-tags'); + var $container = $input.closest('.ettic-otc-tags'); if (e.key === 'Enter' || e.key === ',') { e.preventDefault(); @@ -394,15 +394,15 @@ // Backspace on empty input removes last tag. if (e.key === 'Backspace' && $input.val() === '') { - $container.find('.ot-tag').last().remove(); + $container.find('.ettic-otc-tag').last().remove(); otReindexTags($container); } }); // Also add on blur (if text remains). - $(document).on('blur', '.ot-tags__input', function () { + $(document).on('blur', '.ettic-otc-tags__input', function () { var $input = $(this); - var $container = $input.closest('.ot-tags'); + var $container = $input.closest('.ettic-otc-tags'); if ($.trim($input.val())) { otAddTag($container, $input.val()); $input.val(''); @@ -410,10 +410,10 @@ }); // Remove tag. - $(document).on('click', '.ot-tag__remove', function (e) { + $(document).on('click', '.ettic-otc-tag__remove', function (e) { e.preventDefault(); - var $container = $(this).closest('.ot-tags'); - $(this).closest('.ot-tag').remove(); + var $container = $(this).closest('.ettic-otc-tags'); + $(this).closest('.ettic-otc-tag').remove(); otReindexTags($container); }); }); @@ -427,15 +427,15 @@ * - `fields` → facts, green highlight, "Auto-filled" chip * - `fields_review` → templates, amber highlight, "Verify" chip + notice * - * All state is client-side. The bundle at window.OpenTrustCatalog is injected - * only on post-new.php for opentr_subprocessor / opentr_data_practice screens. + * All state is client-side. The bundle at window.EtticOTCCatalog is injected + * only on post-new.php for eotc_subprocessor / eotc_data_practice screens. */ (function () { 'use strict'; - if (!window.OpenTrustCatalog) return; + if (!window.EtticOTCCatalog) return; - var data = window.OpenTrustCatalog; + var data = window.EtticOTCCatalog; var entries = (data.catalog && data.catalog.entries) || []; if (!entries.length) return; @@ -457,12 +457,12 @@ // ── Build dropdown DOM ─────────────────────────────────────── var titleWrap = document.getElementById('titlewrap') || titleInput.parentNode; var shell = document.createElement('div'); - shell.className = 'ot-typeahead'; + shell.className = 'ettic-otc-typeahead'; var panel = document.createElement('ul'); - panel.className = 'ot-typeahead__panel'; + panel.className = 'ettic-otc-typeahead__panel'; panel.setAttribute('role', 'listbox'); - panel.id = 'ot-typeahead-listbox'; + panel.id = 'ettic-otc-typeahead-listbox'; panel.setAttribute('aria-label', I18N.suggestions || 'Catalog suggestions'); panel.hidden = true; @@ -542,7 +542,7 @@ // No-match hint, a single non-selectable footer row so the user // knows manual entry still works. var hint = document.createElement('li'); - hint.className = 'ot-typeahead__hint'; + hint.className = 'ettic-otc-typeahead__hint'; hint.textContent = I18N.noMatchHint || 'No match in catalog, just keep typing to add manually.'; panel.appendChild(hint); openPanel(); @@ -552,18 +552,18 @@ for (var i = 0; i < results.length; i++) { var r = results[i]; var li = document.createElement('li'); - li.className = 'ot-typeahead__option'; - li.id = 'ot-ta-opt-' + i; + li.className = 'ettic-otc-typeahead__option'; + li.id = 'ettic-otc-ta-opt-' + i; li.setAttribute('role', 'option'); li.setAttribute('aria-selected', 'false'); li.dataset.index = String(i); var nameEl = document.createElement('span'); - nameEl.className = 'ot-typeahead__option-name'; + nameEl.className = 'ettic-otc-typeahead__option-name'; nameEl.textContent = r.name; var hintEl = document.createElement('span'); - hintEl.className = 'ot-typeahead__option-hint'; + hintEl.className = 'ettic-otc-typeahead__option-hint'; hintEl.textContent = hintText; li.appendChild(nameEl); @@ -575,7 +575,7 @@ }; var setActive = function (idx) { - var opts = panel.querySelectorAll('.ot-typeahead__option'); + var opts = panel.querySelectorAll('.ettic-otc-typeahead__option'); if (!opts.length) return; if (idx < 0) idx = opts.length - 1; if (idx >= opts.length) idx = 0; @@ -593,21 +593,21 @@ // Apply a single-line colored border to the field wrapper, append a // short helper message in the matching tint below it, and re-trigger // the flash animation so the user sees what changed. - var wrap = fieldEl.closest('.ot-meta-field'); + var wrap = fieldEl.closest('.ettic-otc-meta-field'); if (!wrap) return; - var existing = wrap.querySelector('.ot-typeahead-help'); + var existing = wrap.querySelector('.ettic-otc-typeahead-help'); if (existing) existing.remove(); // Remove-then-add forces the browser to replay the flash keyframes. - wrap.classList.remove('ot-typeahead-filled', 'is-fact', 'is-review'); + wrap.classList.remove('ettic-otc-typeahead-filled', 'is-fact', 'is-review'); // Force a reflow so the next class add re-starts the animation. void wrap.offsetWidth; - wrap.classList.add('ot-typeahead-filled', tier === 'review' ? 'is-review' : 'is-fact'); - fieldEl.dataset.otPrefilled = tier; + wrap.classList.add('ettic-otc-typeahead-filled', tier === 'review' ? 'is-review' : 'is-fact'); + fieldEl.dataset.etticOtcPrefilled = tier; var help = document.createElement('p'); - help.className = 'ot-typeahead-help' + (tier === 'review' ? ' is-review' : ''); + help.className = 'ettic-otc-typeahead-help' + (tier === 'review' ? ' is-review' : ''); help.textContent = tier === 'review' ? (I18N.helpReview || 'Auto-filled template, please verify this matches how you use this service.') : (I18N.helpFact || 'Auto-filled from catalog, you may want to verify this.'); @@ -617,39 +617,39 @@ // Wipe every field that was previously filled by a catalog entry so // that a new selection produces a clean replacement. User-typed values - // (without the data-ot-prefilled marker) are left untouched. + // (without the data-ettic-otc-prefilled marker) are left untouched. var clearAllPrefilled = function () { - var filled = document.querySelectorAll('[data-ot-prefilled]'); + var filled = document.querySelectorAll('[data-ettic-otc-prefilled]'); for (var i = 0; i < filled.length; i++) { var el = filled[i]; if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') { el.value = ''; } else if (el.tagName === 'SELECT') { el.selectedIndex = 0; - } else if (el.classList && el.classList.contains('ot-tags')) { - var tags = el.querySelectorAll('.ot-tag'); + } else if (el.classList && el.classList.contains('ettic-otc-tags')) { + var tags = el.querySelectorAll('.ettic-otc-tag'); for (var t = 0; t < tags.length; t++) tags[t].remove(); } - delete el.dataset.otPrefilled; - var wrap = el.closest('.ot-meta-field'); + delete el.dataset.etticOtcPrefilled; + var wrap = el.closest('.ettic-otc-meta-field'); if (wrap) { - wrap.classList.remove('ot-typeahead-filled', 'is-fact', 'is-review'); - var help = wrap.querySelector('.ot-typeahead-help'); + wrap.classList.remove('ettic-otc-typeahead-filled', 'is-fact', 'is-review'); + var help = wrap.querySelector('.ettic-otc-typeahead-help'); if (help) help.remove(); } } }; - // Add a tag to an `.ot-tags` container, matching the existing DOM shape + // Add a tag to an `.ettic-otc-tags` container, matching the existing DOM shape // in assets/js/admin.js (fieldName[i][name]). var addTagToContainer = function (container, value) { - var fieldName = container.getAttribute('data-ot-tags'); + var fieldName = container.getAttribute('data-ettic-otc-tags'); if (!fieldName) return; - var idx = container.querySelectorAll('.ot-tag').length; + var idx = container.querySelectorAll('.ettic-otc-tag').length; var tag = document.createElement('span'); - tag.className = 'ot-tag'; + tag.className = 'ettic-otc-tag'; var text = document.createElement('span'); - text.className = 'ot-tag__text'; + text.className = 'ettic-otc-tag__text'; text.textContent = value; var hidden = document.createElement('input'); hidden.type = 'hidden'; @@ -657,13 +657,13 @@ hidden.value = value; var remove = document.createElement('button'); remove.type = 'button'; - remove.className = 'ot-tag__remove'; + remove.className = 'ettic-otc-tag__remove'; remove.setAttribute('aria-label', 'Remove'); remove.innerHTML = '×'; tag.appendChild(text); tag.appendChild(hidden); tag.appendChild(remove); - var input = container.querySelector('.ot-tags__input'); + var input = container.querySelector('.ettic-otc-tags__input'); if (input) { container.insertBefore(tag, input); } else { @@ -674,14 +674,14 @@ var applyField = function (metaKey, value, tier) { var domId = metaKeyToDomId(metaKey); - // Tag-array fields render as `.ot-tags[data-ot-tags=""]`. + // Tag-array fields render as `.ettic-otc-tags[data-ettic-otc-tags=""]`. // Locate by the data attribute — there is no element with id=domId. - var tagContainer = document.querySelector('.ot-tags[data-ot-tags="' + domId + '"]'); + var tagContainer = document.querySelector('.ettic-otc-tags[data-ettic-otc-tags="' + domId + '"]'); if (tagContainer) { if (!Array.isArray(value)) return; // Respect any existing user-typed tags; the catalog-prefilled // ones were already wiped in clearAllPrefilled(). - if (tagContainer.querySelectorAll('.ot-tag').length > 0) return; + if (tagContainer.querySelectorAll('.ettic-otc-tag').length > 0) return; for (var i = 0; i < value.length; i++) { addTagToContainer(tagContainer, String(value[i])); } @@ -783,7 +783,7 @@ panel.addEventListener('mousedown', function (e) { // mousedown (not click) so it fires before the title's blur - var opt = e.target.closest('.ot-typeahead__option'); + var opt = e.target.closest('.ettic-otc-typeahead__option'); if (!opt) return; e.preventDefault(); var idx = parseInt(opt.dataset.index || '0', 10); @@ -810,10 +810,10 @@ (function () { 'use strict'; - var select = document.querySelector('[data-ot-cert-type]'); + var select = document.querySelector('[data-ettic-otc-cert-type]'); if (!select) return; - var certifiedOnlyFields = document.querySelectorAll('[data-ot-cert-certified-only]'); + var certifiedOnlyFields = document.querySelectorAll('[data-ettic-otc-cert-certified-only]'); if (!certifiedOnlyFields.length) return; var apply = function () { @@ -840,7 +840,7 @@ if (!toggle) return; toggle.addEventListener('change', function () { - var wrap = document.getElementById('ot-version-summary-wrap'); + var wrap = document.getElementById('ettic-otc-version-summary-wrap'); if (wrap) { wrap.style.display = this.checked ? 'block' : 'none'; } diff --git a/assets/js/chat.js b/assets/js/chat.js index 3535e18..73990e7 100644 --- a/assets/js/chat.js +++ b/assets/js/chat.js @@ -10,7 +10,7 @@ (function () { 'use strict'; - var configEl = document.getElementById('ot-chat-config'); + var configEl = document.getElementById('ettic-otc-chat-config'); if (!configEl) { return; } @@ -34,12 +34,12 @@ // ── DOM refs ────────────────────────────── - var shellEl = document.querySelector('[data-ot-chat-shell]'); - var messagesEl = document.querySelector('[data-ot-chat-messages]'); - var form = document.querySelector('[data-ot-chat-form]'); - var input = document.querySelector('[data-ot-chat-input]'); - var sendBtn = document.querySelector('[data-ot-chat-send]'); - var resetBtn = document.querySelector('[data-ot-chat-reset]'); + var shellEl = document.querySelector('[data-ettic-otc-chat-shell]'); + var messagesEl = document.querySelector('[data-ettic-otc-chat-messages]'); + var form = document.querySelector('[data-ettic-otc-chat-form]'); + var input = document.querySelector('[data-ettic-otc-chat-input]'); + var sendBtn = document.querySelector('[data-ettic-otc-chat-send]'); + var resetBtn = document.querySelector('[data-ettic-otc-chat-reset]'); if (!form || !input || !messagesEl || !shellEl) { return; @@ -130,9 +130,9 @@ // Suggested question chips. Submit directly — do NOT populate the // input field, or the text would linger there after the submit (the // input-clear only happens inside the form submit handler). - document.querySelectorAll('[data-ot-chat-chip]').forEach(function (btn) { + document.querySelectorAll('[data-ettic-otc-chat-chip]').forEach(function (btn) { btn.addEventListener('click', function () { - var q = btn.getAttribute('data-ot-chat-chip') || ''; + var q = btn.getAttribute('data-ettic-otc-chat-chip') || ''; if (!q) return; submitUserMessage(q); }); @@ -440,7 +440,7 @@ // the aggregated tool_call event below carries the real // count and will morph this pill's label via the reuse path. if (evt.data && typeof evt.data.summary === 'string' && evt.data.summary !== '') { - var thinkingEl = bubble.bodyEl.querySelector('.ot-chat-thinking'); + var thinkingEl = bubble.bodyEl.querySelector('.ettic-otc-chat-thinking'); if (thinkingEl) thinkingEl.remove(); showToolStatus(bubble, evt.data.summary); } @@ -454,7 +454,7 @@ // "Searching for X" → "Searched for X") instead of leaving // the active-tense label up forever. if (evt.data && typeof evt.data.summary === 'string' && evt.data.summary !== '') { - var thinking = bubble.bodyEl.querySelector('.ot-chat-thinking'); + var thinking = bubble.bodyEl.querySelector('.ettic-otc-chat-thinking'); if (thinking) thinking.remove(); bubble.totalToolCount = (bubble.totalToolCount || 0) + (parseInt(evt.data.count, 10) || 1); bubble.toolCallEventCount = (bubble.toolCallEventCount || 0) + 1; @@ -520,10 +520,10 @@ function buildThinkingIndicator() { var wrap = document.createElement('span'); - wrap.className = 'ot-chat-thinking'; + wrap.className = 'ettic-otc-chat-thinking'; for (var i = 0; i < 3; i++) { var dot = document.createElement('span'); - dot.className = 'ot-chat-thinking-dot'; + dot.className = 'ettic-otc-chat-thinking-dot'; wrap.appendChild(dot); } return wrap; @@ -539,53 +539,53 @@ */ function buildMessageScaffold(role, name) { var msg = document.createElement('div'); - msg.className = 'ot-chat-msg ot-chat-msg--' + role; + msg.className = 'ettic-otc-chat-msg ettic-otc-chat-msg--' + role; var avatar = document.createElement('div'); - avatar.className = 'ot-chat-msg__avatar'; + avatar.className = 'ettic-otc-chat-msg__avatar'; avatar.setAttribute('aria-hidden', 'true'); if (role === 'assistant' && config.avatar_url) { var avatarImg = document.createElement('img'); - avatarImg.className = 'ot-chat-msg__avatar-img'; + avatarImg.className = 'ettic-otc-chat-msg__avatar-img'; avatarImg.src = config.avatar_url; avatarImg.alt = ''; avatar.appendChild(avatarImg); } else { var iconEl = svgIcon(role === 'user' ? USER_SVG : ASSISTANT_SVG); if (iconEl && iconEl.nodeType === 1) { - iconEl.classList.add('ot-chat-msg__avatar-icon'); + iconEl.classList.add('ettic-otc-chat-msg__avatar-icon'); } avatar.appendChild(iconEl); } // Pre-render a warning icon kept hidden by default; CSS reveals it - // when the parent row gains `.ot-chat-msg--refused`. + // when the parent row gains `.ettic-otc-chat-msg--refused`. if (role === 'assistant') { var warning = svgIcon(WARNING_SVG); if (warning && warning.nodeType === 1) { - warning.classList.add('ot-chat-msg__avatar-warning'); + warning.classList.add('ettic-otc-chat-msg__avatar-warning'); warning.setAttribute('style', 'display:none'); } avatar.appendChild(warning); } var content = document.createElement('div'); - content.className = 'ot-chat-msg__content'; + content.className = 'ettic-otc-chat-msg__content'; var header = document.createElement('header'); - header.className = 'ot-chat-msg__header'; + header.className = 'ettic-otc-chat-msg__header'; var nameEl = document.createElement('strong'); - nameEl.className = 'ot-chat-msg__name'; + nameEl.className = 'ettic-otc-chat-msg__name'; nameEl.textContent = name; var sep = document.createElement('span'); - sep.className = 'ot-chat-msg__separator'; + sep.className = 'ettic-otc-chat-msg__separator'; sep.textContent = '·'; var time = document.createElement('time'); - time.className = 'ot-chat-msg__time'; + time.className = 'ettic-otc-chat-msg__time'; time.textContent = nowLabel(); header.appendChild(nameEl); @@ -593,7 +593,7 @@ header.appendChild(time); var body = document.createElement('div'); - body.className = 'ot-chat-msg__body'; + body.className = 'ettic-otc-chat-msg__body'; content.appendChild(header); content.appendChild(body); @@ -614,7 +614,7 @@ function appendAssistantBubble(text, citations, refused, finalized) { var scaffold = buildMessageScaffold('assistant', config.assistant_name || config.company_name || 'Assistant'); if (refused) { - scaffold.msgEl.classList.add('ot-chat-msg--refused'); + scaffold.msgEl.classList.add('ettic-otc-chat-msg--refused'); } // During streaming we hold a single text node in the body that receives @@ -750,7 +750,7 @@ if (pill.el) { pill.el.classList.remove('is-static'); - var labelEl = pill.el.querySelector('.ot-chat-thinking__label'); + var labelEl = pill.el.querySelector('.ettic-otc-chat-thinking__label'); if (labelEl) labelEl.textContent = summary; } if (bubble.toolPillTimer) clearTimeout(bubble.toolPillTimer); @@ -798,7 +798,7 @@ // Hide the "Thinking…" indicator now that the pill is on screen — // both serve the same "something is happening" purpose. bubble.streamThinkingHidden = true; - var thinkingEl = bubble.bodyEl.querySelector('.ot-chat-thinking'); + var thinkingEl = bubble.bodyEl.querySelector('.ettic-otc-chat-thinking'); if (thinkingEl) thinkingEl.remove(); bubble.toolPillTimer = setTimeout(function () { settlePillTimer(bubble, newPill); }, TOOL_STATUS_MIN_MS); @@ -813,7 +813,7 @@ function ensureLiveTail(bubble) { if (!bubble.liveTailEl) { bubble.liveTailEl = document.createElement('span'); - bubble.liveTailEl.className = 'ot-chat-msg__live-tail'; + bubble.liveTailEl.className = 'ettic-otc-chat-msg__live-tail'; bubble.bodyEl.appendChild(bubble.liveTailEl); } } @@ -831,7 +831,7 @@ var el = null; if (seg.kind === 'text') { el = document.createElement('div'); - el.className = 'ot-chat-msg__seg-text'; + el.className = 'ettic-otc-chat-msg__seg-text'; el.appendChild(renderMarkdown(seg.text)); } else if (seg.kind === 'pill') { el = buildPillElement(seg.summary, seg.locked); @@ -903,7 +903,7 @@ if (settled) { pill.summary = settled; if (pill.el) { - var labelEl = pill.el.querySelector('.ot-chat-thinking__label'); + var labelEl = pill.el.querySelector('.ettic-otc-chat-thinking__label'); if (labelEl) labelEl.textContent = settled; } } @@ -923,14 +923,14 @@ */ function buildPillElement(summary, locked) { var pill = document.createElement('div'); - pill.className = 'ot-chat-tool-status' + (locked ? ' is-static' : ''); + pill.className = 'ettic-otc-chat-tool-status' + (locked ? ' is-static' : ''); for (var i = 0; i < 3; i++) { var dot = document.createElement('span'); - dot.className = 'ot-chat-thinking-dot'; + dot.className = 'ettic-otc-chat-thinking-dot'; pill.appendChild(dot); } var label = document.createElement('span'); - label.className = 'ot-chat-thinking__label'; + label.className = 'ettic-otc-chat-thinking__label'; label.textContent = summary; pill.appendChild(label); return pill; @@ -1040,7 +1040,7 @@ var hasPriorContent = (bubble.priorSegments && bubble.priorSegments.length > 0); if (!bubble.streamThinkingHidden && (visible.length > 0 || hasPriorContent)) { bubble.streamThinkingHidden = true; - var thinking = bubble.bodyEl.querySelector('.ot-chat-thinking'); + var thinking = bubble.bodyEl.querySelector('.ettic-otc-chat-thinking'); if (thinking) thinking.remove(); } @@ -1229,16 +1229,16 @@ // render fully-formed messages — animating them on every reload // would be visual noise. var animateReveal = bubble.usedStreaming && !REDUCED_MOTION; - var revealClass = animateReveal ? ' ot-chat-msg__reveal' : ''; + var revealClass = animateReveal ? ' ettic-otc-chat-msg__reveal' : ''; // Refusal copy + contact CTA. if (bubble.refused && bubble.citations.length === 0) { - bubble.msgEl.classList.add('ot-chat-msg--refused'); + bubble.msgEl.classList.add('ettic-otc-chat-msg--refused'); // Reveal the pre-rendered warning icon inside the avatar so the // refusal signal lives on the avatar (yellow + orange warning), // not the body. - var warningEl = bubble.msgEl.querySelector('.ot-chat-msg__avatar-warning'); + var warningEl = bubble.msgEl.querySelector('.ettic-otc-chat-msg__avatar-warning'); if (warningEl) { warningEl.removeAttribute('style'); } @@ -1250,7 +1250,7 @@ } if (config.contact_url) { var cta = document.createElement('a'); - cta.className = 'ot-chat-contact-btn' + revealClass; + cta.className = 'ettic-otc-chat-contact-btn' + revealClass; if (animateReveal) cta.style.animationDelay = '40ms'; cta.href = config.contact_url; cta.textContent = strings.refused_contact || 'Contact security team →'; @@ -1261,7 +1261,7 @@ // Sources block (after the body, inside content column). if (bubble.citations.length > 0) { var sources = document.createElement('div'); - sources.className = 'ot-chat-msg__sources' + revealClass; + sources.className = 'ettic-otc-chat-msg__sources' + revealClass; if (animateReveal) sources.style.animationDelay = '40ms'; var h4 = document.createElement('h4'); @@ -1287,7 +1287,7 @@ // the sources above" to "I don't have enough info" is nonsensical. if (!(bubble.refused && bubble.citations.length === 0)) { var disclaimer = document.createElement('p'); - disclaimer.className = 'ot-chat-msg__disclaimer' + revealClass; + disclaimer.className = 'ettic-otc-chat-msg__disclaimer' + revealClass; if (animateReveal) disclaimer.style.animationDelay = '140ms'; disclaimer.textContent = strings.disclaimer || 'AI-generated answer. Not legal, security, or compliance advice. Verify against the sources above.'; bubble.bodyEl.parentNode.appendChild(disclaimer); @@ -1295,7 +1295,7 @@ // Action bar — Copy / Share / Print with icons. var actions = document.createElement('div'); - actions.className = 'ot-chat-msg__actions' + revealClass; + actions.className = 'ettic-otc-chat-msg__actions' + revealClass; if (animateReveal) actions.style.animationDelay = '220ms'; actions.appendChild(buildActionButton(COPY_SVG, strings.copy || 'Copy', function (btn) { copyBubble(bubble, btn); })); actions.appendChild(buildActionButton(PRINT_SVG, strings.print || 'Print', function () { window.print(); })); @@ -1535,14 +1535,14 @@ function appendBanner(kind, text) { var b = document.createElement('div'); - b.className = 'ot-chat-banner ot-chat-banner--' + kind; + b.className = 'ettic-otc-chat-banner ettic-otc-chat-banner--' + kind; b.appendChild(document.createTextNode(text)); messagesEl.appendChild(b); } function appendRetryBanner(userText) { var b = document.createElement('div'); - b.className = 'ot-chat-banner ot-chat-banner--error'; + b.className = 'ettic-otc-chat-banner ettic-otc-chat-banner--error'; b.appendChild(document.createTextNode(strings.retry || 'Connection lost. Retry?')); var btn = document.createElement('button'); @@ -1557,7 +1557,7 @@ } var responseBubble = appendAssistantBubble('', [], false, false); var thinkingEl = document.createElement('span'); - thinkingEl.className = 'ot-chat-thinking'; + thinkingEl.className = 'ettic-otc-chat-thinking'; thinkingEl.textContent = strings.thinking || 'Thinking…'; responseBubble.bodyEl.insertBefore(thinkingEl, responseBubble.textNode); streamResponse(responseBubble, thinkingEl, userText); @@ -1567,9 +1567,9 @@ } function appendLongHint() { - if (document.querySelector('.ot-chat-long-hint')) return; + if (document.querySelector('.ettic-otc-chat-long-hint')) return; var hint = document.createElement('div'); - hint.className = 'ot-chat-long-hint'; + hint.className = 'ettic-otc-chat-long-hint'; hint.appendChild(document.createTextNode(strings.long_hint || 'This conversation is getting long.')); messagesEl.appendChild(hint); } @@ -1577,16 +1577,16 @@ function setSendButtonMode(mode) { [sendBtn].forEach(function (btn) { if (!btn) return; - var labelEl = btn.querySelector('.ot-chat-send__label'); + var labelEl = btn.querySelector('.ettic-otc-chat-send__label'); if (mode === 'stop') { - btn.classList.add('ot-chat-send--stop'); + btn.classList.add('ettic-otc-chat-send--stop'); btn.type = 'button'; btn.onclick = function () { if (currentAbort) currentAbort.abort(); }; if (labelEl) labelEl.textContent = strings.stop || 'Stop'; } else { - btn.classList.remove('ot-chat-send--stop'); + btn.classList.remove('ettic-otc-chat-send--stop'); btn.type = 'submit'; btn.onclick = null; if (labelEl) labelEl.textContent = strings.send || 'Send'; @@ -1712,7 +1712,7 @@ function renderTurnstileWidget(onSuccess) { var holder = document.createElement('div'); - holder.className = 'ot-chat-turnstile'; + holder.className = 'ettic-otc-chat-turnstile'; holder.style.margin = '12px 0'; var note = document.createElement('p'); @@ -1747,7 +1747,7 @@ function renderBudgetExhaustedState(resetAt) { setChattingState(true); var banner = document.createElement('div'); - banner.className = 'ot-chat-banner ot-chat-banner--warn'; + banner.className = 'ettic-otc-chat-banner ettic-otc-chat-banner--warn'; var msg = strings.unavailable || 'AI chat is temporarily unavailable.'; if (resetAt) { var when = new Date(resetAt); diff --git a/assets/js/frontend.js b/assets/js/frontend.js index d969b18..6e29176 100644 --- a/assets/js/frontend.js +++ b/assets/js/frontend.js @@ -32,7 +32,7 @@ e.preventDefault(); - var nav = document.querySelector('.ot-nav'); + var nav = document.querySelector('.ettic-otc-nav'); var offset = nav ? nav.offsetHeight + 16 : 16; window.scrollTo({ @@ -49,7 +49,7 @@ // ── Scroll Spy ───────────────────────────────── function initScrollSpy() { - var navLinks = document.querySelectorAll('[data-ot-nav]'); + var navLinks = document.querySelectorAll('[data-ettic-otc-nav]'); if (!navLinks.length) return; var sections = []; @@ -63,7 +63,7 @@ if (!sections.length) return; - var nav = document.querySelector('.ot-nav'); + var nav = document.querySelector('.ettic-otc-nav'); var offset = nav ? nav.offsetHeight + 32 : 32; function update() { @@ -76,8 +76,8 @@ } } - navLinks.forEach(function (l) { l.classList.remove('ot-nav__link--active'); }); - if (active) active.link.classList.add('ot-nav__link--active'); + navLinks.forEach(function (l) { l.classList.remove('ettic-otc-nav__link--active'); }); + if (active) active.link.classList.add('ettic-otc-nav__link--active'); } window.addEventListener('scroll', throttle(update, 100), { passive: true }); @@ -87,7 +87,7 @@ // ── Table Sort ───────────────────────────────── function initTableSort() { - document.querySelectorAll('.ot-table th[data-ot-sort]').forEach(function (th) { + document.querySelectorAll('.ettic-otc-table th[data-ettic-otc-sort]').forEach(function (th) { th.addEventListener('click', function () { sortTable(this); }); @@ -99,11 +99,11 @@ var tbody = table.querySelector('tbody'); var rows = Array.from(tbody.querySelectorAll('tr')); var colIndex = Array.from(th.parentNode.children).indexOf(th); - var ascending = th.getAttribute('data-ot-sort-dir') !== 'asc'; + var ascending = th.getAttribute('data-ettic-otc-sort-dir') !== 'asc'; // Clear sort direction on siblings. - th.parentNode.querySelectorAll('[data-ot-sort]').forEach(function (sibling) { - if (sibling !== th) sibling.removeAttribute('data-ot-sort-dir'); + th.parentNode.querySelectorAll('[data-ettic-otc-sort]').forEach(function (sibling) { + if (sibling !== th) sibling.removeAttribute('data-ettic-otc-sort-dir'); }); rows.sort(function (a, b) { @@ -114,7 +114,7 @@ return ascending ? aText.localeCompare(bText) : bText.localeCompare(aText); }); - th.setAttribute('data-ot-sort-dir', ascending ? 'asc' : 'desc'); + th.setAttribute('data-ettic-otc-sort-dir', ascending ? 'asc' : 'desc'); var fragment = document.createDocumentFragment(); rows.forEach(function (row) { fragment.appendChild(row); }); @@ -125,9 +125,9 @@ function initDpCards() { // Card header toggle — expand/collapse details. - document.querySelectorAll('[data-ot-dp-toggle]').forEach(function (head) { + document.querySelectorAll('[data-ettic-otc-dp-toggle]').forEach(function (head) { function toggle() { - var detailId = head.getAttribute('data-ot-dp-toggle'); + var detailId = head.getAttribute('data-ettic-otc-dp-toggle'); var detail = document.getElementById(detailId); if (!detail) return; @@ -146,14 +146,14 @@ }); // "View N more" buttons — show overflow items. - document.querySelectorAll('[data-ot-dp-more]').forEach(function (btn) { + document.querySelectorAll('[data-ettic-otc-dp-more]').forEach(function (btn) { btn.addEventListener('click', function () { - var card = this.closest('.ot-dp-card'); - var overflow = card.querySelector('.ot-dp-card__list--overflow'); + var card = this.closest('.ettic-otc-dp-card'); + var overflow = card.querySelector('.ettic-otc-dp-card__list--overflow'); if (overflow) { overflow.hidden = false; } - this.classList.add('ot-dp-card__more--hidden'); + this.classList.add('ettic-otc-dp-card__more--hidden'); }); }); } @@ -161,7 +161,7 @@ // ── Version History Toggle ───────────────────── function initVersionHistory() { - var toggle = document.querySelector('[data-ot-version-toggle]'); + var toggle = document.querySelector('[data-ettic-otc-version-toggle]'); if (!toggle) return; var list = toggle.nextElementSibling; @@ -177,20 +177,20 @@ // ── Clamp Toggles (subprocessor table) ───────── function initClampToggles() { - document.querySelectorAll('.ot-table__clamp-text').forEach(function (el) { + document.querySelectorAll('.ettic-otc-table__clamp-text').forEach(function (el) { // Show the "more" button only if text is actually truncated. if (el.scrollHeight > el.clientHeight + 1) { var btn = el.nextElementSibling; - if (btn && btn.hasAttribute('data-ot-clamp-toggle')) { + if (btn && btn.hasAttribute('data-ettic-otc-clamp-toggle')) { btn.style.display = 'inline'; } } }); - document.querySelectorAll('[data-ot-clamp-toggle]').forEach(function (btn) { + document.querySelectorAll('[data-ettic-otc-clamp-toggle]').forEach(function (btn) { btn.addEventListener('click', function () { var text = this.previousElementSibling; - var expanded = text.classList.toggle('ot-table__clamp-text--expanded'); + var expanded = text.classList.toggle('ettic-otc-table__clamp-text--expanded'); this.textContent = expanded ? 'less' : 'more'; }); }); diff --git a/composer.json b/composer.json index c186f8d..c93d971 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { - "name": "ettic/opentrust-dev", - "description": "Dev tooling for the OpenTrust WordPress plugin (PHPStan). Not shipped to wp.org.", + "name": "ettic/open-trust-center-by-ettic-dev", + "description": "Dev tooling for the Open Trust Center by Ettic WordPress plugin (PHPStan). Not shipped to wp.org.", "license": "GPL-2.0-or-later", "require-dev": { "phpstan/phpstan": "^2.0", diff --git a/includes/class-opentrust-admin-ai.php b/includes/class-ettic-otc-admin-ai.php similarity index 72% rename from includes/class-opentrust-admin-ai.php rename to includes/class-ettic-otc-admin-ai.php index 9c49c46..14c8263 100644 --- a/includes/class-opentrust-admin-ai.php +++ b/includes/class-ettic-otc-admin-ai.php @@ -2,21 +2,21 @@ /** * AI Chat settings tab and its admin-post handlers. * - * Owns the entire "AI Chat" surface inside the OpenTrust settings page: + * Owns the entire "AI Chat" surface inside the Ettic_OTC settings page: * the provider picker (Anthropic primary, others behind an "advanced" * disclosure), the per-provider key card with validate-and-save flow, * the post-key model picker + budget/limit form, and the four * admin-post.php endpoints that drive key save/forget/refresh and the * summary-backfill sweep. * - * Bootstrapped by OpenTrust_Admin's constructor; subscribes its own + * Bootstrapped by Ettic_OTC_Admin's constructor; subscribes its own * admin_post_* hooks. The settings page (which still lives on - * OpenTrust_Admin as the menu callback) calls render_ai_tab() when the + * Ettic_OTC_Admin as the menu callback) calls render_ai_tab() when the * "ai" tab is active. * * Settings writes that bypass the sanitize_settings filter (key * validation flips ai_enabled / ai_provider / ai_model_list_cached_at) - * route through OpenTrust_Admin_Settings::save_settings_raw(). + * route through Ettic_OTC_Admin_Settings::save_settings_raw(). */ declare(strict_types=1); @@ -25,9 +25,9 @@ exit; } -final class OpenTrust_Admin_AI { +final class Ettic_OTC_Admin_AI { - public const CRON_HOOK = 'opentrust_ai_models_refresh'; + public const CRON_HOOK = 'ettic_otc_ai_models_refresh'; public const CACHE_TTL = 25 * HOUR_IN_SECONDS; // Slightly > daily cron cadence so the cache never expires between ticks. private static ?self $instance = null; @@ -37,10 +37,10 @@ public static function instance(): self { } private function __construct() { - add_action('admin_post_opentrust_ai_save_key', [$this, 'handle_ai_save_key']); - add_action('admin_post_opentrust_ai_forget_key', [$this, 'handle_ai_forget_key']); - add_action('admin_post_opentrust_ai_refresh_models', [$this, 'handle_ai_refresh_models']); - add_action('admin_post_opentrust_ai_summarize_sweep', [$this, 'handle_ai_summarize_sweep']); + add_action('admin_post_ettic_otc_ai_save_key', [$this, 'handle_ai_save_key']); + add_action('admin_post_ettic_otc_ai_forget_key', [$this, 'handle_ai_forget_key']); + add_action('admin_post_ettic_otc_ai_refresh_models', [$this, 'handle_ai_refresh_models']); + add_action('admin_post_ettic_otc_ai_summarize_sweep', [$this, 'handle_ai_summarize_sweep']); // Idempotent re-schedule on admin page loads — defends against the // daily cron getting cleared externally without forcing a deactivate/ @@ -53,15 +53,15 @@ private function __construct() { // ────────────────────────────────────────────── public function render_ai_tab(array $settings): void { - $stored_keys = OpenTrust_Chat_Secrets::get_all(); + $stored_keys = Ettic_OTC_Chat_Secrets::get_all(); $active_provider = $settings['ai_provider'] ?? ''; $has_active_key = $active_provider !== '' && isset($stored_keys[$active_provider]); $is_non_anthropic_active = $has_active_key && $active_provider !== 'anthropic'; // Surface any transient notice from the admin-post handlers. - $notice = get_transient('opentrust_ai_notice_' . get_current_user_id()); + $notice = get_transient('ettic_otc_ai_notice_' . get_current_user_id()); if (is_array($notice)) { - delete_transient('opentrust_ai_notice_' . get_current_user_id()); + delete_transient('ettic_otc_ai_notice_' . get_current_user_id()); $class = $notice['type'] === 'error' ? 'notice-error' : 'notice-success'; printf( '

%s

', @@ -74,13 +74,13 @@ public function render_ai_tab(array $settings): void { ?> -
- +
+

%s. Only Anthropic uses a structural Citations API — every other provider relies on prompted citation tags the model can ignore or fabricate. For a published trust center, switch to Anthropic below.', 'opentrust'), ['strong' => []]), + wp_kses(__('You are currently using %s. Only Anthropic uses a structural Citations API — every other provider relies on prompted citation tags the model can ignore or fabricate. For a published trust center, switch to Anthropic below.', 'open-trust-center-by-ettic'), ['strong' => []]), esc_html(ucfirst($active_provider)) ); ?> @@ -88,22 +88,22 @@ public function render_ai_tab(array $settings): void {

-

+

Anthropic Claude with the native Citations API to answer visitor questions about your trust center. Every claim the assistant makes is tied to an exact quote from one of your published documents — so no policy text is invented and nothing is paraphrased into something you did not actually publish.', 'opentrust'), + __('Open Trust Center uses Anthropic Claude with the native Citations API to answer visitor questions about your trust center. Every claim the assistant makes is tied to an exact quote from one of your published documents — so no policy text is invented and nothing is paraphrased into something you did not actually publish.', 'open-trust-center-by-ettic'), ['strong' => []] ); ?>

-
- -
+
+ +

compliance surface. If the assistant invents a security commitment you never made, that is not a UX papercut — it is a misrepresentation of your security posture, and your customers and auditors will hold you to it.', 'opentrust'), + __('A trust center is a compliance surface. If the assistant invents a security commitment you never made, that is not a UX papercut — it is a misrepresentation of your security posture, and your customers and auditors will hold you to it.', 'open-trust-center-by-ettic'), ['strong' => []] ); ?> @@ -111,13 +111,13 @@ public function render_ai_tab(array $settings): void {

only major provider that exposes a structural Citations API. Documents are sent as typed blocks and the model emits citations as first-class events containing the exact source document and the exact quoted text. The model literally cannot return a citation for text that is not in your source documents.', 'opentrust'), + __('Anthropic is the only major provider that exposes a structural Citations API. Documents are sent as typed blocks and the model emits citations as first-class events containing the exact source document and the exact quoted text. The model literally cannot return a citation for text that is not in your source documents.', 'open-trust-center-by-ettic'), ['strong' => []] ); ?>

- +

@@ -141,10 +141,10 @@ public function render_ai_tab(array $settings): void { * up-to-date summary. */ private function render_summary_backfill_banner(array $settings, bool $has_active_key): void { - if (!$has_active_key || empty($settings['ai_auto_summarize']) || !class_exists('OpenTrust_Chat_Summarizer')) { + if (!$has_active_key || empty($settings['ai_auto_summarize']) || !class_exists('Ettic_OTC_Chat_Summarizer')) { return; } - $missing = OpenTrust_Chat_Summarizer::missing_summary_count(); + $missing = Ettic_OTC_Chat_Summarizer::missing_summary_count(); if ($missing < 1) { return; } @@ -160,26 +160,26 @@ private function render_summary_backfill_banner(array $settings, bool $has_activ '%d policy is missing an AI summary.', '%d policies are missing AI summaries.', $missing, - 'opentrust' + 'open-trust-center-by-ettic' ) ), (int) $missing ); ?> - +

- - - + + +
' . esc_html__('Choose a provider and add your key', 'opentrust') . ''; - echo '
'; + echo '

' . esc_html__('Choose a provider and add your key', 'open-trust-center-by-ettic') . '

'; + echo '
'; foreach ($providers as $provider) { $this->render_provider_card($provider, $stored_keys, $active_provider, 'advanced'); } @@ -208,26 +208,26 @@ private function render_ai_provider_picker(array $settings, array $stored_keys): $is_anthropic_active = $active_provider === 'anthropic' && isset($stored_keys['anthropic']); $advanced_open = $active_provider !== '' && $active_provider !== 'anthropic'; ?> -

+

render_provider_card($primary, $stored_keys, $active_provider, 'primary'); ?> -
> - +
> + -
- +
+

- +

- - + +

-
+
render_provider_card($provider, $stored_keys, $active_provider, 'advanced'); ?> @@ -251,60 +251,60 @@ private function render_provider_card(array $provider, array $stored_keys, strin $key_url = (string) $provider['key_url']; $is_active = $slug === $active_provider; $has_key = isset($stored_keys[$slug]); - $masked = $has_key ? OpenTrust_Chat_Secrets::mask($stored_keys[$slug]) : ''; + $masked = $has_key ? Ettic_OTC_Chat_Secrets::mask($stored_keys[$slug]) : ''; - $card_classes = ['ot-ai-card', 'ot-ai-card--' . $variant]; + $card_classes = ['ettic-otc-ai-card', 'ettic-otc-ai-card--' . $variant]; if ($is_active) { $card_classes[] = 'is-active'; } ?>
-
-

+
+

- +
-

- +

+

- -
+
- - + + -
- - + + - -
@@ -317,8 +317,8 @@ private function render_ai_settings_form(array $settings): void { $models = $this->cached_models_for($active_provider); $current_model = (string) ($settings['ai_model'] ?? ''); $refresh_url = wp_nonce_url( - admin_url('admin-post.php?action=opentrust_ai_refresh_models&provider=' . rawurlencode($active_provider)), - 'opentrust_ai_refresh_models' + admin_url('admin-post.php?action=ettic_otc_ai_refresh_models&provider=' . rawurlencode($active_provider)), + 'ettic_otc_ai_refresh_models' ); // Cache may have expired (empty list) or the provider may have @@ -343,53 +343,53 @@ private function render_ai_settings_form(array $settings): void { array_unshift($models, $snapshot); } ?> -

+

- + - + - + - + - + - + - + - + - + - + - + - + - - + + - + -

+

- +

- + - + - + - +
, error?: string} */ private function refresh_provider_cache(string $slug, string $api_key): array { - $adapter = OpenTrust_Chat_Provider::for($slug); + $adapter = Ettic_OTC_Chat_Provider::for($slug); if (!$adapter) { return ['ok' => false, 'error' => 'Unknown provider']; } @@ -654,38 +654,38 @@ private function refresh_provider_cache(string $slug, string $api_key): array { public function handle_ai_save_key(): void { if (!current_user_can('manage_options')) { - wp_die(esc_html__('You do not have permission to perform this action.', 'opentrust'), '', ['response' => 403]); + wp_die(esc_html__('You do not have permission to perform this action.', 'open-trust-center-by-ettic'), '', ['response' => 403]); } - check_admin_referer('opentrust_ai_save_key'); + check_admin_referer('ettic_otc_ai_save_key'); $provider = isset($_POST['provider']) ? sanitize_key((string) wp_unslash($_POST['provider'])) : ''; $api_key = isset($_POST['api_key']) ? trim(sanitize_text_field((string) wp_unslash($_POST['api_key']))) : ''; - $adapter = OpenTrust_Chat_Provider::for($provider); + $adapter = Ettic_OTC_Chat_Provider::for($provider); if (!$adapter) { - $this->ai_notice('error', __('Unknown provider.', 'opentrust')); + $this->ai_notice('error', __('Unknown provider.', 'open-trust-center-by-ettic')); $this->redirect_to_ai_tab(); } if ($api_key === '') { - $this->ai_notice('error', __('API key cannot be empty.', 'opentrust')); + $this->ai_notice('error', __('API key cannot be empty.', 'open-trust-center-by-ettic')); $this->redirect_to_ai_tab(); } $result = $this->refresh_provider_cache($provider, $api_key); if (empty($result['ok'])) { - $error = $result['error'] ?? __('Validation failed.', 'opentrust'); + $error = $result['error'] ?? __('Validation failed.', 'open-trust-center-by-ettic'); /* translators: 1: provider label, 2: provider error message */ - $msg = sprintf(__('%1$s rejected the key: %2$s', 'opentrust'), $adapter->label(), $error); + $msg = sprintf(__('%1$s rejected the key: %2$s', 'open-trust-center-by-ettic'), $adapter->label(), $error); $this->ai_notice('error', $msg); $this->redirect_to_ai_tab(); } - OpenTrust_Chat_Secrets::put($provider, $api_key); + Ettic_OTC_Chat_Secrets::put($provider, $api_key); // Update settings: mark AI enabled, record provider + cache timestamp, // and if no model is selected yet, pre-pick the first recommended model. - $settings = OpenTrust::get_settings(); + $settings = Ettic_OTC::get_settings(); $settings['ai_enabled'] = true; $settings['ai_provider'] = $provider; $settings['ai_model_list_cached_at'] = time(); @@ -701,85 +701,85 @@ public function handle_ai_save_key(): void { } } $this->snapshot_active_model($settings, $result['models']); - OpenTrust_Admin_Settings::instance()->save_settings_raw($settings); + Ettic_OTC_Admin_Settings::instance()->save_settings_raw($settings); /* translators: 1: provider label, 2: number of models */ - $count_msg = sprintf(__('%1$s key validated. Found %2$d model(s).', 'opentrust'), $adapter->label(), count($result['models'])); + $count_msg = sprintf(__('%1$s key validated. Found %2$d model(s).', 'open-trust-center-by-ettic'), $adapter->label(), count($result['models'])); $this->ai_notice('success', $count_msg); $this->redirect_to_ai_tab(); } public function handle_ai_forget_key(): void { if (!current_user_can('manage_options')) { - wp_die(esc_html__('You do not have permission to perform this action.', 'opentrust'), '', ['response' => 403]); + wp_die(esc_html__('You do not have permission to perform this action.', 'open-trust-center-by-ettic'), '', ['response' => 403]); } - check_admin_referer('opentrust_ai_forget_key'); + check_admin_referer('ettic_otc_ai_forget_key'); $provider = isset($_POST['provider']) ? sanitize_key((string) wp_unslash($_POST['provider'])) : ''; - $adapter = OpenTrust_Chat_Provider::for($provider); + $adapter = Ettic_OTC_Chat_Provider::for($provider); if (!$adapter) { - $this->ai_notice('error', __('Unknown provider.', 'opentrust')); + $this->ai_notice('error', __('Unknown provider.', 'open-trust-center-by-ettic')); $this->redirect_to_ai_tab(); } // Clear cached model list for this key before forgetting. - $existing = OpenTrust_Chat_Secrets::get($provider); + $existing = Ettic_OTC_Chat_Secrets::get($provider); if ($existing !== null) { - $fingerprint = OpenTrust_Chat_Secrets::fingerprint($existing); - delete_transient('opentrust_models_' . $provider . '_' . $fingerprint); + $fingerprint = Ettic_OTC_Chat_Secrets::fingerprint($existing); + delete_transient('ettic_otc_models_' . $provider . '_' . $fingerprint); } - OpenTrust_Chat_Secrets::forget($provider); + Ettic_OTC_Chat_Secrets::forget($provider); // If the forgotten provider was the active one, disable chat and clear the model. - $settings = OpenTrust::get_settings(); + $settings = Ettic_OTC::get_settings(); if (($settings['ai_provider'] ?? '') === $provider) { $settings['ai_enabled'] = false; $settings['ai_provider'] = ''; $settings['ai_model'] = ''; $settings['ai_model_list_cached_at'] = 0; - OpenTrust_Admin_Settings::instance()->save_settings_raw($settings); + Ettic_OTC_Admin_Settings::instance()->save_settings_raw($settings); } - $this->ai_notice('success', __('Key removed.', 'opentrust')); + $this->ai_notice('success', __('Key removed.', 'open-trust-center-by-ettic')); $this->redirect_to_ai_tab(); } public function handle_ai_refresh_models(): void { if (!current_user_can('manage_options')) { - wp_die(esc_html__('You do not have permission to perform this action.', 'opentrust'), '', ['response' => 403]); + wp_die(esc_html__('You do not have permission to perform this action.', 'open-trust-center-by-ettic'), '', ['response' => 403]); } - check_admin_referer('opentrust_ai_refresh_models'); + check_admin_referer('ettic_otc_ai_refresh_models'); $provider = isset($_GET['provider']) ? sanitize_key((string) wp_unslash($_GET['provider'])) : ''; - $adapter = OpenTrust_Chat_Provider::for($provider); + $adapter = Ettic_OTC_Chat_Provider::for($provider); if (!$adapter) { - $this->ai_notice('error', __('Unknown provider.', 'opentrust')); + $this->ai_notice('error', __('Unknown provider.', 'open-trust-center-by-ettic')); $this->redirect_to_ai_tab(); } - $api_key = OpenTrust_Chat_Secrets::get($provider); + $api_key = Ettic_OTC_Chat_Secrets::get($provider); if ($api_key === null) { - $this->ai_notice('error', __('No key on file for this provider.', 'opentrust')); + $this->ai_notice('error', __('No key on file for this provider.', 'open-trust-center-by-ettic')); $this->redirect_to_ai_tab(); } $result = $this->refresh_provider_cache($provider, $api_key); if (empty($result['ok'])) { - $error = $result['error'] ?? __('Refresh failed.', 'opentrust'); + $error = $result['error'] ?? __('Refresh failed.', 'open-trust-center-by-ettic'); /* translators: %s: error message from the provider */ - $this->ai_notice('error', sprintf(__('Refresh failed: %s', 'opentrust'), $error)); + $this->ai_notice('error', sprintf(__('Refresh failed: %s', 'open-trust-center-by-ettic'), $error)); $this->redirect_to_ai_tab(); } - $settings = OpenTrust::get_settings(); + $settings = Ettic_OTC::get_settings(); $settings['ai_model_list_cached_at'] = time(); $this->snapshot_active_model($settings, $result['models']); - OpenTrust_Admin_Settings::instance()->save_settings_raw($settings); + Ettic_OTC_Admin_Settings::instance()->save_settings_raw($settings); /* translators: %d: number of models */ - $this->ai_notice('success', sprintf(__('Model list refreshed. Found %d model(s).', 'opentrust'), count($result['models']))); + $this->ai_notice('success', sprintf(__('Model list refreshed. Found %d model(s).', 'open-trust-center-by-ettic'), count($result['models']))); $this->redirect_to_ai_tab(); } @@ -793,17 +793,17 @@ public function handle_ai_refresh_models(): void { */ public function handle_ai_summarize_sweep(): void { if (!current_user_can('manage_options')) { - wp_die(esc_html__('You do not have permission to perform this action.', 'opentrust'), '', ['response' => 403]); + wp_die(esc_html__('You do not have permission to perform this action.', 'open-trust-center-by-ettic'), '', ['response' => 403]); } - check_admin_referer('opentrust_ai_summarize_sweep'); + check_admin_referer('ettic_otc_ai_summarize_sweep'); $count = 0; - if (class_exists('OpenTrust_Chat_Summarizer')) { - $count = OpenTrust_Chat_Summarizer::sweep_all(); + if (class_exists('Ettic_OTC_Chat_Summarizer')) { + $count = Ettic_OTC_Chat_Summarizer::sweep_all(); } set_transient( - 'opentrust_ai_notice_' . get_current_user_id(), + 'ettic_otc_ai_notice_' . get_current_user_id(), [ 'type' => 'success', 'message' => $count > 0 @@ -813,28 +813,28 @@ public function handle_ai_summarize_sweep(): void { 'Queued %d policy for AI summary generation. Summaries will appear over the next minute.', 'Queued %d policies for AI summary generation. Summaries will appear over the next few minutes.', $count, - 'opentrust' + 'open-trust-center-by-ettic' ), (int) $count ) - : __('All policies already have up-to-date AI summaries.', 'opentrust'), + : __('All policies already have up-to-date AI summaries.', 'open-trust-center-by-ettic'), ], MINUTE_IN_SECONDS ); - wp_safe_redirect(admin_url('admin.php?page=opentrust&tab=ai')); + wp_safe_redirect(admin_url('admin.php?page=ettic-otc&tab=ai')); exit; } private function ai_notice(string $type, string $message): void { set_transient( - 'opentrust_ai_notice_' . get_current_user_id(), + 'ettic_otc_ai_notice_' . get_current_user_id(), ['type' => $type, 'message' => $message], MINUTE_IN_SECONDS ); } private function redirect_to_ai_tab(): never { - wp_safe_redirect(admin_url('admin.php?page=opentrust&tab=ai')); + wp_safe_redirect(admin_url('admin.php?page=ettic-otc&tab=ai')); exit; } @@ -862,15 +862,15 @@ public static function unschedule_cron(): void { * others. */ public static function cron_refresh_all_providers(): void { - $stored_keys = OpenTrust_Chat_Secrets::get_all(); + $stored_keys = Ettic_OTC_Chat_Secrets::get_all(); if (empty($stored_keys)) { return; } $self = self::instance(); - $settings = OpenTrust::get_settings(); + $settings = Ettic_OTC::get_settings(); $active = (string) ($settings['ai_provider'] ?? ''); - $log = static fn(string $slug, string $why) => OpenTrust::debug_log("AI model refresh failed for {$slug}: {$why}"); + $log = static fn(string $slug, string $why) => Ettic_OTC::debug_log("AI model refresh failed for {$slug}: {$why}"); $dirty = false; foreach ($stored_keys as $slug => $api_key) { @@ -892,10 +892,10 @@ public static function cron_refresh_all_providers(): void { } if ($dirty) { - OpenTrust_Admin_Settings::instance()->save_settings_raw($settings); + Ettic_OTC_Admin_Settings::instance()->save_settings_raw($settings); } } } // Wire the cron hook at file load so it fires regardless of admin context. -add_action(OpenTrust_Admin_AI::CRON_HOOK, [OpenTrust_Admin_AI::class, 'cron_refresh_all_providers']); +add_action(Ettic_OTC_Admin_AI::CRON_HOOK, [Ettic_OTC_Admin_AI::class, 'cron_refresh_all_providers']); diff --git a/includes/class-opentrust-admin-questions.php b/includes/class-ettic-otc-admin-questions.php similarity index 69% rename from includes/class-opentrust-admin-questions.php rename to includes/class-ettic-otc-admin-questions.php index 29580c9..7c689f2 100644 --- a/includes/class-opentrust-admin-questions.php +++ b/includes/class-ettic-otc-admin-questions.php @@ -4,13 +4,13 @@ * admin-post handlers that drive its toolbar (CSV export, full clear, * logging toggle). * - * Lives on its own submenu under the OpenTrust top-level menu. Visibility - * of that submenu is gated in OpenTrust_Admin::register_menu() on the + * Lives on its own submenu under the Ettic_OTC top-level menu. Visibility + * of that submenu is gated in Ettic_OTC_Admin::register_menu() on the * `ai_enabled` setting. Once the submenu is registered, this class owns * the page render and all handler endpoints. * * Identifiers in the underlying log table are pre-hashed by - * OpenTrust_Chat_Log; nothing in this screen surfaces raw IPs/sessions. + * Ettic_OTC_Chat_Log; nothing in this screen surfaces raw IPs/sessions. */ declare(strict_types=1); @@ -19,7 +19,7 @@ exit; } -final class OpenTrust_Admin_Questions { +final class Ettic_OTC_Admin_Questions { private static ?self $instance = null; @@ -28,9 +28,9 @@ public static function instance(): self { } private function __construct() { - add_action('admin_post_opentrust_ai_questions_export', [$this, 'handle_export']); - add_action('admin_post_opentrust_ai_questions_clear', [$this, 'handle_clear']); - add_action('admin_post_opentrust_ai_toggle_logging', [$this, 'handle_toggle_logging']); + add_action('admin_post_ettic_otc_ai_questions_export', [$this, 'handle_export']); + add_action('admin_post_ettic_otc_ai_questions_clear', [$this, 'handle_clear']); + add_action('admin_post_ettic_otc_ai_toggle_logging', [$this, 'handle_toggle_logging']); } // ────────────────────────────────────────────── @@ -42,7 +42,7 @@ public function render_page(): void { return; } - $settings = OpenTrust::get_settings(); + $settings = Ettic_OTC::get_settings(); // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only filter params on admin display page. $filters = [ @@ -55,105 +55,107 @@ public function render_page(): void { ]; // phpcs:enable WordPress.Security.NonceVerification.Recommended - $result = OpenTrust_Chat_Log::query($filters); + $result = Ettic_OTC_Chat_Log::query($filters); $total = $result['total']; $rows = $result['rows']; $pages = max(1, (int) ceil($total / $filters['per_page'])); - $models = OpenTrust_Chat_Log::distinct_models(); - $counts = OpenTrust_Chat_Log::total_count(); - - $export_url = wp_nonce_url( - admin_url('admin-post.php?action=opentrust_ai_questions_export&' . http_build_query(array_filter($filters + ['paged' => 0]))), - 'opentrust_ai_questions_export' + $models = Ettic_OTC_Chat_Log::distinct_models(); + $counts = Ettic_OTC_Chat_Log::total_count(); + + $export_url = self::build_export_url( + (string) $filters['search'], + (string) $filters['model'], + (string) $filters['date_from'], + (string) $filters['date_to'] ); $clear_url = wp_nonce_url( - admin_url('admin-post.php?action=opentrust_ai_questions_clear'), - 'opentrust_ai_questions_clear' + admin_url('admin-post.php?action=ettic_otc_ai_questions_clear'), + 'ettic_otc_ai_questions_clear' ); $toggle_url = wp_nonce_url( - admin_url('admin-post.php?action=opentrust_ai_toggle_logging'), - 'opentrust_ai_toggle_logging' + admin_url('admin-post.php?action=ettic_otc_ai_toggle_logging'), + 'ettic_otc_ai_toggle_logging' ); - $notice = get_transient('opentrust_ai_notice_' . get_current_user_id()); + $notice = get_transient('ettic_otc_ai_notice_' . get_current_user_id()); if (is_array($notice)) { - delete_transient('opentrust_ai_notice_' . get_current_user_id()); + delete_transient('ettic_otc_ai_notice_' . get_current_user_id()); $class = $notice['type'] === 'error' ? 'notice-error' : 'notice-success'; printf('

%s

', esc_attr($class), esc_html((string) $notice['message'])); } ?>
-

+

- +

- +
- - + +
- +
- +
- +
- - - + + +
- - - - - - + + + + + + - + refused ? 'background:#fef9c3' : ''; @@ -162,7 +164,7 @@ public function render_page(): void { @@ -181,8 +183,13 @@ public function render_page(): void {
created_at . ' UTC'))); ?> refused): ?> - + question); ?>
1): - $base = add_query_arg($filters + ['page' => 'opentrust-questions'], admin_url('admin.php')); - $base = remove_query_arg('paged', $base); + $base = add_query_arg([ + 'page' => 'ettic-otc-questions', + 'q' => $filters['search'], + 'model' => $filters['model'], + 'date_from' => $filters['date_from'], + 'date_to' => $filters['date_to'], + ], admin_url('admin.php')); ?>
@@ -206,26 +213,46 @@ public function render_page(): void {
-

-

- +

+

+

'ettic_otc_ai_questions_export', + 'q' => $search, + 'model' => $model, + 'date_from' => $date_from, + 'date_to' => $date_to, + ], static fn($v): bool => $v !== ''); + return wp_nonce_url( + admin_url('admin-post.php?' . http_build_query($params)), + 'ettic_otc_ai_questions_export' + ); + } + // ────────────────────────────────────────────── // admin-post handlers // ────────────────────────────────────────────── public function handle_export(): void { if (!current_user_can('manage_options')) { - wp_die(esc_html__('You do not have permission to perform this action.', 'opentrust'), '', ['response' => 403]); + wp_die(esc_html__('You do not have permission to perform this action.', 'open-trust-center-by-ettic'), '', ['response' => 403]); } - check_admin_referer('opentrust_ai_questions_export'); + check_admin_referer('ettic_otc_ai_questions_export'); $filters = [ - 'search' => isset($_GET['search']) ? sanitize_text_field((string) wp_unslash($_GET['search'])) : '', + 'search' => isset($_GET['q']) ? sanitize_text_field((string) wp_unslash($_GET['q'])) : '', 'model' => isset($_GET['model']) ? sanitize_text_field((string) wp_unslash($_GET['model'])) : '', 'date_from' => isset($_GET['date_from']) ? sanitize_text_field((string) wp_unslash($_GET['date_from'])) : '', 'date_to' => isset($_GET['date_to']) ? sanitize_text_field((string) wp_unslash($_GET['date_to'])) : '', @@ -233,10 +260,10 @@ public function handle_export(): void { 'per_page' => 10000, // hard cap — nobody exports >10k rows per page ]; - $result = OpenTrust_Chat_Log::query($filters); + $result = Ettic_OTC_Chat_Log::query($filters); header('Content-Type: text/csv; charset=utf-8'); - header('Content-Disposition: attachment; filename=opentrust-questions-' . gmdate('Y-m-d') . '.csv'); + header('Content-Disposition: attachment; filename=ettic-otc-questions-' . gmdate('Y-m-d') . '.csv'); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Writing to php://output, not filesystem $out = fopen('php://output', 'w'); @@ -261,37 +288,37 @@ public function handle_export(): void { public function handle_clear(): void { if (!current_user_can('manage_options')) { - wp_die(esc_html__('You do not have permission to perform this action.', 'opentrust'), '', ['response' => 403]); + wp_die(esc_html__('You do not have permission to perform this action.', 'open-trust-center-by-ettic'), '', ['response' => 403]); } - check_admin_referer('opentrust_ai_questions_clear'); + check_admin_referer('ettic_otc_ai_questions_clear'); - OpenTrust_Chat_Log::clear_all(); + Ettic_OTC_Chat_Log::clear_all(); set_transient( - 'opentrust_ai_notice_' . get_current_user_id(), - ['type' => 'success', 'message' => __('Question log cleared.', 'opentrust')], + 'ettic_otc_ai_notice_' . get_current_user_id(), + ['type' => 'success', 'message' => __('Question log cleared.', 'open-trust-center-by-ettic')], MINUTE_IN_SECONDS ); - wp_safe_redirect(admin_url('admin.php?page=opentrust-questions')); + wp_safe_redirect(admin_url('admin.php?page=ettic-otc-questions')); exit; } public function handle_toggle_logging(): void { if (!current_user_can('manage_options')) { - wp_die(esc_html__('You do not have permission to perform this action.', 'opentrust'), '', ['response' => 403]); + wp_die(esc_html__('You do not have permission to perform this action.', 'open-trust-center-by-ettic'), '', ['response' => 403]); } - check_admin_referer('opentrust_ai_toggle_logging'); + check_admin_referer('ettic_otc_ai_toggle_logging'); - $settings = OpenTrust::get_settings(); + $settings = Ettic_OTC::get_settings(); $settings['ai_logging_enabled'] = empty($settings['ai_logging_enabled']); - OpenTrust_Admin_Settings::instance()->save_settings_raw($settings); + Ettic_OTC_Admin_Settings::instance()->save_settings_raw($settings); set_transient( - 'opentrust_ai_notice_' . get_current_user_id(), - ['type' => 'success', 'message' => $settings['ai_logging_enabled'] ? __('Logging enabled.', 'opentrust') : __('Logging disabled.', 'opentrust')], + 'ettic_otc_ai_notice_' . get_current_user_id(), + ['type' => 'success', 'message' => $settings['ai_logging_enabled'] ? __('Logging enabled.', 'open-trust-center-by-ettic') : __('Logging disabled.', 'open-trust-center-by-ettic')], MINUTE_IN_SECONDS ); - wp_safe_redirect(admin_url('admin.php?page=opentrust-questions')); + wp_safe_redirect(admin_url('admin.php?page=ettic-otc-questions')); exit; } } diff --git a/includes/class-opentrust-admin-review.php b/includes/class-ettic-otc-admin-review.php similarity index 80% rename from includes/class-opentrust-admin-review.php rename to includes/class-ettic-otc-admin-review.php index 7a0558a..a9e89cc 100644 --- a/includes/class-opentrust-admin-review.php +++ b/includes/class-ettic-otc-admin-review.php @@ -4,7 +4,7 @@ * * A small, opt-out-able nudge asking long-time admins to leave a rating on * the WordPress.org plugin directory. Two surfaces, both scoped strictly to - * OpenTrust admin screens (never the dashboard, plugin list, or other + * Ettic_OTC admin screens (never the dashboard, plugin list, or other * plugins' pages): * * 1. Footer text — replaces the default "Thank you for creating with @@ -28,11 +28,11 @@ exit; } -final class OpenTrust_Admin_Review { +final class Ettic_OTC_Admin_Review { - private const DISMISS_META_KEY = '_opentrust_review_dismissed_v1'; - private const FIRST_SEEN_OPTION = 'opentrust_first_activated_at'; - private const ACTION = 'opentrust_dismiss_review_notice'; + private const DISMISS_META_KEY = '_ettic_otc_review_dismissed_v1'; + private const FIRST_SEEN_OPTION = 'ettic_otc_first_activated_at'; + private const ACTION = 'ettic_otc_dismiss_review_notice'; private const POLICY_THRESHOLD = 3; private const DAYS_THRESHOLD = 14; private const SNOOZE_DAYS = 30; @@ -71,14 +71,14 @@ public function capture_first_seen(): void { // ────────────────────────────────────────────── public function footer_text(string $text): string { - if (!self::is_opentrust_screen()) { + if (!self::is_ettic_otc_screen()) { return $text; } $link = sprintf( '%s', esc_url(self::review_url()), - esc_html__('review on WordPress.org', 'opentrust') + esc_html__('review on WordPress.org', 'open-trust-center-by-ettic') ); // The static English string contains no HTML; the only insertion is @@ -86,7 +86,7 @@ public function footer_text(string $text): string { // the result — same risk model as core's admin_footer_text usage. return sprintf( /* translators: %s: link to the WordPress.org reviews page */ - __('OpenTrust is built and maintained in the open. If it is helping your team, a %s keeps the project moving.', 'opentrust'), + __('Open Trust Center by Ettic is built and maintained in the open. If it is helping your team, a %s keeps the project moving.', 'open-trust-center-by-ettic'), $link ); } @@ -99,7 +99,7 @@ public function render_milestone_notice(): void { if (!current_user_can('manage_options')) { return; } - if (!self::is_opentrust_screen()) { + if (!self::is_ettic_otc_screen()) { return; } if (!$this->should_show_milestone_notice()) { @@ -110,20 +110,20 @@ public function render_milestone_notice(): void { $not_now_url = self::dismiss_url('snooze'); $already_url = self::dismiss_url('permanent'); ?> -
+ @@ -135,7 +135,7 @@ public function render_milestone_notice(): void { * edits, and a per-user dismissal check. */ private function should_show_milestone_notice(): bool { - if (!apply_filters('opentrust_show_review_notice', true)) { + if (!apply_filters('ettic_otc_show_review_notice', true)) { return false; } @@ -152,7 +152,7 @@ private function should_show_milestone_notice(): bool { return false; } - $counts = wp_count_posts(OpenTrust_CPT::POLICY); + $counts = wp_count_posts(Ettic_OTC_CPT::POLICY); $published = (int) ($counts->publish ?? 0); return $published >= self::POLICY_THRESHOLD; @@ -165,7 +165,7 @@ private function should_show_milestone_notice(): bool { public function handle_dismiss(): void { if (!current_user_can('manage_options')) { wp_die( - esc_html__('You do not have permission to dismiss this notice.', 'opentrust'), + esc_html__('You do not have permission to dismiss this notice.', 'open-trust-center-by-ettic'), '', ['response' => 403] ); @@ -181,7 +181,7 @@ public function handle_dismiss(): void { update_user_meta(get_current_user_id(), self::DISMISS_META_KEY, $value); $referer = wp_get_referer(); - wp_safe_redirect($referer ?: admin_url('admin.php?page=opentrust')); + wp_safe_redirect($referer ?: admin_url('admin.php?page=ettic-otc')); exit; } @@ -191,10 +191,10 @@ public function handle_dismiss(): void { /** * Match the scoping rule used by render_plain_permalinks_notice in - * OpenTrust_Admin: any screen whose id contains "opentrust" plus the + * Ettic_OTC_Admin: any screen whose id contains "open-trust-center-by-ettic" plus the * five content CPTs. Identical pattern keeps both notices in lockstep. */ - private static function is_opentrust_screen(): bool { + private static function is_ettic_otc_screen(): bool { if (!function_exists('get_current_screen')) { return false; } @@ -203,16 +203,16 @@ private static function is_opentrust_screen(): bool { return false; } - return str_contains((string) $screen->id, 'opentrust') - || in_array($screen->post_type, OpenTrust_CPT::CORPUS, true); + return str_contains((string) $screen->id, 'open-trust-center-by-ettic') + || in_array($screen->post_type, Ettic_OTC_CPT::CORPUS, true); } private static function review_url(): string { // Neutral reviews index — wp.org Detailed Plugin Guideline 9 forbids // linking to a pre-rated/filtered review surface (e.g. ?rate=5#new-post), // which hides constructive feedback and pressures positive ratings. - $url = 'https://wordpress.org/support/plugin/opentrust/reviews/'; - return (string) apply_filters('opentrust_review_url', $url); + $url = 'https://wordpress.org/support/plugin/open-trust-center-by-ettic/reviews/'; + return (string) apply_filters('ettic_otc_review_url', $url); } private static function dismiss_url(string $type): string { diff --git a/includes/class-opentrust-admin-settings.php b/includes/class-ettic-otc-admin-settings.php similarity index 67% rename from includes/class-opentrust-admin-settings.php rename to includes/class-ettic-otc-admin-settings.php index 7b300fa..032b105 100644 --- a/includes/class-opentrust-admin-settings.php +++ b/includes/class-ettic-otc-admin-settings.php @@ -3,15 +3,15 @@ * Settings API registration, field rendering, sanitization, and the * settings-page wrapper that hosts the General / Contact / AI tabs. * - * Owns the entire WordPress Settings API surface for OpenTrust: + * Owns the entire WordPress Settings API surface for Ettic_OTC: * register_setting() with a sanitize_callback, every add_settings_section * and add_settings_field call, the eight per-type field renderers, and * the schema-driven sanitize cascade that keeps cross-tab saves shape- * stable. * - * Bootstrapped by OpenTrust_Admin's constructor; subscribes its own + * Bootstrapped by Ettic_OTC_Admin's constructor; subscribes its own * admin_init hook for register_settings(). The settings menu page in - * OpenTrust_Admin::register_menu() points its callback directly at this + * Ettic_OTC_Admin::register_menu() points its callback directly at this * class's render_settings_page(). * * Also owns save_settings_raw() — the skip-sanitize writer used by the @@ -26,7 +26,7 @@ exit; } -final class OpenTrust_Admin_Settings { +final class Ettic_OTC_Admin_Settings { private static ?self $instance = null; @@ -43,109 +43,109 @@ private function __construct() { // ────────────────────────────────────────────── public function register_settings(): void { - register_setting('opentrust_settings_group', 'opentrust_settings', [ + register_setting('ettic_otc_settings_group', 'ettic_otc_settings', [ 'type' => 'array', 'sanitize_callback' => [$this, 'sanitize_settings'], - 'default' => OpenTrust::defaults(), + 'default' => Ettic_OTC::defaults(), ]); // ── General tab ────────────────────────────────────────────── add_settings_section( - 'opentrust_general', - __('General Settings', 'opentrust'), + 'ettic_otc_general', + __('General Settings', 'open-trust-center-by-ettic'), fn() => null, - 'opentrust-settings-general' + 'ettic-otc-settings-general' ); - $this->add_field('endpoint_slug', __('Endpoint Slug', 'opentrust'), 'render_text_field', 'opentrust_general', 'opentrust-settings-general', [ - 'description' => __('The URL path for your trust center (e.g., "trust-center" = yoursite.com/trust-center/).', 'opentrust'), + $this->add_field('endpoint_slug', __('Endpoint Slug', 'open-trust-center-by-ettic'), 'render_text_field', 'ettic_otc_general', 'ettic-otc-settings-general', [ + 'description' => __('The URL path for your trust center (e.g., "trust-center" = yoursite.com/trust-center/).', 'open-trust-center-by-ettic'), ]); - $this->add_field('page_title', __('Page Title', 'opentrust'), 'render_text_field', 'opentrust_general', 'opentrust-settings-general'); + $this->add_field('page_title', __('Page Title', 'open-trust-center-by-ettic'), 'render_text_field', 'ettic_otc_general', 'ettic-otc-settings-general'); - $this->add_field('company_name', __('Company Name', 'opentrust'), 'render_text_field', 'opentrust_general', 'opentrust-settings-general'); + $this->add_field('company_name', __('Company Name', 'open-trust-center-by-ettic'), 'render_text_field', 'ettic_otc_general', 'ettic-otc-settings-general'); - $this->add_field('tagline', __('Tagline', 'opentrust'), 'render_textarea_field', 'opentrust_general', 'opentrust-settings-general', [ - 'description' => __('A short description displayed below the company name in the hero section.', 'opentrust'), + $this->add_field('tagline', __('Tagline', 'open-trust-center-by-ettic'), 'render_textarea_field', 'ettic_otc_general', 'ettic-otc-settings-general', [ + 'description' => __('A short description displayed below the company name in the hero section.', 'open-trust-center-by-ettic'), ]); // Branding section (General tab). add_settings_section( - 'opentrust_branding', - __('Branding', 'opentrust'), + 'ettic_otc_branding', + __('Branding', 'open-trust-center-by-ettic'), fn() => null, - 'opentrust-settings-general' + 'ettic-otc-settings-general' ); - $this->add_field('logo_id', __('Logo', 'opentrust'), 'render_logo_field', 'opentrust_branding', 'opentrust-settings-general'); - $this->add_field('avatar_id', __('AI Avatar', 'opentrust'), 'render_avatar_field', 'opentrust_branding', 'opentrust-settings-general'); + $this->add_field('logo_id', __('Logo', 'open-trust-center-by-ettic'), 'render_logo_field', 'ettic_otc_branding', 'ettic-otc-settings-general'); + $this->add_field('avatar_id', __('AI Avatar', 'open-trust-center-by-ettic'), 'render_avatar_field', 'ettic_otc_branding', 'ettic-otc-settings-general'); - $this->add_field('accent_color', __('Accent Color', 'opentrust'), 'render_color_field', 'opentrust_branding', 'opentrust-settings-general', [ - 'description' => __('Used for buttons, links, and highlights. Choose a color that matches your brand.', 'opentrust'), + $this->add_field('accent_color', __('Accent Color', 'open-trust-center-by-ettic'), 'render_color_field', 'ettic_otc_branding', 'ettic-otc-settings-general', [ + 'description' => __('Used for buttons, links, and highlights. Choose a color that matches your brand.', 'open-trust-center-by-ettic'), ]); - $this->add_field('show_powered_by', __('Credit Link', 'opentrust'), 'render_show_powered_by_field', 'opentrust_branding', 'opentrust-settings-general'); + $this->add_field('show_powered_by', __('Credit Link', 'open-trust-center-by-ettic'), 'render_show_powered_by_field', 'ettic_otc_branding', 'ettic-otc-settings-general'); // Sections visibility (General tab). add_settings_section( - 'opentrust_sections', - __('Visible Sections', 'opentrust'), - fn() => print('

' . esc_html__('Choose which sections to display on the trust center.', 'opentrust') . '

'), - 'opentrust-settings-general' + 'ettic_otc_sections', + __('Visible Sections', 'open-trust-center-by-ettic'), + fn() => print('

' . esc_html__('Choose which sections to display on the trust center.', 'open-trust-center-by-ettic') . '

'), + 'ettic-otc-settings-general' ); - $this->add_field('sections_visible', __('Sections', 'opentrust'), 'render_sections_field', 'opentrust_sections', 'opentrust-settings-general'); + $this->add_field('sections_visible', __('Sections', 'open-trust-center-by-ettic'), 'render_sections_field', 'ettic_otc_sections', 'ettic-otc-settings-general'); // ── Contact tab ────────────────────────────────────────────── // Fields are optional — the frontend block renders only when at least one field below is populated. add_settings_section( - 'opentrust_contact', - __('Get in touch', 'opentrust'), - fn() => print('

' . esc_html__('Publish a dark-accent "Get in touch" block on the trust center. Every field is optional — the block only appears if at least one is filled in.', 'opentrust') . '

'), - 'opentrust-settings-contact' + 'ettic_otc_contact', + __('Get in touch', 'open-trust-center-by-ettic'), + fn() => print('

' . esc_html__('Publish a dark-accent "Get in touch" block on the trust center. Every field is optional — the block only appears if at least one is filled in.', 'open-trust-center-by-ettic') . '

'), + 'ettic-otc-settings-contact' ); - $this->add_field('company_description', __('Company Description', 'opentrust'), 'render_textarea_field', 'opentrust_contact', 'opentrust-settings-contact', [ - 'description' => __('Two or three sentences describing what the company does. Rendered under the "Get in touch" section title.', 'opentrust'), + $this->add_field('company_description', __('Company Description', 'open-trust-center-by-ettic'), 'render_textarea_field', 'ettic_otc_contact', 'ettic-otc-settings-contact', [ + 'description' => __('Two or three sentences describing what the company does. Rendered under the "Get in touch" section title.', 'open-trust-center-by-ettic'), ]); - $this->add_field('dpo_name', __('DPO Name', 'opentrust'), 'render_text_field', 'opentrust_contact', 'opentrust-settings-contact', [ - 'description' => __('Data Protection Officer name. Required under GDPR for many organisations.', 'opentrust'), + $this->add_field('dpo_name', __('DPO Name', 'open-trust-center-by-ettic'), 'render_text_field', 'ettic_otc_contact', 'ettic-otc-settings-contact', [ + 'description' => __('Data Protection Officer name. Required under GDPR for many organisations.', 'open-trust-center-by-ettic'), ]); - $this->add_field('dpo_email', __('DPO Email', 'opentrust'), 'render_email_field', 'opentrust_contact', 'opentrust-settings-contact', [ - 'description' => __('Dedicated DPO mailbox. Rendered as a mailto link.', 'opentrust'), + $this->add_field('dpo_email', __('DPO Email', 'open-trust-center-by-ettic'), 'render_email_field', 'ettic_otc_contact', 'ettic-otc-settings-contact', [ + 'description' => __('Dedicated DPO mailbox. Rendered as a mailto link.', 'open-trust-center-by-ettic'), ]); - $this->add_field('security_email', __('Security Contact Email', 'opentrust'), 'render_email_field', 'opentrust_contact', 'opentrust-settings-contact', [ - 'description' => __('For vulnerability reports and security questions. Often separate from the DPO.', 'opentrust'), + $this->add_field('security_email', __('Security Contact Email', 'open-trust-center-by-ettic'), 'render_email_field', 'ettic_otc_contact', 'ettic-otc-settings-contact', [ + 'description' => __('For vulnerability reports and security questions. Often separate from the DPO.', 'open-trust-center-by-ettic'), ]); - $this->add_field('contact_form_url', __('Contact Form URL', 'opentrust'), 'render_url_field', 'opentrust_contact', 'opentrust-settings-contact', [ - 'description' => __('Optional link to a gated contact form.', 'opentrust'), + $this->add_field('contact_form_url', __('Contact Form URL', 'open-trust-center-by-ettic'), 'render_url_field', 'ettic_otc_contact', 'ettic-otc-settings-contact', [ + 'description' => __('Optional link to a gated contact form.', 'open-trust-center-by-ettic'), ]); - $this->add_field('contact_address', __('Mailing Address', 'opentrust'), 'render_textarea_field', 'opentrust_contact', 'opentrust-settings-contact', [ - 'description' => __('Postal address for formal GDPR / legal notices.', 'opentrust'), + $this->add_field('contact_address', __('Mailing Address', 'open-trust-center-by-ettic'), 'render_textarea_field', 'ettic_otc_contact', 'ettic-otc-settings-contact', [ + 'description' => __('Postal address for formal GDPR / legal notices.', 'open-trust-center-by-ettic'), ]); - $this->add_field('pgp_key_url', __('PGP Public Key URL', 'opentrust'), 'render_url_field', 'opentrust_contact', 'opentrust-settings-contact', [ - 'description' => __('Optional link to your security team\'s PGP public key.', 'opentrust'), + $this->add_field('pgp_key_url', __('PGP Public Key URL', 'open-trust-center-by-ettic'), 'render_url_field', 'ettic_otc_contact', 'ettic-otc-settings-contact', [ + 'description' => __('Optional link to your security team\'s PGP public key.', 'open-trust-center-by-ettic'), ]); - $this->add_field('company_registration', __('Company Registration Number', 'opentrust'), 'render_text_field', 'opentrust_contact', 'opentrust-settings-contact', [ - 'description' => __('KvK (NL), Companies House (UK), Handelsregister (DE), EIN (US), or equivalent business registration.', 'opentrust'), + $this->add_field('company_registration', __('Company Registration Number', 'open-trust-center-by-ettic'), 'render_text_field', 'ettic_otc_contact', 'ettic-otc-settings-contact', [ + 'description' => __('KvK (NL), Companies House (UK), Handelsregister (DE), EIN (US), or equivalent business registration.', 'open-trust-center-by-ettic'), ]); - $this->add_field('vat_number', __('VAT / Tax ID', 'opentrust'), 'render_text_field', 'opentrust_contact', 'opentrust-settings-contact', [ - 'description' => __('VAT number, sales-tax ID, or equivalent international tax identifier.', 'opentrust'), + $this->add_field('vat_number', __('VAT / Tax ID', 'open-trust-center-by-ettic'), 'render_text_field', 'ettic_otc_contact', 'ettic-otc-settings-contact', [ + 'description' => __('VAT number, sales-tax ID, or equivalent international tax identifier.', 'open-trust-center-by-ettic'), ]); } - private function add_field(string $key, string $title, string $callback, string $section, string $page = 'opentrust-settings-general', array $extra = []): void { + private function add_field(string $key, string $title, string $callback, string $section, string $page = 'ettic-otc-settings-general', array $extra = []): void { add_settings_field( - 'opentrust_' . $key, + 'ettic_otc_' . $key, $title, [$this, $callback], $page, @@ -184,14 +184,14 @@ public function render_textarea_field(array $args): void { * @param array $extra_attrs */ private function render_input_field(string $type, array $args, array $extra_attrs = []): void { - $settings = OpenTrust::get_settings(); + $settings = Ettic_OTC::get_settings(); $key = $args['key']; $value = $settings[$key] ?? ''; if ($type === 'textarea') { - echo ' +

+
+ post) || $context->post->post_type !== self::POLICY) { + return $allowed; + } + return [ + 'core/paragraph', + 'core/heading', + 'core/list', + 'core/list-item', + 'core/table', + 'core/quote', + 'core/separator', + 'core/image', + 'core/code', + 'core/details', + ]; + } + + // ── Policy meta box (sidebar) ── + + public function render_policy_meta_box(\WP_Post $post): void { + wp_nonce_field('ettic_otc_save_policy', 'ettic_otc_policy_nonce'); + + $ref_id = get_post_meta($post->ID, '_ettic_otc_policy_ref_id', true) ?: ''; + $category = get_post_meta($post->ID, '_ettic_otc_policy_category', true) ?: 'other'; + $effective_date = get_post_meta($post->ID, '_ettic_otc_policy_effective_date', true) ?: ''; + $review_date = get_post_meta($post->ID, '_ettic_otc_policy_review_date', true) ?: ''; + $sort_order = metadata_exists('post', $post->ID, '_ettic_otc_policy_sort_order') + ? (int) get_post_meta($post->ID, '_ettic_otc_policy_sort_order', true) + : 10; + $version = (int) get_post_meta($post->ID, '_ettic_otc_version', true) ?: 1; + + $citations = get_post_meta($post->ID, '_ettic_otc_policy_citations', true); + $citations = is_array($citations) ? $citations : []; + + $attachment_id = (int) get_post_meta($post->ID, '_ettic_otc_policy_attachment_id', true); + $attachment_url = $attachment_id ? wp_get_attachment_url($attachment_id) : ''; + $attachment_name = $attachment_id ? get_the_title($attachment_id) : ''; + + $categories = Ettic_OTC_Render::policy_category_labels(); + ?> +
+

+ +

+

+ +

+
+ + post_status): ?> +
+ + +
+ + +
+ + +

+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ $ot_citation): + $ot_citation_name = is_array($ot_citation) ? ($ot_citation['name'] ?? '') : (string) $ot_citation; + ?> + + + + + + + +
+

+
+ +
+ +
+ + +
+ + + +

+
+ +
+ + +

+
+ ID, '_ettic_otc_sub_purpose', true) ?: ''; + $data_processed = get_post_meta($post->ID, '_ettic_otc_sub_data_processed', true) ?: ''; + $country = get_post_meta($post->ID, '_ettic_otc_sub_country', true) ?: ''; + $website = get_post_meta($post->ID, '_ettic_otc_sub_website', true) ?: ''; + $dpa_signed = (bool) get_post_meta($post->ID, '_ettic_otc_sub_dpa_signed', true); + ?> +
+ + +

+
+ +
+ + +

+
+ +
+ + +
+ +
+ + +
+ +
+ +

+
+ ID, '_ettic_otc_dp_data_items', true); + $data_items = is_array($data_items) ? $data_items : []; + $purpose = get_post_meta($post->ID, '_ettic_otc_dp_purpose', true) ?: ''; + $legal_basis = get_post_meta($post->ID, '_ettic_otc_dp_legal_basis', true) ?: ''; + $retention_period = get_post_meta($post->ID, '_ettic_otc_dp_retention_period', true) ?: ''; + $shared_with = get_post_meta($post->ID, '_ettic_otc_dp_shared_with', true); + $shared_with = is_array($shared_with) ? $shared_with : []; + $sort_order = (int) get_post_meta($post->ID, '_ettic_otc_dp_sort_order', true); + + $prop_collected = (bool) get_post_meta($post->ID, '_ettic_otc_dp_collected', true); + $prop_stored = (bool) get_post_meta($post->ID, '_ettic_otc_dp_stored', true); + $prop_shared = (bool) get_post_meta($post->ID, '_ettic_otc_dp_shared', true); + $prop_sold = (bool) get_post_meta($post->ID, '_ettic_otc_dp_sold', true); + $prop_encrypted = (bool) get_post_meta($post->ID, '_ettic_otc_dp_encrypted', true); + + $basis_options = Ettic_OTC_Render::legal_basis_labels(); + ?> + + +
+ +
+ $item): ?> + + + + + + + +
+
+ + +
+ + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ $entry): ?> + + + + + + + +
+
+ + +
+ +
+ + + + + +
+

+
+ + +
+ + +

+
+ post_type, self::ALL, true)) { + return; + } + if (get_post_meta($post_id, '_ettic_otc_uuid', true)) { + return; + } + update_post_meta($post_id, '_ettic_otc_uuid', wp_generate_uuid4()); + } + + // ────────────────────────────────────────────── + // Save Meta + // ────────────────────────────────────────────── + + public function save_meta(int $post_id, \WP_Post $post): void { + if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { + return; + } + + match ($post->post_type) { + self::CERTIFICATION => $this->save_cert_meta($post_id), + self::POLICY => $this->save_policy_meta($post_id), + self::SUBPROCESSOR => $this->save_sub_meta($post_id), + self::DATA_PRACTICE => $this->save_dp_meta($post_id), + self::FAQ => $this->save_faq_meta($post_id), + default => null, + }; + } + + private function save_cert_meta(int $post_id): void { + if (!isset($_POST['ettic_otc_cert_nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['ettic_otc_cert_nonce'] ) ), 'ettic_otc_save_cert')) { + return; + } + if (!current_user_can('edit_post', $post_id)) { + return; + } + + $valid_types = ['certified', 'compliant']; + $type = sanitize_text_field( wp_unslash( $_POST['ettic_otc_cert_type'] ?? 'compliant' ) ); + update_post_meta($post_id, '_ettic_otc_cert_type', in_array($type, $valid_types, true) ? $type : 'compliant'); + + update_post_meta($post_id, '_ettic_otc_cert_issuing_body', sanitize_text_field( wp_unslash( $_POST['ettic_otc_cert_issuing_body'] ?? '' ) )); + + $valid_statuses = ['active', 'in_progress', 'expired']; + $status = sanitize_text_field( wp_unslash( $_POST['ettic_otc_cert_status'] ?? 'active' ) ); + update_post_meta($post_id, '_ettic_otc_cert_status', in_array($status, $valid_statuses, true) ? $status : 'active'); + + update_post_meta($post_id, '_ettic_otc_cert_issue_date', sanitize_text_field( wp_unslash( $_POST['ettic_otc_cert_issue_date'] ?? '' ) )); + update_post_meta($post_id, '_ettic_otc_cert_expiry_date', sanitize_text_field( wp_unslash( $_POST['ettic_otc_cert_expiry_date'] ?? '' ) )); + update_post_meta($post_id, '_ettic_otc_cert_badge_id', absint( wp_unslash( $_POST['ettic_otc_cert_badge_id'] ?? 0 ) )); + update_post_meta($post_id, '_ettic_otc_cert_artifact_id', absint( wp_unslash( $_POST['ettic_otc_cert_artifact_id'] ?? 0 ) )); + update_post_meta($post_id, '_ettic_otc_cert_description', sanitize_textarea_field( wp_unslash( $_POST['ettic_otc_cert_description'] ?? '' ) )); + } + + private function save_policy_meta(int $post_id): void { + if (!isset($_POST['ettic_otc_policy_nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['ettic_otc_policy_nonce'] ) ), 'ettic_otc_save_policy')) { + return; + } + if (!current_user_can('edit_post', $post_id)) { + return; + } + + // Version bump — only when explicitly requested by the user. + if (!empty($_POST['ettic_otc_publish_new_version'])) { + $post = get_post($post_id); + if ($post && 'publish' === $post->post_status) { + $summary = sanitize_text_field( wp_unslash( $_POST['ettic_otc_version_summary'] ?? '' ) ); + Ettic_OTC_Version::bump_version($post_id, $summary); + } + } + + // Ensure first-publish posts get v1. + Ettic_OTC_Version::ensure_initial_version($post_id); + + $ref_id = sanitize_text_field( wp_unslash( $_POST['ettic_otc_policy_ref_id'] ?? '' ) ); + // Collapse internal whitespace runs so "POL 012" becomes "POL 012" on save. + $ref_id = trim((string) preg_replace('/\s+/u', ' ', $ref_id)); + if ($ref_id !== '') { + update_post_meta($post_id, '_ettic_otc_policy_ref_id', $ref_id); + } else { + delete_post_meta($post_id, '_ettic_otc_policy_ref_id'); + } + + $valid_categories = ['security', 'privacy', 'compliance', 'operational', 'other']; + $category = sanitize_text_field( wp_unslash( $_POST['ettic_otc_policy_category'] ?? 'other' ) ); + update_post_meta($post_id, '_ettic_otc_policy_category', in_array($category, $valid_categories, true) ? $category : 'other'); + + update_post_meta($post_id, '_ettic_otc_policy_effective_date', sanitize_text_field( wp_unslash( $_POST['ettic_otc_policy_effective_date'] ?? '' ) )); + update_post_meta($post_id, '_ettic_otc_policy_review_date', sanitize_text_field( wp_unslash( $_POST['ettic_otc_policy_review_date'] ?? '' ) )); + update_post_meta($post_id, '_ettic_otc_policy_sort_order', absint( wp_unslash( $_POST['ettic_otc_policy_sort_order'] ?? 0 ) )); + + // Framework citations — repeater array, shape mirrors ettic_otc_dp_data_items. + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Each element is individually sanitized below. + $raw_citations = wp_unslash( $_POST['ettic_otc_policy_citations'] ?? [] ); + $citations = []; + if (is_array($raw_citations)) { + foreach ($raw_citations as $entry) { + $name = sanitize_text_field(is_array($entry) ? ($entry['name'] ?? '') : (string) $entry); + $name = trim((string) preg_replace('/\s+/u', ' ', $name)); + if ($name !== '') { + $citations[] = ['name' => $name]; + } + } + } + if (!empty($citations)) { + update_post_meta($post_id, '_ettic_otc_policy_citations', $citations); + } else { + delete_post_meta($post_id, '_ettic_otc_policy_citations'); + } + + // PDF attachment — only accept a real attachment the user can read. + $attachment_id = absint( wp_unslash( $_POST['ettic_otc_policy_attachment_id'] ?? 0 ) ); + if ($attachment_id > 0 && get_post_type($attachment_id) === 'attachment') { + update_post_meta($post_id, '_ettic_otc_policy_attachment_id', $attachment_id); + } else { + delete_post_meta($post_id, '_ettic_otc_policy_attachment_id'); + } + } + + private function save_sub_meta(int $post_id): void { + if (!isset($_POST['ettic_otc_sub_nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['ettic_otc_sub_nonce'] ) ), 'ettic_otc_save_sub')) { + return; + } + if (!current_user_can('edit_post', $post_id)) { + return; + } + + update_post_meta($post_id, '_ettic_otc_sub_purpose', sanitize_textarea_field( wp_unslash( $_POST['ettic_otc_sub_purpose'] ?? '' ) )); + update_post_meta($post_id, '_ettic_otc_sub_data_processed', sanitize_textarea_field( wp_unslash( $_POST['ettic_otc_sub_data_processed'] ?? '' ) )); + update_post_meta($post_id, '_ettic_otc_sub_country', sanitize_text_field( wp_unslash( $_POST['ettic_otc_sub_country'] ?? '' ) )); + update_post_meta($post_id, '_ettic_otc_sub_website', esc_url_raw( wp_unslash( $_POST['ettic_otc_sub_website'] ?? '' ) )); + update_post_meta($post_id, '_ettic_otc_sub_dpa_signed', !empty($_POST['ettic_otc_sub_dpa_signed'])); + } + + private function save_dp_meta(int $post_id): void { + if (!isset($_POST['ettic_otc_dp_nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['ettic_otc_dp_nonce'] ) ), 'ettic_otc_save_dp')) { + return; + } + if (!current_user_can('edit_post', $post_id)) { + return; + } + + // Data Items (repeater array). + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Each element is individually sanitized below. + $raw_items = wp_unslash( $_POST['ettic_otc_dp_data_items'] ?? [] ); + $data_items = []; + if (is_array($raw_items)) { + foreach ($raw_items as $item) { + $name = sanitize_text_field($item['name'] ?? ''); + if ($name !== '') { + $data_items[] = ['name' => $name]; + } + } + } + update_post_meta($post_id, '_ettic_otc_dp_data_items', $data_items); + + // Purpose. + update_post_meta($post_id, '_ettic_otc_dp_purpose', sanitize_textarea_field( wp_unslash( $_POST['ettic_otc_dp_purpose'] ?? '' ) )); + + // Legal Basis. + $valid_bases = ['consent', 'contract', 'legitimate_interest', 'legal_obligation', 'vital_interest', 'public_interest']; + $basis = sanitize_text_field( wp_unslash( $_POST['ettic_otc_dp_legal_basis'] ?? '' ) ); + update_post_meta($post_id, '_ettic_otc_dp_legal_basis', in_array($basis, $valid_bases, true) ? $basis : ''); + + // Retention Period. + update_post_meta($post_id, '_ettic_otc_dp_retention_period', sanitize_text_field( wp_unslash( $_POST['ettic_otc_dp_retention_period'] ?? '' ) )); + + // Shared With (repeater array). + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Each element is individually sanitized below. + $raw_shared = wp_unslash( $_POST['ettic_otc_dp_shared_with'] ?? [] ); + $shared_items = []; + if (is_array($raw_shared)) { + foreach ($raw_shared as $entry) { + $name = sanitize_text_field($entry['name'] ?? ''); + if ($name !== '') { + $shared_items[] = ['name' => $name]; + } + } + } + update_post_meta($post_id, '_ettic_otc_dp_shared_with', $shared_items); + + // Sort order. + update_post_meta($post_id, '_ettic_otc_dp_sort_order', absint( wp_unslash( $_POST['ettic_otc_dp_sort_order'] ?? 0 ) )); + + // Property flags — the AI assistant reports these verbatim. Unchecked + // means explicit "No", not "unknown", so we always write the value. + update_post_meta($post_id, '_ettic_otc_dp_collected', !empty($_POST['ettic_otc_dp_collected'])); + update_post_meta($post_id, '_ettic_otc_dp_stored', !empty($_POST['ettic_otc_dp_stored'])); + update_post_meta($post_id, '_ettic_otc_dp_shared', !empty($_POST['ettic_otc_dp_shared'])); + update_post_meta($post_id, '_ettic_otc_dp_sold', !empty($_POST['ettic_otc_dp_sold'])); + update_post_meta($post_id, '_ettic_otc_dp_encrypted', !empty($_POST['ettic_otc_dp_encrypted'])); + + // Clean up legacy meta keys. + delete_post_meta($post_id, '_ettic_otc_dp_data_type'); + delete_post_meta($post_id, '_ettic_otc_dp_collection_method'); + delete_post_meta($post_id, '_ettic_otc_dp_is_sensitive'); + } + + // ────────────────────────────────────────────── + // Admin Columns + // ────────────────────────────────────────────── + + // Certifications + public function cert_columns(array $columns): array { + $new = []; + $new['cb'] = $columns['cb']; + $new['title'] = $columns['title']; + $new['ettic_otc_issuing_body'] = __('Issuing Body', 'open-trust-center-by-ettic'); + $new['ettic_otc_status'] = __('Status', 'open-trust-center-by-ettic'); + $new['ettic_otc_expiry'] = __('Expiry Date', 'open-trust-center-by-ettic'); + $new['date'] = $columns['date']; + return $new; + } + + public function cert_column_content(string $column, int $post_id): void { + match ($column) { + 'ettic_otc_issuing_body' => print(esc_html(get_post_meta($post_id, '_ettic_otc_cert_issuing_body', true) ?: '—')), + 'ettic_otc_status' => (function () use ($post_id): void { + $status = get_post_meta($post_id, '_ettic_otc_cert_status', true) ?: 'active'; + $type = get_post_meta($post_id, '_ettic_otc_cert_type', true) ?: 'compliant'; + $labels = $type === 'compliant' + ? Ettic_OTC_Render::cert_aligned_status_labels() + : Ettic_OTC_Render::cert_status_labels(); + $swatch = match ($status) { + 'active' => 'background:#dcfce7;color:#166534', + 'in_progress' => 'background:#fef9c3;color:#854d0e', + 'expired' => 'background:#f3f4f6;color:#6b7280', + default => '', + }; + printf( + '%3$s', + esc_attr($status), + esc_attr($swatch), + esc_html($labels[$status] ?? '') + ); + })(), + 'ettic_otc_expiry' => print(esc_html(get_post_meta($post_id, '_ettic_otc_cert_expiry_date', true) ?: '—')), + default => null, + }; + } + + // Policies + public function policy_columns(array $columns): array { + $new = []; + $new['cb'] = $columns['cb']; + $new['title'] = $columns['title']; + $new['ettic_otc_ref_id'] = __('ID', 'open-trust-center-by-ettic'); + $new['ettic_otc_category'] = __('Category', 'open-trust-center-by-ettic'); + $new['ettic_otc_version'] = __('Version', 'open-trust-center-by-ettic'); + $new['ettic_otc_pdf'] = __('PDF', 'open-trust-center-by-ettic'); + $new['date'] = $columns['date']; + return $new; + } + + public function policy_column_content(string $column, int $post_id): void { + // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Match arms emit either hard-coded HTML or values already passed through esc_html(); PHPCS misreads the IIFE/match-expression syntax. + match ($column) { + 'ettic_otc_ref_id' => (function () use ($post_id): void { + $ref = (string) get_post_meta($post_id, '_ettic_otc_policy_ref_id', true); + if ($ref === '') { + print ''; + return; + } + printf('%s', esc_html($ref)); + })(), + 'ettic_otc_category' => print(esc_html(Ettic_OTC_Render::policy_category_labels()[get_post_meta($post_id, '_ettic_otc_policy_category', true) ?: 'other'] ?? '')), + 'ettic_otc_version' => printf('v%s', esc_html((string) ((int) get_post_meta($post_id, '_ettic_otc_version', true) ?: 1))), + 'ettic_otc_pdf' => print(((int) get_post_meta($post_id, '_ettic_otc_policy_attachment_id', true)) > 0 ? '' : ''), + default => null, + }; + // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped + } + + // Subprocessors + public function sub_columns(array $columns): array { + $new = []; + $new['cb'] = $columns['cb']; + $new['title'] = $columns['title']; + $new['ettic_otc_purpose'] = __('Purpose', 'open-trust-center-by-ettic'); + $new['ettic_otc_country'] = __('Location', 'open-trust-center-by-ettic'); + $new['ettic_otc_dpa'] = __('DPA', 'open-trust-center-by-ettic'); + $new['date'] = $columns['date']; + return $new; + } + + public function sub_column_content(string $column, int $post_id): void { + match ($column) { + 'ettic_otc_purpose' => print(esc_html(wp_trim_words(get_post_meta($post_id, '_ettic_otc_sub_purpose', true) ?: '', 10))), + 'ettic_otc_country' => print(esc_html(get_post_meta($post_id, '_ettic_otc_sub_country', true) ?: '—')), + 'ettic_otc_dpa' => print((bool) get_post_meta($post_id, '_ettic_otc_sub_dpa_signed', true) ? '' : '—'), + default => null, + }; + } + + // Data Practices + public function dp_columns(array $columns): array { + $new = []; + $new['cb'] = $columns['cb']; + $new['title'] = $columns['title']; + $new['ettic_otc_dp_items'] = __('Data Items', 'open-trust-center-by-ettic'); + $new['ettic_otc_dp_sort'] = __('Order', 'open-trust-center-by-ettic'); + $new['date'] = $columns['date']; + return $new; + } + + public function dp_column_content(string $column, int $post_id): void { + match ($column) { + 'ettic_otc_dp_items' => print(esc_html((string) count((array) (get_post_meta($post_id, '_ettic_otc_dp_data_items', true) ?: [])))), + 'ettic_otc_dp_sort' => print(esc_html((string) ((int) get_post_meta($post_id, '_ettic_otc_dp_sort_order', true)))), + default => null, + }; + } + + // ── FAQ meta box ── + + public function render_faq_meta_box(\WP_Post $post): void { + wp_nonce_field('ettic_otc_save_faq', 'ettic_otc_faq_nonce'); + + $policy_id = (int) get_post_meta($post->ID, '_ettic_otc_faq_related_policy', true); + + $policies = get_posts([ + 'post_type' => self::POLICY, + 'posts_per_page' => -1, + 'post_status' => 'publish', + 'orderby' => 'title', + 'order' => 'ASC', + ]); + ?> +
+ + +

+
+ +
+

+ + +

+
+ 0 && get_post_type($related) === self::POLICY) { + update_post_meta($post_id, '_ettic_otc_faq_related_policy', $related); + } else { + delete_post_meta($post_id, '_ettic_otc_faq_related_policy'); + } + } + + // FAQs + public function faq_columns(array $columns): array { + $new = []; + $new['cb'] = $columns['cb']; + $new['title'] = $columns['title']; + $new['ettic_otc_faq_order'] = __('Order', 'open-trust-center-by-ettic'); + $new['date'] = $columns['date']; + return $new; + } + + public function faq_column_content(string $column, int $post_id): void { + match ($column) { + 'ettic_otc_faq_order' => print(esc_html((string) ((int) (get_post($post_id)->menu_order ?? 0)))), + default => null, + }; + } +} diff --git a/includes/class-opentrust-io.php b/includes/class-ettic-otc-io.php similarity index 77% rename from includes/class-opentrust-io.php rename to includes/class-ettic-otc-io.php index bf1331d..aec1332 100644 --- a/includes/class-opentrust-io.php +++ b/includes/class-ettic-otc-io.php @@ -9,11 +9,24 @@ exit; } -final class OpenTrust_IO { +final class Ettic_OTC_IO { public const SCHEMA_VERSION = 1; - public const FORMAT_SETTINGS = 'opentrust-settings'; - public const FORMAT_CONTENT = 'opentrust-content'; + public const FORMAT_SETTINGS = 'ettic-otc-settings'; + public const FORMAT_CONTENT = 'ettic-otc-content'; + + /** + * Legacy format header values produced by the original OpenTrust plugin + * (v1.0.x and v1.1.x). Normalised on read so downstream === comparisons + * against the FORMAT_* constants keep working without per-site changes. + * + * @deprecated 1.2.0 Drop in 2.0.0 alongside remap_legacy_cpt_keys(), + * remap_legacy_meta_keys() and Ettic_OTC_CPT::LEGACY_*MAP. + */ + private const LEGACY_FORMAT_ALIASES = [ + 'opentrust-settings' => self::FORMAT_SETTINGS, + 'opentrust-content' => self::FORMAT_CONTENT, + ]; public const STRATEGY_SKIP = 'skip'; public const STRATEGY_OVERWRITE = 'overwrite'; @@ -22,7 +35,7 @@ final class OpenTrust_IO { // Encrypted secrets, per-site salt, and server-controlled flags. Never exported. public const SETTINGS_EXCLUDE = [ 'turnstile_secret_key', - 'opentrust_site_salt', + 'ettic_otc_site_salt', 'ai_enabled', 'ai_provider', // ai_model is paired with ai_provider — exporting it without the provider @@ -35,75 +48,75 @@ final class OpenTrust_IO { 'ai_model_recommended', ]; - // Keep in sync with class-opentrust-cpt.php save handlers. + // Keep in sync with class-ettic-otc-cpt.php save handlers. private const META_KEYS = [ - OpenTrust_CPT::POLICY => [ - '_opentrust_uuid', - '_opentrust_policy_ref_id', - '_opentrust_policy_category', - '_opentrust_policy_effective_date', - '_opentrust_policy_review_date', - '_opentrust_policy_sort_order', - '_opentrust_policy_citations', - '_opentrust_policy_attachment_id', - '_opentrust_version', - '_opentrust_version_summary', - '_opentrust_policy_chat_summary', - '_opentrust_policy_chat_summary_updated_at', - '_opentrust_policy_chat_summary_origin', + Ettic_OTC_CPT::POLICY => [ + '_ettic_otc_uuid', + '_ettic_otc_policy_ref_id', + '_ettic_otc_policy_category', + '_ettic_otc_policy_effective_date', + '_ettic_otc_policy_review_date', + '_ettic_otc_policy_sort_order', + '_ettic_otc_policy_citations', + '_ettic_otc_policy_attachment_id', + '_ettic_otc_version', + '_ettic_otc_version_summary', + '_ettic_otc_policy_chat_summary', + '_ettic_otc_policy_chat_summary_updated_at', + '_ettic_otc_policy_chat_summary_origin', ], - OpenTrust_CPT::CERTIFICATION => [ - '_opentrust_uuid', - '_opentrust_cert_type', - '_opentrust_cert_status', - '_opentrust_cert_issuing_body', - '_opentrust_cert_issue_date', - '_opentrust_cert_expiry_date', - '_opentrust_cert_badge_id', - '_opentrust_cert_artifact_id', - '_opentrust_cert_description', + Ettic_OTC_CPT::CERTIFICATION => [ + '_ettic_otc_uuid', + '_ettic_otc_cert_type', + '_ettic_otc_cert_status', + '_ettic_otc_cert_issuing_body', + '_ettic_otc_cert_issue_date', + '_ettic_otc_cert_expiry_date', + '_ettic_otc_cert_badge_id', + '_ettic_otc_cert_artifact_id', + '_ettic_otc_cert_description', ], - OpenTrust_CPT::SUBPROCESSOR => [ - '_opentrust_uuid', - '_opentrust_sub_purpose', - '_opentrust_sub_data_processed', - '_opentrust_sub_country', - '_opentrust_sub_website', - '_opentrust_sub_dpa_signed', + Ettic_OTC_CPT::SUBPROCESSOR => [ + '_ettic_otc_uuid', + '_ettic_otc_sub_purpose', + '_ettic_otc_sub_data_processed', + '_ettic_otc_sub_country', + '_ettic_otc_sub_website', + '_ettic_otc_sub_dpa_signed', ], - OpenTrust_CPT::DATA_PRACTICE => [ - '_opentrust_uuid', - '_opentrust_dp_data_items', - '_opentrust_dp_purpose', - '_opentrust_dp_legal_basis', - '_opentrust_dp_retention_period', - '_opentrust_dp_shared_with', - '_opentrust_dp_sort_order', - '_opentrust_dp_collected', - '_opentrust_dp_stored', - '_opentrust_dp_shared', - '_opentrust_dp_sold', - '_opentrust_dp_encrypted', + Ettic_OTC_CPT::DATA_PRACTICE => [ + '_ettic_otc_uuid', + '_ettic_otc_dp_data_items', + '_ettic_otc_dp_purpose', + '_ettic_otc_dp_legal_basis', + '_ettic_otc_dp_retention_period', + '_ettic_otc_dp_shared_with', + '_ettic_otc_dp_sort_order', + '_ettic_otc_dp_collected', + '_ettic_otc_dp_stored', + '_ettic_otc_dp_shared', + '_ettic_otc_dp_sold', + '_ettic_otc_dp_encrypted', ], - OpenTrust_CPT::FAQ => [ - '_opentrust_uuid', - '_opentrust_faq_related_policy', + Ettic_OTC_CPT::FAQ => [ + '_ettic_otc_uuid', + '_ettic_otc_faq_related_policy', ], ]; // Meta keys whose value is an attachment ID; serialized as __media_ref. private const ATTACHMENT_META_KEYS = [ - OpenTrust_CPT::POLICY => ['_opentrust_policy_attachment_id'], - OpenTrust_CPT::CERTIFICATION => ['_opentrust_cert_badge_id', '_opentrust_cert_artifact_id'], - OpenTrust_CPT::SUBPROCESSOR => [], - OpenTrust_CPT::DATA_PRACTICE => [], - OpenTrust_CPT::FAQ => [], + Ettic_OTC_CPT::POLICY => ['_ettic_otc_policy_attachment_id'], + Ettic_OTC_CPT::CERTIFICATION => ['_ettic_otc_cert_badge_id', '_ettic_otc_cert_artifact_id'], + Ettic_OTC_CPT::SUBPROCESSOR => [], + Ettic_OTC_CPT::DATA_PRACTICE => [], + Ettic_OTC_CPT::FAQ => [], ]; // meta_key => target_cpt_slug. Cross-CPT refs serialized as __post_ref. private const POST_REF_META_KEYS = [ - OpenTrust_CPT::FAQ => [ - '_opentrust_faq_related_policy' => OpenTrust_CPT::POLICY, + Ettic_OTC_CPT::FAQ => [ + '_ettic_otc_faq_related_policy' => Ettic_OTC_CPT::POLICY, ], ]; @@ -114,7 +127,7 @@ final class OpenTrust_IO { // ────────────────────────────────────────────── public static function build_settings_manifest(bool $include_media = true): array { - $settings = OpenTrust::get_settings(); + $settings = Ettic_OTC::get_settings(); foreach (self::SETTINGS_EXCLUDE as $k) { unset($settings[$k]); } @@ -133,8 +146,8 @@ public static function build_settings_manifest(bool $include_media = true): arra return [ 'format' => self::FORMAT_SETTINGS, 'schema' => self::SCHEMA_VERSION, - 'opentrust_version' => OPENTRUST_VERSION, - 'db_version' => OPENTRUST_DB_VERSION, + 'ettic_otc_version' => ETTIC_OTC_VERSION, + 'db_version' => ETTIC_OTC_DB_VERSION, 'exported_at' => gmdate('c'), 'site_url' => home_url('/'), 'site_locale' => get_locale(), @@ -177,8 +190,8 @@ public static function build_content_manifest(array $selection, bool $include_me return [ 'format' => self::FORMAT_CONTENT, 'schema' => self::SCHEMA_VERSION, - 'opentrust_version' => OPENTRUST_VERSION, - 'db_version' => OPENTRUST_DB_VERSION, + 'ettic_otc_version' => ETTIC_OTC_VERSION, + 'db_version' => ETTIC_OTC_DB_VERSION, 'exported_at' => gmdate('c'), 'site_url' => home_url('/'), 'site_locale' => get_locale(), @@ -190,12 +203,12 @@ public static function build_content_manifest(array $selection, bool $include_me // Returns the path to a temp ZIP. Caller deletes it when done. public static function write_zip(array $manifest, string $filename_prefix): string { if (!class_exists('ZipArchive')) { - throw new \RuntimeException(esc_html__('PHP ZipArchive extension is required for export.', 'opentrust')); + throw new \RuntimeException(esc_html__('PHP ZipArchive extension is required for export.', 'open-trust-center-by-ettic')); } $tmp = wp_tempnam($filename_prefix . '.zip'); if (!$tmp) { - throw new \RuntimeException(esc_html__('Could not create temp file for export.', 'opentrust')); + throw new \RuntimeException(esc_html__('Could not create temp file for export.', 'open-trust-center-by-ettic')); } // Source paths are local-only — pull them out before the manifest is encoded. @@ -209,7 +222,7 @@ public static function write_zip(array $manifest, string $filename_prefix): stri $zip = new \ZipArchive(); if ($zip->open($tmp, \ZipArchive::OVERWRITE | \ZipArchive::CREATE) !== true) { - throw new \RuntimeException(esc_html__('Could not open ZIP for writing.', 'opentrust')); + throw new \RuntimeException(esc_html__('Could not open ZIP for writing.', 'open-trust-center-by-ettic')); } $zip->addFromString('manifest.json', wp_json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); @@ -236,28 +249,41 @@ public static function validate_manifest(array $manifest): array { $errors = []; $format = (string) ($manifest['format'] ?? ''); + // Belt-and-suspenders normalisation for callers that bypass read_zip() + // and hand a raw manifest array directly (e.g. fixtures, future tests). + // @deprecated 1.2.0 Drop in 2.0.0 with LEGACY_FORMAT_ALIASES. + $format = self::LEGACY_FORMAT_ALIASES[$format] ?? $format; if (!in_array($format, [self::FORMAT_SETTINGS, self::FORMAT_CONTENT], true)) { - $errors[] = __('Unrecognised export format.', 'opentrust'); + $errors[] = __('Unrecognised export format.', 'open-trust-center-by-ettic'); } if ((int) ($manifest['schema'] ?? 0) !== self::SCHEMA_VERSION) { $errors[] = sprintf( /* translators: %1$d: schema version found, %2$d: schema version expected */ - __('Schema version mismatch (found %1$d, expected %2$d).', 'opentrust'), + __('Schema version mismatch (found %1$d, expected %2$d).', 'open-trust-center-by-ettic'), (int) ($manifest['schema'] ?? 0), self::SCHEMA_VERSION ); } - $their_major = (int) explode('.', (string) ($manifest['opentrust_version'] ?? '0.0.0'))[0]; - $our_major = (int) explode('.', OPENTRUST_VERSION)[0]; - if ($their_major !== $our_major) { - $errors[] = sprintf( - /* translators: %1$s: their version, %2$s: our version */ - __('Plugin major version mismatch (export: %1$s, this site: %2$s).', 'opentrust'), - (string) ($manifest['opentrust_version'] ?? '?'), - OPENTRUST_VERSION - ); + // Accept either the new ettic_otc_version field or the legacy + // opentrust_version field (v1.0.x and v1.1.x exports). If only the + // legacy field is present, skip the major-version gate — legacy + // archives are always v1.x and the LEGACY_MAP / LEGACY_META_MAP + // remap handles the schema differences. + $their_version = (string) ($manifest['ettic_otc_version'] ?? $manifest['opentrust_version'] ?? ''); + $is_legacy = isset($manifest['opentrust_version']) && !isset($manifest['ettic_otc_version']); + if (!$is_legacy && $their_version !== '') { + $their_major = (int) explode('.', $their_version)[0]; + $our_major = (int) explode('.', ETTIC_OTC_VERSION)[0]; + if ($their_major !== $our_major) { + $errors[] = sprintf( + /* translators: %1$s: their version, %2$s: our version */ + __('Plugin major version mismatch (export: %1$s, this site: %2$s).', 'open-trust-center-by-ettic'), + $their_version, + ETTIC_OTC_VERSION + ); + } } return [ @@ -313,12 +339,12 @@ public static function apply_content_import(array $manifest, string $zip_path, s // Suppress the chat summarizer for the duration of the import, otherwise // every imported policy queues a fresh summary generation (real cost). - $had_summarizer = remove_action('save_post_' . OpenTrust_CPT::POLICY, ['OpenTrust_Chat_Summarizer', 'on_save_post'], 20); + $had_summarizer = remove_action('save_post_' . Ettic_OTC_CPT::POLICY, ['Ettic_OTC_Chat_Summarizer', 'on_save_post'], 20); $media_map = self::sideload_bundled_media($manifest['media'] ?? [], $zip_path, $errors); // Policies first so FAQs can resolve their policy refs in one pass. - $cpt_order = [OpenTrust_CPT::POLICY, OpenTrust_CPT::CERTIFICATION, OpenTrust_CPT::SUBPROCESSOR, OpenTrust_CPT::DATA_PRACTICE, OpenTrust_CPT::FAQ]; + $cpt_order = [Ettic_OTC_CPT::POLICY, Ettic_OTC_CPT::CERTIFICATION, Ettic_OTC_CPT::SUBPROCESSOR, Ettic_OTC_CPT::DATA_PRACTICE, Ettic_OTC_CPT::FAQ]; $uuid_to_new_id = []; foreach ($cpt_order as $cpt) { @@ -343,10 +369,10 @@ public static function apply_content_import(array $manifest, string $zip_path, s self::remap_post_references($manifest['records'] ?? [], $uuid_to_new_id); if ($had_summarizer) { - add_action('save_post_' . OpenTrust_CPT::POLICY, ['OpenTrust_Chat_Summarizer', 'on_save_post'], 20, 3); + add_action('save_post_' . Ettic_OTC_CPT::POLICY, ['Ettic_OTC_Chat_Summarizer', 'on_save_post'], 20, 3); } - OpenTrust::instance()->invalidate_cache(); + Ettic_OTC::instance()->invalidate_cache(); return compact('created', 'updated', 'skipped', 'errors'); } @@ -368,21 +394,21 @@ public static function apply_settings_import(array $manifest, string $zip_path): } } - $current = OpenTrust::get_settings(); + $current = Ettic_OTC::get_settings(); $merged = array_merge($current, $imported); foreach (self::SETTINGS_EXCLUDE as $k) { $merged[$k] = $current[$k] ?? ''; } - update_option('opentrust_settings', $merged, false); + update_option('ettic_otc_settings', $merged, false); // Slug change → flush rewrites on next admin load. if (isset($imported['endpoint_slug']) && $imported['endpoint_slug'] !== ($current['endpoint_slug'] ?? '')) { - set_transient('opentrust_flush_rewrite', true); + set_transient('ettic_otc_flush_rewrite', true); } - OpenTrust::instance()->invalidate_cache(); + Ettic_OTC::instance()->invalidate_cache(); return ['updated' => count($imported), 'errors' => $errors]; } @@ -393,20 +419,28 @@ public static function apply_settings_import(array $manifest, string $zip_path): public static function read_zip(string $zip_path): array { if (!class_exists('ZipArchive')) { - throw new \RuntimeException(esc_html__('PHP ZipArchive extension is required.', 'opentrust')); + throw new \RuntimeException(esc_html__('PHP ZipArchive extension is required.', 'open-trust-center-by-ettic')); } $zip = new \ZipArchive(); if ($zip->open($zip_path) !== true) { - throw new \RuntimeException(esc_html__('Could not open uploaded archive.', 'opentrust')); + throw new \RuntimeException(esc_html__('Could not open uploaded archive.', 'open-trust-center-by-ettic')); } $raw = $zip->getFromName('manifest.json'); $zip->close(); if ($raw === false) { - throw new \RuntimeException(esc_html__('Archive is missing manifest.json.', 'opentrust')); + throw new \RuntimeException(esc_html__('Archive is missing manifest.json.', 'open-trust-center-by-ettic')); } $manifest = json_decode($raw, true); if (!is_array($manifest)) { - throw new \RuntimeException(esc_html__('manifest.json could not be parsed.', 'opentrust')); + throw new \RuntimeException(esc_html__('manifest.json could not be parsed.', 'open-trust-center-by-ettic')); + } + // Rewrite legacy "opentrust-*" format header to its renamed equivalent + // so the admin handler's strict === comparisons against FORMAT_* + // continue to dispatch correctly. Mutates the in-memory copy only. + // @deprecated 1.2.0 Drop in 2.0.0 with LEGACY_FORMAT_ALIASES. + $fmt = (string) ($manifest['format'] ?? ''); + if (isset(self::LEGACY_FORMAT_ALIASES[$fmt])) { + $manifest['format'] = self::LEGACY_FORMAT_ALIASES[$fmt]; } return ['manifest' => $manifest, 'zip_path' => $zip_path]; } @@ -416,16 +450,11 @@ public static function read_zip(string $zip_path): array { // ────────────────────────────────────────────── /** - * Rewrite v1.0.x CPT slugs (ot_*) in an inbound manifest to the v1.1+ - * slugs (opentr_*). Touches top-level record keys only — record bodies - * carry __post_ref values as UUIDs (not slugs) and resolve fine once the - * outer key is corrected. - * - * @deprecated 1.1.0 Drop in 2.0.0. The major-version mismatch check in - * validate_manifest() already hard-rejects 1.x archives on a - * 2.x destination, so this remap becomes redundant. Also - * remove the two call sites in preview_import() and - * apply_content_import(). + * Rewrite legacy CPT slugs (v1.0.x `ot_*`, v1.1.x `opentr_*`) in an + * inbound manifest to the current `eotc_*` slugs. Touches top-level + * record keys only — record bodies carry __post_ref values as UUIDs + * (not slugs) and resolve fine once the outer key is corrected. Phase 8 + * extends Ettic_OTC_CPT::LEGACY_MAP with the v1.1.x entries. */ private static function remap_legacy_cpt_keys(array $manifest): array { if (empty($manifest['records']) || !is_array($manifest['records'])) { @@ -433,7 +462,7 @@ private static function remap_legacy_cpt_keys(array $manifest): array { } $out = []; foreach ($manifest['records'] as $cpt => $recs) { - $out[OpenTrust_CPT::LEGACY_MAP[$cpt] ?? $cpt] = $recs; + $out[Ettic_OTC_CPT::LEGACY_MAP[$cpt] ?? $cpt] = $recs; } $manifest['records'] = $out; return $manifest; @@ -441,18 +470,18 @@ private static function remap_legacy_cpt_keys(array $manifest): array { /** * Rewrite legacy `_ot_*` postmeta keys in an inbound manifest's record - * bodies to the v1.1.1+ `_opentrust_*` keys. Pre-1.1.1 exports carry the + * bodies to the v1.1.1+ `_ettic_otc_*` keys. Pre-1.1.1 exports carry the * short prefix; without this remap their meta would be written under the * old key names and read back as empty. * * @deprecated 1.1.1 Drop in 2.0.0 alongside remap_legacy_cpt_keys() and - * OpenTrust_CPT::LEGACY_META_MAP. + * Ettic_OTC_CPT::LEGACY_META_MAP. */ private static function remap_legacy_meta_keys(array $manifest): array { if (empty($manifest['records']) || !is_array($manifest['records'])) { return $manifest; } - $map = OpenTrust_CPT::LEGACY_META_MAP; + $map = Ettic_OTC_CPT::LEGACY_META_MAP; foreach ($manifest['records'] as $cpt => $recs) { if (!is_array($recs)) { continue; @@ -509,7 +538,7 @@ private static function record_for_post(int $post_id, bool $include_media, array foreach (self::POST_REF_META_KEYS[$cpt] ?? [] as $meta_key => $_target_cpt) { $ref_id = (int) ($meta_out[$meta_key] ?? 0); if ($ref_id > 0) { - $ref_uuid = (string) get_post_meta($ref_id, '_opentrust_uuid', true); + $ref_uuid = (string) get_post_meta($ref_id, '_ettic_otc_uuid', true); if ($ref_uuid !== '') { $meta_out[$meta_key] = ['__post_ref' => $ref_uuid]; } else { @@ -519,7 +548,7 @@ private static function record_for_post(int $post_id, bool $include_media, array } return [ - 'uuid' => $meta_out['_opentrust_uuid'] ?? null, + 'uuid' => $meta_out['_ettic_otc_uuid'] ?? null, 'slug' => $post->post_name, 'title' => $post->post_title, 'content' => $post->post_content, @@ -541,8 +570,8 @@ private static function collect_attachment(int $att_id, array &$media): ?string } // Stamp the source so a same-site re-import dedupes by hash instead // of re-uploading (and tripping WP's MIME allowlist on SVG, etc.). - if (!get_post_meta($att_id, '_opentrust_import_sha256', true)) { - update_post_meta($att_id, '_opentrust_import_sha256', $hash); + if (!get_post_meta($att_id, '_ettic_otc_import_sha256', true)) { + update_post_meta($att_id, '_ettic_otc_import_sha256', $hash); } $att = get_post($att_id); $ext = pathinfo($path, PATHINFO_EXTENSION); @@ -572,7 +601,7 @@ private static function find_existing_post(string $cpt, array $rec): int { 'posts_per_page' => 1, 'fields' => 'ids', 'meta_query' => [ - ['key' => '_opentrust_uuid', 'value' => $uuid], + ['key' => '_ettic_otc_uuid', 'value' => $uuid], ], ]); if (!empty($hits)) return (int) $hits[0]; @@ -655,8 +684,8 @@ private static function upsert_record(string $cpt, array $rec, string $strategy, } // Fresh UUID on create_new so we don't collide with the existing post. - if (empty($meta['_opentrust_uuid']) || $strategy === self::STRATEGY_CREATE_NEW) { - $meta['_opentrust_uuid'] = wp_generate_uuid4(); + if (empty($meta['_ettic_otc_uuid']) || $strategy === self::STRATEGY_CREATE_NEW) { + $meta['_ettic_otc_uuid'] = wp_generate_uuid4(); } foreach ($meta as $key => $val) { @@ -694,7 +723,7 @@ private static function sideload_bundled_media(array $media, string $zip_path, a $map = []; $zip = new \ZipArchive(); if ($zip->open($zip_path) !== true) { - $errors[] = __('Could not reopen archive for media import.', 'opentrust'); + $errors[] = __('Could not reopen archive for media import.', 'open-trust-center-by-ettic'); return []; } @@ -709,7 +738,7 @@ private static function sideload_bundled_media(array $media, string $zip_path, a if ($contents === false) { $errors[] = sprintf( /* translators: %s: media path */ - __('Bundled media missing during import: %s', 'opentrust'), + __('Bundled media missing during import: %s', 'open-trust-center-by-ettic'), (string) $entry['path'] ); continue; @@ -729,7 +758,7 @@ private static function sideload_bundled_media(array $media, string $zip_path, a if (file_put_contents($dest_path, $contents) === false) { $errors[] = sprintf( /* translators: %s: filename */ - __('Could not write attachment file: %s', 'opentrust'), + __('Could not write attachment file: %s', 'open-trust-center-by-ettic'), $filename ); continue; @@ -747,7 +776,7 @@ private static function sideload_bundled_media(array $media, string $zip_path, a continue; } if (!$att_id) { - $errors[] = __('Could not create attachment.', 'opentrust'); + $errors[] = __('Could not create attachment.', 'open-trust-center-by-ettic'); wp_delete_file($dest_path); continue; } @@ -760,7 +789,7 @@ private static function sideload_bundled_media(array $media, string $zip_path, a update_post_meta($att_id, '_wp_attachment_image_alt', (string) $entry['alt']); } - update_post_meta($att_id, '_opentrust_import_sha256', $hash); + update_post_meta($att_id, '_ettic_otc_import_sha256', $hash); $map[$hash] = $att_id; } @@ -775,7 +804,7 @@ private static function find_attachment_by_hash(string $hash): int { 'posts_per_page' => 1, 'fields' => 'ids', 'meta_query' => [ - ['key' => '_opentrust_import_sha256', 'value' => $hash], + ['key' => '_ettic_otc_import_sha256', 'value' => $hash], ], ]); return !empty($hits) ? (int) $hits[0] : 0; diff --git a/includes/class-opentrust-render.php b/includes/class-ettic-otc-render.php similarity index 78% rename from includes/class-opentrust-render.php rename to includes/class-ettic-otc-render.php index 723acd9..63f555d 100644 --- a/includes/class-opentrust-render.php +++ b/includes/class-ettic-otc-render.php @@ -12,7 +12,7 @@ exit; } -final class OpenTrust_Render { +final class Ettic_OTC_Render { private static ?self $instance = null; @@ -39,7 +39,7 @@ public function dispatch(string $page): void { // ────────────────────────────────────────────── private function render_chat_page(): void { - $ot_settings = OpenTrust::get_settings(); + $ot_settings = Ettic_OTC::get_settings(); $ot_data = $this->gather_data($ot_settings); $ot_data['view'] = 'chat'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only prefill of search box on public chat page. @@ -63,14 +63,14 @@ private function render_chat_page(): void { // Never cache the chat page (fresh nonce required every load). // Set session cookie BEFORE any header() / body output. - if (class_exists('OpenTrust_Chat_Budget')) { - OpenTrust_Chat_Budget::ensure_session_cookie(); + if (class_exists('Ettic_OTC_Chat_Budget')) { + Ettic_OTC_Chat_Budget::ensure_session_cookie(); } header('Content-Type: text/html; charset=utf-8'); header('Cache-Control: no-store, no-cache, must-revalidate, private'); header('Pragma: no-cache'); - include OPENTRUST_PLUGIN_DIR . 'templates/chat.php'; + include ETTIC_OTC_PLUGIN_DIR . 'templates/chat.php'; } /** @@ -79,43 +79,43 @@ private function render_chat_page(): void { */ private function handle_chat_noscript_post(array $settings): array { $nonce = isset($_POST['_wpnonce']) ? sanitize_text_field((string) wp_unslash($_POST['_wpnonce'])) : ''; - if (!wp_verify_nonce($nonce, 'opentrust_chat_noscript')) { - return ['error' => __('Session expired. Please reload the page and try again.', 'opentrust')]; + if (!wp_verify_nonce($nonce, 'ettic_otc_chat_noscript')) { + return ['error' => __('Session expired. Please reload the page and try again.', 'open-trust-center-by-ettic')]; } $question = isset($_POST['question']) ? sanitize_textarea_field((string) wp_unslash($_POST['question'])) : ''; - $max_len = (int) ($settings['ai_max_message_length'] ?? OpenTrust_Chat::DEFAULT_MAX_MESSAGE_LENGTH); + $max_len = (int) ($settings['ai_max_message_length'] ?? Ettic_OTC_Chat::DEFAULT_MAX_MESSAGE_LENGTH); if ($question === '') { - return ['error' => __('Please enter a question.', 'opentrust')]; + return ['error' => __('Please enter a question.', 'open-trust-center-by-ettic')]; } if (strlen($question) > $max_len) { $question = substr($question, 0, $max_len); } - $adapter = OpenTrust_Chat_Provider::for((string) $settings['ai_provider']); - $api_key = OpenTrust_Chat_Secrets::get((string) $settings['ai_provider']); + $adapter = Ettic_OTC_Chat_Provider::for((string) $settings['ai_provider']); + $api_key = Ettic_OTC_Chat_Secrets::get((string) $settings['ai_provider']); if (!$adapter || $api_key === null) { - return ['error' => __('AI chat is not configured.', 'opentrust')]; + return ['error' => __('AI chat is not configured.', 'open-trust-center-by-ettic')]; } $locale = (string) determine_locale(); - $corpus = OpenTrust_Chat_Corpus::get_or_build($locale); + $corpus = Ettic_OTC_Chat_Corpus::get_or_build($locale); // Build a chat request identical to the REST handler's blocking path. // The system prompt + index + tool surface come from the same shared // builder so the noscript path is byte-equivalent to the streaming // one — we just don't get to stream the response. $args = [ - 'system' => OpenTrust_Chat::build_system_prompt($settings, $corpus), + 'system' => Ettic_OTC_Chat::build_system_prompt($settings, $corpus), 'corpus' => $corpus, 'messages' => [['role' => 'user', 'content' => $question]], - 'tools' => OpenTrust_Chat::tool_definitions(), + 'tools' => Ettic_OTC_Chat::tool_definitions(), 'model' => (string) $settings['ai_model'], 'api_key' => $api_key, 'settings' => $settings, ]; - $collector = new OpenTrust_Chat_Stream_Collector($corpus['urls'] ?? []); + $collector = new Ettic_OTC_Chat_Stream_Collector($corpus['urls'] ?? []); $on_chunk = static function (array $event) use ($collector): void { $collector->ingest($event); // noscript path is blocking; never forwards @@ -124,7 +124,7 @@ private function handle_chat_noscript_post(array $settings): array { // Per-request loop detection map — same contract as the REST path. $seen_calls = []; $tool_resolver = static function (string $name, array $args) use ($corpus, &$seen_calls): array { - return OpenTrust_Chat::resolve_tool($name, $args, $corpus, $seen_calls); + return Ettic_OTC_Chat::resolve_tool($name, $args, $corpus, $seen_calls); }; try { @@ -155,7 +155,7 @@ private function compute_chat_state(array $settings): string { if (empty($settings['ai_enabled']) || empty($settings['ai_provider']) || empty($settings['ai_model'])) { return 'unconfigured'; } - if (OpenTrust_Chat_Secrets::get((string) $settings['ai_provider']) === null) { + if (Ettic_OTC_Chat_Secrets::get((string) $settings['ai_provider']) === null) { return 'unconfigured'; } return 'ready'; @@ -166,11 +166,11 @@ private function compute_chat_state(array $settings): string { // ────────────────────────────────────────────── private function render_trust_center(): void { - $ot_settings = OpenTrust::get_settings(); + $ot_settings = Ettic_OTC::get_settings(); $ot_data = $this->gather_data($ot_settings); header('Content-Type: text/html; charset=utf-8'); - include OPENTRUST_PLUGIN_DIR . 'templates/trust-center.php'; + include ETTIC_OTC_PLUGIN_DIR . 'templates/trust-center.php'; } // ────────────────────────────────────────────── @@ -178,7 +178,7 @@ private function render_trust_center(): void { // ────────────────────────────────────────────── private function render_policy_single(): void { - $slug = sanitize_title(get_query_var('opentrust_policy_slug', '')); + $slug = sanitize_title(get_query_var('ettic_otc_policy_slug', '')); $policy = $this->find_policy_by_slug($slug); if (!$policy) { @@ -186,19 +186,19 @@ private function render_policy_single(): void { return; } - $ot_settings = OpenTrust::get_settings(); + $ot_settings = Ettic_OTC::get_settings(); $ot_data = $this->gather_data($ot_settings); $ot_data['current_policy'] = $policy; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Core WordPress filter $ot_data['policy_content'] = apply_filters('the_content', $policy->post_content); - $ot_data['policy_version'] = (int) get_post_meta($policy->ID, '_opentrust_version', true) ?: 1; + $ot_data['policy_version'] = (int) get_post_meta($policy->ID, '_ettic_otc_version', true) ?: 1; $ot_data['policy_meta'] = $this->get_policy_meta($policy->ID); $ot_data['policy_versions'] = $this->get_policy_versions($policy); $ot_data['is_pending'] = $this->is_future_dated($policy); $ot_data['view'] = 'policy_single'; header('Content-Type: text/html; charset=utf-8'); - include OPENTRUST_PLUGIN_DIR . 'templates/trust-center.php'; + include ETTIC_OTC_PLUGIN_DIR . 'templates/trust-center.php'; } // ────────────────────────────────────────────── @@ -206,8 +206,8 @@ private function render_policy_single(): void { // ────────────────────────────────────────────── private function render_policy_version(): void { - $slug = sanitize_title(get_query_var('opentrust_policy_slug', '')); - $version = (int) get_query_var('opentrust_version', '0'); + $slug = sanitize_title(get_query_var('ettic_otc_policy_slug', '')); + $version = (int) get_query_var('ettic_otc_version', '0'); $policy = $this->find_policy_by_slug($slug); if (!$policy || $version < 1) { @@ -216,9 +216,9 @@ private function render_policy_version(): void { } // Current version — redirect to canonical. - $current_version = (int) get_post_meta($policy->ID, '_opentrust_version', true) ?: 1; + $current_version = (int) get_post_meta($policy->ID, '_ettic_otc_version', true) ?: 1; if ($version === $current_version) { - $settings = OpenTrust::get_settings(); + $settings = Ettic_OTC::get_settings(); $base = home_url('/' . $settings['endpoint_slug'] . '/policy/' . $policy->post_name . '/'); wp_safe_redirect($base, 301); exit; @@ -228,7 +228,7 @@ private function render_policy_version(): void { $revisions = wp_get_post_revisions($policy->ID, ['order' => 'ASC']); $target = null; foreach ($revisions as $rev) { - if ((int) get_post_meta($rev->ID, '_opentrust_version', true) === $version) { + if ((int) get_post_meta($rev->ID, '_ettic_otc_version', true) === $version) { $target = $rev; break; } @@ -239,7 +239,7 @@ private function render_policy_version(): void { return; } - $ot_settings = OpenTrust::get_settings(); + $ot_settings = Ettic_OTC::get_settings(); $ot_data = $this->gather_data($ot_settings); $ot_data['current_policy'] = $policy; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Core WordPress filter @@ -251,7 +251,7 @@ private function render_policy_version(): void { $ot_data['view'] = 'policy_single'; header('Content-Type: text/html; charset=utf-8'); - include OPENTRUST_PLUGIN_DIR . 'templates/trust-center.php'; + include ETTIC_OTC_PLUGIN_DIR . 'templates/trust-center.php'; } // ────────────────────────────────────────────── @@ -260,15 +260,15 @@ private function render_policy_version(): void { private function render_404(): void { status_header(404); - $settings = OpenTrust::get_settings(); - $hsl = OpenTrust::hex_to_hsl($settings['accent_color'] ?? '#2563EB'); + $settings = Ettic_OTC::get_settings(); + $hsl = Ettic_OTC::hex_to_hsl($settings['accent_color'] ?? '#2563EB'); header('Content-Type: text/html; charset=utf-8'); echo 'Not Found'; echo ''; echo '

404

'; - echo '

' . esc_html__('Page not found.', 'opentrust') . '

'; - echo '' . esc_html__('Back to Trust Center', 'opentrust') . ''; + echo '

' . esc_html__('Page not found.', 'open-trust-center-by-ettic') . '

'; + echo '' . esc_html__('Back to Trust Center', 'open-trust-center-by-ettic') . ''; echo '
'; } @@ -277,15 +277,15 @@ private function render_404(): void { // ────────────────────────────────────────────── public function gather_data(array $settings): array { - $hsl = OpenTrust::hex_to_hsl($settings['accent_color'] ?? '#2563EB'); - $repo = OpenTrust_Repository::instance(); + $hsl = Ettic_OTC::hex_to_hsl($settings['accent_color'] ?? '#2563EB'); + $repo = Ettic_OTC_Repository::instance(); $data = [ 'settings' => $settings, 'hsl' => $hsl, 'logo_url' => '', 'avatar_url' => '', - 'base_url' => home_url('/' . ($settings['endpoint_slug'] ?? OpenTrust::DEFAULT_ENDPOINT_SLUG) . '/'), + 'base_url' => home_url('/' . ($settings['endpoint_slug'] ?? Ettic_OTC::DEFAULT_ENDPOINT_SLUG) . '/'), 'view' => 'main', 'certifications' => [], 'policies' => [], @@ -313,11 +313,11 @@ public function gather_data(array $settings): array { // gate is consumer-layer concern. $visible = $settings['sections_visible'] ?? []; $section_cpt_map = [ - 'certifications' => OpenTrust_CPT::CERTIFICATION, - 'policies' => OpenTrust_CPT::POLICY, - 'subprocessors' => OpenTrust_CPT::SUBPROCESSOR, - 'data_practices' => OpenTrust_CPT::DATA_PRACTICE, - 'faqs' => OpenTrust_CPT::FAQ, + 'certifications' => Ettic_OTC_CPT::CERTIFICATION, + 'policies' => Ettic_OTC_CPT::POLICY, + 'subprocessors' => Ettic_OTC_CPT::SUBPROCESSOR, + 'data_practices' => Ettic_OTC_CPT::DATA_PRACTICE, + 'faqs' => Ettic_OTC_CPT::FAQ, ]; if (!empty($visible['certifications'])) { @@ -353,7 +353,7 @@ public function gather_data(array $settings): array { * Whether a policy's effective date is in the future. */ private function is_future_dated(\WP_Post $policy): bool { - $effective_date = get_post_meta($policy->ID, '_opentrust_policy_effective_date', true); + $effective_date = get_post_meta($policy->ID, '_ettic_otc_policy_effective_date', true); if (!$effective_date) { return false; } @@ -370,14 +370,14 @@ private function is_future_dated(\WP_Post $policy): bool { * Returns an array of ['version' => int, 'date' => string, 'url' => string, 'current' => bool]. */ private function get_policy_versions(\WP_Post $policy): array { - $settings = OpenTrust::get_settings(); - $endpoint = $settings['endpoint_slug'] ?? OpenTrust::DEFAULT_ENDPOINT_SLUG; - $current_version = (int) get_post_meta($policy->ID, '_opentrust_version', true) ?: 1; + $settings = Ettic_OTC::get_settings(); + $endpoint = $settings['endpoint_slug'] ?? Ettic_OTC::DEFAULT_ENDPOINT_SLUG; + $current_version = (int) get_post_meta($policy->ID, '_ettic_otc_version', true) ?: 1; $current_url = home_url('/' . $endpoint . '/policy/' . $policy->post_name . '/'); // Start with the current version. $date_fmt = get_option('date_format'); - $current_eff = get_post_meta($policy->ID, '_opentrust_policy_effective_date', true); + $current_eff = get_post_meta($policy->ID, '_ettic_otc_policy_effective_date', true); $versions = []; $versions[] = [ 'version' => $current_version, @@ -386,7 +386,7 @@ private function get_policy_versions(\WP_Post $policy): array { : wp_date($date_fmt, strtotime($policy->post_modified)), 'url' => $current_url, 'current' => true, - 'summary' => get_post_meta($policy->ID, '_opentrust_version_summary', true) ?: '', + 'summary' => get_post_meta($policy->ID, '_ettic_otc_version_summary', true) ?: '', ]; // Add past versions from revisions. @@ -397,13 +397,13 @@ private function get_policy_versions(\WP_Post $policy): array { $seen = [$current_version => true]; foreach ($revisions as $rev) { - $rev_version = (int) get_post_meta($rev->ID, '_opentrust_version', true); + $rev_version = (int) get_post_meta($rev->ID, '_ettic_otc_version', true); if (!$rev_version || isset($seen[$rev_version])) { continue; } $seen[$rev_version] = true; - $rev_eff = get_post_meta($rev->ID, '_opentrust_policy_effective_date', true); + $rev_eff = get_post_meta($rev->ID, '_ettic_otc_policy_effective_date', true); $versions[] = [ 'version' => $rev_version, 'date' => $rev_eff @@ -411,7 +411,7 @@ private function get_policy_versions(\WP_Post $policy): array { : wp_date($date_fmt, strtotime($rev->post_modified)), 'url' => home_url('/' . $endpoint . '/policy/' . $policy->post_name . '/version/' . $rev_version . '/'), 'current' => false, - 'summary' => get_post_meta($rev->ID, '_opentrust_version_summary', true) ?: '', + 'summary' => get_post_meta($rev->ID, '_ettic_otc_version_summary', true) ?: '', ]; } @@ -439,37 +439,37 @@ public static function updated_pill(string $section_key, array $data): void { $diff = time() - (int) $timestamp; if ($diff < 60) { - $text = __('Updated just now', 'opentrust'); + $text = __('Updated just now', 'open-trust-center-by-ettic'); } elseif ($diff < 3600) { $minutes = (int) floor($diff / 60); $text = sprintf( /* translators: %d: number of minutes since last update */ - _n('Updated %d minute ago', 'Updated %d minutes ago', $minutes, 'opentrust'), + _n('Updated %d minute ago', 'Updated %d minutes ago', $minutes, 'open-trust-center-by-ettic'), $minutes ); } elseif ($diff < 86400) { $hours = (int) floor($diff / 3600); $text = sprintf( /* translators: %d: number of hours since last update */ - _n('Updated %d hour ago', 'Updated %d hours ago', $hours, 'opentrust'), + _n('Updated %d hour ago', 'Updated %d hours ago', $hours, 'open-trust-center-by-ettic'), $hours ); } elseif ($diff < 2592000) { $days = (int) floor($diff / 86400); $text = sprintf( /* translators: %d: number of days since last update */ - _n('Updated %d day ago', 'Updated %d days ago', $days, 'opentrust'), + _n('Updated %d day ago', 'Updated %d days ago', $days, 'open-trust-center-by-ettic'), $days ); } else { $text = sprintf( /* translators: %s = formatted date */ - __('Updated %s', 'opentrust'), + __('Updated %s', 'open-trust-center-by-ettic'), wp_date('M j, Y', (int) $timestamp) ); } - ?> OpenTrust_CPT::POLICY, + 'post_type' => Ettic_OTC_CPT::POLICY, 'name' => $slug, 'posts_per_page' => 1, 'post_status' => 'publish', @@ -492,13 +492,13 @@ private function find_policy_by_slug(string $slug): ?\WP_Post { } private function get_policy_meta(int $post_id): array { - $repo = OpenTrust_Repository::instance(); + $repo = Ettic_OTC_Repository::instance(); return [ - 'ref_id' => (string) (get_post_meta($post_id, '_opentrust_policy_ref_id', true) ?: ''), - 'category' => get_post_meta($post_id, '_opentrust_policy_category', true) ?: 'other', - 'citations' => $repo->normalize_citations(get_post_meta($post_id, '_opentrust_policy_citations', true)), - 'effective_date' => get_post_meta($post_id, '_opentrust_policy_effective_date', true) ?: '', - 'review_date' => get_post_meta($post_id, '_opentrust_policy_review_date', true) ?: '', + 'ref_id' => (string) (get_post_meta($post_id, '_ettic_otc_policy_ref_id', true) ?: ''), + 'category' => get_post_meta($post_id, '_ettic_otc_policy_category', true) ?: 'other', + 'citations' => $repo->normalize_citations(get_post_meta($post_id, '_ettic_otc_policy_citations', true)), + 'effective_date' => get_post_meta($post_id, '_ettic_otc_policy_effective_date', true) ?: '', + 'review_date' => get_post_meta($post_id, '_ettic_otc_policy_review_date', true) ?: '', 'attachment' => $repo->resolve_policy_attachment($post_id), ]; } @@ -508,11 +508,11 @@ private function get_policy_meta(int $post_id): array { */ public static function policy_category_labels(): array { return [ - 'security' => __('Security', 'opentrust'), - 'privacy' => __('Privacy', 'opentrust'), - 'compliance' => __('Compliance', 'opentrust'), - 'operational' => __('Operational', 'opentrust'), - 'other' => __('General', 'opentrust'), + 'security' => __('Security', 'open-trust-center-by-ettic'), + 'privacy' => __('Privacy', 'open-trust-center-by-ettic'), + 'compliance' => __('Compliance', 'open-trust-center-by-ettic'), + 'operational' => __('Operational', 'open-trust-center-by-ettic'), + 'other' => __('General', 'open-trust-center-by-ettic'), ]; } @@ -523,9 +523,9 @@ public static function policy_category_labels(): array { */ public static function cert_status_labels(): array { return [ - 'active' => __('Certified', 'opentrust'), - 'in_progress' => __('In audit', 'opentrust'), - 'expired' => __('Expired', 'opentrust'), + 'active' => __('Certified', 'open-trust-center-by-ettic'), + 'in_progress' => __('In audit', 'open-trust-center-by-ettic'), + 'expired' => __('Expired', 'open-trust-center-by-ettic'), ]; } @@ -534,20 +534,20 @@ public static function cert_status_labels(): array { */ public static function cert_aligned_status_labels(): array { return [ - 'active' => __('Compliant', 'opentrust'), - 'in_progress' => __('Working toward', 'opentrust'), - 'expired' => __('Lapsed', 'opentrust'), + 'active' => __('Compliant', 'open-trust-center-by-ettic'), + 'in_progress' => __('Working toward', 'open-trust-center-by-ettic'), + 'expired' => __('Lapsed', 'open-trust-center-by-ettic'), ]; } public static function legal_basis_labels(): array { return [ - 'consent' => __('Consent', 'opentrust'), - 'contract' => __('Contractual Necessity', 'opentrust'), - 'legitimate_interest' => __('Legitimate Interest', 'opentrust'), - 'legal_obligation' => __('Legal Obligation', 'opentrust'), - 'vital_interest' => __('Vital Interest', 'opentrust'), - 'public_interest' => __('Public Interest', 'opentrust'), + 'consent' => __('Consent', 'open-trust-center-by-ettic'), + 'contract' => __('Contractual Necessity', 'open-trust-center-by-ettic'), + 'legitimate_interest' => __('Legitimate Interest', 'open-trust-center-by-ettic'), + 'legal_obligation' => __('Legal Obligation', 'open-trust-center-by-ettic'), + 'vital_interest' => __('Vital Interest', 'open-trust-center-by-ettic'), + 'public_interest' => __('Public Interest', 'open-trust-center-by-ettic'), ]; } diff --git a/includes/class-opentrust-repository.php b/includes/class-ettic-otc-repository.php similarity index 81% rename from includes/class-opentrust-repository.php rename to includes/class-ettic-otc-repository.php index 2c58b23..fdaba59 100644 --- a/includes/class-opentrust-repository.php +++ b/includes/class-ettic-otc-repository.php @@ -3,7 +3,7 @@ * Read-side data layer for the trust center. * * Single source of truth for "what published trust-center items exist." Owns - * all DB fetching for the five OpenTrust CPTs and the per-CPT projection + * all DB fetching for the five Ettic_OTC CPTs and the per-CPT projection * shape consumed by Render (templates) and Chat_Corpus (AI corpus index). * * Returns ALL published items unconditionally — no visibility filtering, no @@ -16,7 +16,7 @@ * the public page is also hidden from the AI's corpus index. * * Caching: each fetcher is memoized in a locale-scoped transient bumped by - * opentrust_cache_version. Returns are plain projected arrays. + * ettic_otc_cache_version. Returns are plain projected arrays. */ declare(strict_types=1); @@ -25,7 +25,7 @@ exit; } -final class OpenTrust_Repository { +final class Ettic_OTC_Repository { private static ?self $instance = null; @@ -44,25 +44,25 @@ public function fetch_certifications(): array { return $this->cached_query( 'certifications', [ - 'post_type' => OpenTrust_CPT::CERTIFICATION, + 'post_type' => Ettic_OTC_CPT::CERTIFICATION, 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => 'menu_order title', 'order' => 'ASC', ], static function (WP_Post $post): array { - $badge_id = (int) get_post_meta($post->ID, '_opentrust_cert_badge_id', true); - $artifact_id = (int) get_post_meta($post->ID, '_opentrust_cert_artifact_id', true); + $badge_id = (int) get_post_meta($post->ID, '_ettic_otc_cert_badge_id', true); + $artifact_id = (int) get_post_meta($post->ID, '_ettic_otc_cert_artifact_id', true); return [ 'id' => $post->ID, 'title' => $post->post_title, - 'type' => get_post_meta($post->ID, '_opentrust_cert_type', true) ?: 'compliant', - 'issuing_body' => get_post_meta($post->ID, '_opentrust_cert_issuing_body', true) ?: '', - 'status' => get_post_meta($post->ID, '_opentrust_cert_status', true) ?: 'active', - 'issue_date' => get_post_meta($post->ID, '_opentrust_cert_issue_date', true) ?: '', - 'expiry_date' => get_post_meta($post->ID, '_opentrust_cert_expiry_date', true) ?: '', + 'type' => get_post_meta($post->ID, '_ettic_otc_cert_type', true) ?: 'compliant', + 'issuing_body' => get_post_meta($post->ID, '_ettic_otc_cert_issuing_body', true) ?: '', + 'status' => get_post_meta($post->ID, '_ettic_otc_cert_status', true) ?: 'active', + 'issue_date' => get_post_meta($post->ID, '_ettic_otc_cert_issue_date', true) ?: '', + 'expiry_date' => get_post_meta($post->ID, '_ettic_otc_cert_expiry_date', true) ?: '', 'badge_url' => $badge_id ? (wp_get_attachment_image_url($badge_id, 'thumbnail') ?: '') : '', - 'description' => get_post_meta($post->ID, '_opentrust_cert_description', true) ?: '', + 'description' => get_post_meta($post->ID, '_ettic_otc_cert_description', true) ?: '', 'artifact_url' => $artifact_id ? (wp_get_attachment_url($artifact_id) ?: '') : '', ]; } @@ -76,27 +76,27 @@ public function fetch_policies(): array { return $this->cached_query( 'policies', [ - 'post_type' => OpenTrust_CPT::POLICY, + 'post_type' => Ettic_OTC_CPT::POLICY, 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => 'meta_value_num title', - 'meta_key' => '_opentrust_policy_sort_order', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Transient-cached; <100 posts + 'meta_key' => '_ettic_otc_policy_sort_order', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Transient-cached; <100 posts 'order' => 'ASC', ], function (WP_Post $post): array { - $eff = get_post_meta($post->ID, '_opentrust_policy_effective_date', true) ?: ''; + $eff = get_post_meta($post->ID, '_ettic_otc_policy_effective_date', true) ?: ''; $attachment = $this->resolve_policy_attachment($post->ID); return [ 'id' => $post->ID, 'title' => $post->post_title, 'slug' => $post->post_name, 'excerpt' => $post->post_excerpt ?: wp_trim_words($post->post_content, 30), - 'version' => (int) get_post_meta($post->ID, '_opentrust_version', true) ?: 1, - 'ref_id' => (string) (get_post_meta($post->ID, '_opentrust_policy_ref_id', true) ?: ''), - 'category' => get_post_meta($post->ID, '_opentrust_policy_category', true) ?: 'other', - 'citations' => $this->normalize_citations(get_post_meta($post->ID, '_opentrust_policy_citations', true)), + 'version' => (int) get_post_meta($post->ID, '_ettic_otc_version', true) ?: 1, + 'ref_id' => (string) (get_post_meta($post->ID, '_ettic_otc_policy_ref_id', true) ?: ''), + 'category' => get_post_meta($post->ID, '_ettic_otc_policy_category', true) ?: 'other', + 'citations' => $this->normalize_citations(get_post_meta($post->ID, '_ettic_otc_policy_citations', true)), 'effective_date' => $eff, - 'review_date' => get_post_meta($post->ID, '_opentrust_policy_review_date', true) ?: '', + 'review_date' => get_post_meta($post->ID, '_ettic_otc_policy_review_date', true) ?: '', 'attachment' => $attachment, 'last_modified' => $post->post_modified, 'is_pending' => $eff && strtotime($eff) > time(), @@ -112,7 +112,7 @@ public function fetch_subprocessors(): array { return $this->cached_query( 'subprocessors', [ - 'post_type' => OpenTrust_CPT::SUBPROCESSOR, + 'post_type' => Ettic_OTC_CPT::SUBPROCESSOR, 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => 'title', @@ -121,11 +121,11 @@ public function fetch_subprocessors(): array { static fn(WP_Post $post): array => [ 'id' => $post->ID, 'name' => $post->post_title, - 'purpose' => get_post_meta($post->ID, '_opentrust_sub_purpose', true) ?: '', - 'data_processed' => get_post_meta($post->ID, '_opentrust_sub_data_processed', true) ?: '', - 'country' => get_post_meta($post->ID, '_opentrust_sub_country', true) ?: '', - 'website' => get_post_meta($post->ID, '_opentrust_sub_website', true) ?: '', - 'dpa_signed' => (bool) get_post_meta($post->ID, '_opentrust_sub_dpa_signed', true), + 'purpose' => get_post_meta($post->ID, '_ettic_otc_sub_purpose', true) ?: '', + 'data_processed' => get_post_meta($post->ID, '_ettic_otc_sub_data_processed', true) ?: '', + 'country' => get_post_meta($post->ID, '_ettic_otc_sub_country', true) ?: '', + 'website' => get_post_meta($post->ID, '_ettic_otc_sub_website', true) ?: '', + 'dpa_signed' => (bool) get_post_meta($post->ID, '_ettic_otc_sub_dpa_signed', true), ] ); } @@ -137,29 +137,29 @@ public function fetch_data_practices(): array { return $this->cached_query( 'data_practices', [ - 'post_type' => OpenTrust_CPT::DATA_PRACTICE, + 'post_type' => Ettic_OTC_CPT::DATA_PRACTICE, 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => 'meta_value_num title', - 'meta_key' => '_opentrust_dp_sort_order', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Transient-cached; <100 posts + 'meta_key' => '_ettic_otc_dp_sort_order', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Transient-cached; <100 posts 'order' => 'ASC', ], static function (WP_Post $post): array { - $data_items = get_post_meta($post->ID, '_opentrust_dp_data_items', true); - $shared = get_post_meta($post->ID, '_opentrust_dp_shared_with', true); + $data_items = get_post_meta($post->ID, '_ettic_otc_dp_data_items', true); + $shared = get_post_meta($post->ID, '_ettic_otc_dp_shared_with', true); return [ 'id' => $post->ID, 'title' => $post->post_title, 'data_items' => is_array($data_items) ? $data_items : [], - 'purpose' => get_post_meta($post->ID, '_opentrust_dp_purpose', true) ?: '', - 'legal_basis' => get_post_meta($post->ID, '_opentrust_dp_legal_basis', true) ?: '', - 'retention_period' => get_post_meta($post->ID, '_opentrust_dp_retention_period', true) ?: '', + 'purpose' => get_post_meta($post->ID, '_ettic_otc_dp_purpose', true) ?: '', + 'legal_basis' => get_post_meta($post->ID, '_ettic_otc_dp_legal_basis', true) ?: '', + 'retention_period' => get_post_meta($post->ID, '_ettic_otc_dp_retention_period', true) ?: '', 'shared_with' => is_array($shared) ? $shared : [], - 'prop_collected' => (bool) get_post_meta($post->ID, '_opentrust_dp_collected', true), - 'prop_stored' => (bool) get_post_meta($post->ID, '_opentrust_dp_stored', true), - 'prop_shared' => (bool) get_post_meta($post->ID, '_opentrust_dp_shared', true), - 'prop_sold' => (bool) get_post_meta($post->ID, '_opentrust_dp_sold', true), - 'prop_encrypted' => (bool) get_post_meta($post->ID, '_opentrust_dp_encrypted', true), + 'prop_collected' => (bool) get_post_meta($post->ID, '_ettic_otc_dp_collected', true), + 'prop_stored' => (bool) get_post_meta($post->ID, '_ettic_otc_dp_stored', true), + 'prop_shared' => (bool) get_post_meta($post->ID, '_ettic_otc_dp_shared', true), + 'prop_sold' => (bool) get_post_meta($post->ID, '_ettic_otc_dp_sold', true), + 'prop_encrypted' => (bool) get_post_meta($post->ID, '_ettic_otc_dp_encrypted', true), ]; } ); @@ -169,18 +169,18 @@ static function (WP_Post $post): array { * @return array> */ public function fetch_faqs(): array { - $endpoint = OpenTrust::get_settings()['endpoint_slug'] ?? OpenTrust::DEFAULT_ENDPOINT_SLUG; + $endpoint = Ettic_OTC::get_settings()['endpoint_slug'] ?? Ettic_OTC::DEFAULT_ENDPOINT_SLUG; return $this->cached_query( 'faqs', [ - 'post_type' => OpenTrust_CPT::FAQ, + 'post_type' => Ettic_OTC_CPT::FAQ, 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => ['menu_order' => 'ASC', 'title' => 'ASC'], ], static function (WP_Post $post) use ($endpoint): array { - $related_id = (int) get_post_meta($post->ID, '_opentrust_faq_related_policy', true); + $related_id = (int) get_post_meta($post->ID, '_ettic_otc_faq_related_policy', true); $related_url = ''; $related_title = ''; if ($related_id && get_post_status($related_id) === 'publish') { @@ -214,7 +214,7 @@ static function (WP_Post $post) use ($endpoint): array { */ public function fetch_policy_posts(): array { return get_posts([ - 'post_type' => OpenTrust_CPT::POLICY, + 'post_type' => Ettic_OTC_CPT::POLICY, 'posts_per_page' => -1, 'post_status' => 'publish', 'orderby' => 'title', @@ -252,17 +252,17 @@ public function section_last_updated(string $post_type): string { /** * Build a locale-and-version-scoped transient key. The locale suffix keeps * WPML/Polylang variants in separate buckets; the version counter - * (opentrust_cache_version, bumped by OpenTrust::invalidate_cache) lets a + * (ettic_otc_cache_version, bumped by Ettic_OTC::invalidate_cache) lets a * single option flip bust every cached locale at once. */ private function cache_key(string $bucket): string { - $version = (int) get_option('opentrust_cache_version', 1); - return 'opentrust_' . $bucket . '_' . sanitize_key(determine_locale()) . '_v' . $version; + $version = (int) get_option('ettic_otc_cache_version', 1); + return 'ettic_otc_' . $bucket . '_' . sanitize_key(determine_locale()) . '_v' . $version; } /** * Shared transient + WP_Query plumbing for the per-CPT fetchers. Memoized - * for HOUR_IN_SECONDS and invalidated globally via opentrust_cache_version. + * for HOUR_IN_SECONDS and invalidated globally via ettic_otc_cache_version. * * @param array $query_args * @param callable(WP_Post):array $mapper @@ -313,7 +313,7 @@ public function normalize_citations($raw): array { * @return array{url:string,filename:string,size_bytes:int,size_human:string}|null */ public function resolve_policy_attachment(int $post_id): ?array { - $attachment_id = (int) get_post_meta($post_id, '_opentrust_policy_attachment_id', true); + $attachment_id = (int) get_post_meta($post_id, '_ettic_otc_policy_attachment_id', true); if ($attachment_id <= 0 || get_post_type($attachment_id) !== 'attachment') { return null; } diff --git a/includes/class-opentrust-version.php b/includes/class-ettic-otc-version.php similarity index 75% rename from includes/class-opentrust-version.php rename to includes/class-ettic-otc-version.php index 4261057..4b8e58d 100644 --- a/includes/class-opentrust-version.php +++ b/includes/class-ettic-otc-version.php @@ -16,7 +16,7 @@ exit; } -final class OpenTrust_Version { +final class Ettic_OTC_Version { private static ?self $instance = null; @@ -37,7 +37,7 @@ private function __construct() { * with the OLD version so it's preserved in history. */ public static function bump_version(int $post_id, string $change_summary = ''): void { - $current_version = (int) get_post_meta($post_id, '_opentrust_version', true) ?: 1; + $current_version = (int) get_post_meta($post_id, '_ettic_otc_version', true) ?: 1; $new_version = $current_version + 1; // Find an untagged revision that holds the OLD content (pre-update). @@ -51,7 +51,7 @@ public static function bump_version(int $post_id, string $change_summary = ''): if ($revisions && $post) { foreach ($revisions as $rev) { - $existing = get_post_meta($rev->ID, '_opentrust_version', true); + $existing = get_post_meta($rev->ID, '_ettic_otc_version', true); if (!empty($existing) && (int) $existing > 0) { continue; // already tagged, skip } @@ -66,16 +66,16 @@ public static function bump_version(int $post_id, string $change_summary = ''): // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.SlowDBQuery -- Admin-only postmeta operations on revisions $wpdb->delete($wpdb->postmeta, [ 'post_id' => $rev->ID, - 'meta_key' => '_opentrust_version', + 'meta_key' => '_ettic_otc_version', ]); $wpdb->insert($wpdb->postmeta, [ 'post_id' => $rev->ID, - 'meta_key' => '_opentrust_version', + 'meta_key' => '_ettic_otc_version', 'meta_value' => (string) $current_version, ]); // Copy old version's summary and effective date to the revision. - foreach (['_opentrust_version_summary', '_opentrust_policy_effective_date'] as $meta_key) { + foreach (['_ettic_otc_version_summary', '_ettic_otc_policy_effective_date'] as $meta_key) { $old_val = get_post_meta($post_id, $meta_key, true); if ($old_val) { $wpdb->delete($wpdb->postmeta, [ @@ -97,13 +97,13 @@ public static function bump_version(int $post_id, string $change_summary = ''): } // Bump the main post to the new version. - update_post_meta($post_id, '_opentrust_version', $new_version); + update_post_meta($post_id, '_ettic_otc_version', $new_version); // Store change summary for this version. if ($change_summary !== '') { - update_post_meta($post_id, '_opentrust_version_summary', $change_summary); + update_post_meta($post_id, '_ettic_otc_version_summary', $change_summary); } else { - delete_post_meta($post_id, '_opentrust_version_summary'); + delete_post_meta($post_id, '_ettic_otc_version_summary'); } } @@ -111,9 +111,9 @@ public static function bump_version(int $post_id, string $change_summary = ''): * Ensure a first-publish post gets v1. */ public static function ensure_initial_version(int $post_id): void { - $version = get_post_meta($post_id, '_opentrust_version', true); + $version = get_post_meta($post_id, '_ettic_otc_version', true); if (!$version) { - update_post_meta($post_id, '_opentrust_version', 1); + update_post_meta($post_id, '_ettic_otc_version', 1); } } @@ -123,53 +123,53 @@ public static function ensure_initial_version(int $post_id): void { public function add_version_history_meta_box(): void { add_meta_box( - 'opentrust_version_history', - __('Version History', 'opentrust'), + 'ettic_otc_version_history', + __('Version History', 'open-trust-center-by-ettic'), [$this, 'render_version_history'], - OpenTrust_CPT::POLICY, + Ettic_OTC_CPT::POLICY, 'side', 'default' ); } public function render_version_history(\WP_Post $post): void { - $current_version = (int) get_post_meta($post->ID, '_opentrust_version', true) ?: 1; + $current_version = (int) get_post_meta($post->ID, '_ettic_otc_version', true) ?: 1; $revisions = wp_get_post_revisions($post->ID, [ 'orderby' => 'ID', 'order' => 'DESC', ]); - $settings = OpenTrust::get_settings(); - $slug = $settings['endpoint_slug'] ?? OpenTrust::DEFAULT_ENDPOINT_SLUG; + $settings = Ettic_OTC::get_settings(); + $slug = $settings['endpoint_slug'] ?? Ettic_OTC::DEFAULT_ENDPOINT_SLUG; $post_slug = $post->post_name ?: sanitize_title($post->post_title); if (empty($revisions)) { printf( '

%s v%d

', - esc_html__('Current version:', 'opentrust'), + esc_html__('Current version:', 'open-trust-center-by-ettic'), (int) $current_version ); - echo '

' . esc_html__('Version history will appear after the first update.', 'opentrust') . '

'; + echo '

' . esc_html__('Version history will appear after the first update.', 'open-trust-center-by-ettic') . '

'; return; } ?> -
+
- - - + + + - + - + ID, '_opentrust_version', true); + $rev_version = (int) get_post_meta($rev->ID, '_ettic_otc_version', true); if (!$rev_version) continue; if ($rev_version === $current_version) continue; @@ -180,12 +180,12 @@ public function render_version_history(\WP_Post $post): void { diff --git a/includes/class-opentrust.php b/includes/class-ettic-otc.php similarity index 55% rename from includes/class-opentrust.php rename to includes/class-ettic-otc.php index 25cca3a..382eb3b 100644 --- a/includes/class-opentrust.php +++ b/includes/class-ettic-otc.php @@ -9,7 +9,7 @@ exit; } -final class OpenTrust { +final class Ettic_OTC { /** * Default URL path the trust center mounts at when the operator hasn't @@ -33,20 +33,17 @@ private function __construct() { // Flush rewrite rules when settings change (transient flag). add_action('init', [$this, 'maybe_flush_rewrites'], 99); - // Check for DB schema upgrades. - add_action('init', [$this, 'maybe_upgrade'], 5); - - // Bump the render-cache version on any OpenTrust CPT change. Catches + // Bump the render-cache version on any Ettic_OTC CPT change. Catches // saves, deletes, trash/untrash, and publish transitions in one wire-up. - OpenTrust_CPT::register_invalidator(OpenTrust_CPT::ALL, [$this, 'invalidate_cache']); + Ettic_OTC_CPT::register_invalidator(Ettic_OTC_CPT::ALL, [$this, 'invalidate_cache']); // Boot sub-systems. - OpenTrust_CPT::instance(); - OpenTrust_Version::instance(); - OpenTrust_Chat::instance(); + Ettic_OTC_CPT::instance(); + Ettic_OTC_Version::instance(); + Ettic_OTC_Chat::instance(); if (is_admin()) { - OpenTrust_Admin::instance(); + Ettic_OTC_Admin::instance(); } } @@ -89,11 +86,11 @@ public static function defaults(): array { 'company_registration' => '', 'vat_number' => '', - // Per-site salt — written out-of-band by OpenTrust_Chat_Budget::site_salt() + // Per-site salt — written out-of-band by Ettic_OTC_Chat_Budget::site_salt() // on first access. Empty here so a fresh install starts in the // "needs lazy generation" state; once written, sanitize_settings // carries it forward byte-for-byte. - 'opentrust_site_salt' => '', + 'ettic_otc_site_salt' => '', // ── AI chat (OTC) ────────────────────────── 'ai_enabled' => false, @@ -105,11 +102,11 @@ public static function defaults(): array { // expired or the provider has deprecated the model id. 'ai_model_display_name' => '', 'ai_model_recommended' => false, - 'ai_daily_token_budget' => OpenTrust_Chat_Budget::DEFAULT_DAILY_TOKEN_BUDGET, - 'ai_monthly_token_budget' => OpenTrust_Chat_Budget::DEFAULT_MONTHLY_TOKEN_BUDGET, - 'ai_rate_limit_per_ip' => OpenTrust_Chat_Budget::DEFAULT_RATE_LIMIT_PER_IP, - 'ai_rate_limit_per_session' => OpenTrust_Chat_Budget::DEFAULT_RATE_LIMIT_PER_SESSION, - 'ai_max_message_length' => OpenTrust_Chat::DEFAULT_MAX_MESSAGE_LENGTH, + 'ai_daily_token_budget' => Ettic_OTC_Chat_Budget::DEFAULT_DAILY_TOKEN_BUDGET, + 'ai_monthly_token_budget' => Ettic_OTC_Chat_Budget::DEFAULT_MONTHLY_TOKEN_BUDGET, + 'ai_rate_limit_per_ip' => Ettic_OTC_Chat_Budget::DEFAULT_RATE_LIMIT_PER_IP, + 'ai_rate_limit_per_session' => Ettic_OTC_Chat_Budget::DEFAULT_RATE_LIMIT_PER_SESSION, + 'ai_max_message_length' => Ettic_OTC_Chat::DEFAULT_MAX_MESSAGE_LENGTH, 'ai_contact_url' => '', 'ai_show_model_attribution' => true, 'ai_logging_enabled' => true, @@ -127,7 +124,7 @@ public static function defaults(): array { } public static function get_settings(): array { - $saved = get_option('opentrust_settings', []); + $saved = get_option('ettic_otc_settings', []); return wp_parse_args($saved, self::defaults()); } @@ -141,216 +138,38 @@ public static function add_rewrite_rules(): void { add_rewrite_rule( '^' . preg_quote($slug, '/') . '/?$', - 'index.php?opentrust=main', + 'index.php?ettic_otc=main', 'top' ); add_rewrite_rule( '^' . preg_quote($slug, '/') . '/policy/([^/]+)/?$', - 'index.php?opentrust=policy&opentrust_policy_slug=$matches[1]', + 'index.php?ettic_otc=policy&ettic_otc_policy_slug=$matches[1]', 'top' ); add_rewrite_rule( '^' . preg_quote($slug, '/') . '/policy/([^/]+)/version/([0-9]+)/?$', - 'index.php?opentrust=policy_version&opentrust_policy_slug=$matches[1]&opentrust_version=$matches[2]', + 'index.php?ettic_otc=policy_version&ettic_otc_policy_slug=$matches[1]&ettic_otc_version=$matches[2]', 'top' ); // AI chat (OTC). add_rewrite_rule( '^' . preg_quote($slug, '/') . '/ask/?$', - 'index.php?opentrust=ask', + 'index.php?ettic_otc=ask', 'top' ); } public function register_query_vars(array $vars): array { - $vars[] = 'opentrust'; - $vars[] = 'opentrust_policy_slug'; - $vars[] = 'opentrust_version'; + $vars[] = 'ettic_otc'; + $vars[] = 'ettic_otc_policy_slug'; + $vars[] = 'ettic_otc_version'; return $vars; } - /** - * Schema upgrade hook. Runs on every init at priority 5. Future schema - * migrations land here, gated on the current value of opentrust_db_version - * vs. the OPENTRUST_DB_VERSION constant. opentrust_db_version is only - * advanced once, at the tail — so an interrupted migration retries cleanly - * on the next init, and every migration step must be idempotent. - */ - public function maybe_upgrade(): void { - $current = (int) get_option('opentrust_db_version', 0); - if ($current === (int) OPENTRUST_DB_VERSION) { - return; - } - - // v3 → v4: rename CPT slugs from `ot_*` to `opentr_*` to satisfy the - // wp.org 4-character-prefix rule. Runs first so any v1→v4 jump renames - // the rows before the v1→v2 UUID back-fill queries them — the back-fill - // resolves OpenTrust_CPT::ALL to the new slugs, so the rows must already - // carry those slugs by the time it runs. Runs at init priority 5, - // before OpenTrust_CPT::register_post_types() at priority 10. - if ($current < 4) { - self::rename_cpt_slugs_v4(); - } - - // v4 → v5: rename postmeta keys from `_ot_*` to `_opentrust_*` so the - // plugin no longer uses a 2-character prefix in the shared postmeta - // table. MUST run before the v1→v2 UUID back-fill below: the back-fill - // stamps `_opentrust_uuid` on any post that lacks it, so a v1 post's - // legacy `_ot_uuid` row has to be renamed to `_opentrust_uuid` first — - // otherwise the back-fill would see no `_opentrust_uuid`, add a second - // one, and the post would end up with two conflicting UUID rows. - if ($current < 5) { - self::rename_postmeta_keys_v5(); - } - - // v1 → v2: back-fill _opentrust_uuid on existing CPT posts. Runs after - // the v4→v5 rename above so it never double-stamps a post that already - // carries a (renamed) legacy UUID. - if ($current < 2) { - self::backfill_uuids(); - } - - // v2 → v3: register the daily AI-model-refresh cron and back-fill - // the active-model snapshot so existing installs don't render a raw - // id before the first cron tick. - if ($current < 3) { - OpenTrust_Admin_AI::schedule_cron(); - self::backfill_model_snapshot(); - } - - update_option('opentrust_db_version', OPENTRUST_DB_VERSION, false); - set_transient('opentrust_flush_rewrite', true); - $this->invalidate_cache(); - - // The chat corpus transient is locale-keyed and not bound to - // opentrust_cache_version, so invalidate_cache() above doesn't bust - // it. After the v4 rename the surviving corpus would be valid but - // mention the old slug context in admin telemetry — drop it so the - // next chat request rebuilds against the renamed rows. - if (class_exists('OpenTrust_Chat_Corpus')) { - OpenTrust_Chat_Corpus::invalidate(); - } - } - - /** - * Seed the active-model snapshot from the existing per-provider transient - * cache. No HTTP — runs at upgrade time only. - */ - private static function backfill_model_snapshot(): void { - $settings = self::get_settings(); - $snap = OpenTrust_Admin_AI::instance()->snapshot_for_provider( - (string) ($settings['ai_provider'] ?? ''), - (string) ($settings['ai_model'] ?? '') - ); - if ($snap === null) { - return; - } - $settings['ai_model_display_name'] = $snap['display_name']; - $settings['ai_model_recommended'] = $snap['recommended']; - OpenTrust_Admin_Settings::instance()->save_settings_raw($settings); - } - - /** - * Rewrite wp_posts.post_type for each renamed CPT. Idempotent: if the - * migration is interrupted, opentrust_db_version stays unadvanced and the - * next init retries. Revisions carry post_type='revision' and are not - * matched. Translation linkage (WPML/Polylang) is keyed by post ID, not - * by slug, so existing translations stay paired. - * - * Collects affected IDs before the UPDATE so each row's WP_Post entry in - * the object cache can be invalidated — otherwise post_type checks that - * read from cache return stale 'ot_*' values until the cache expires. - * - * @deprecated 1.1.0 Drop in 2.0.0 once v1.0.x upgrades are no longer - * supported. Also remove the `if ($current < 4)` branch in - * maybe_upgrade() and the OpenTrust_CPT::LEGACY_* constants. - */ - private static function rename_cpt_slugs_v4(): void { - global $wpdb; - foreach (OpenTrust_CPT::LEGACY_MAP as $old => $new) { - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-shot schema migration; per-row clean_post_cache() runs below + opentrust_cache_version bumped after maybe_upgrade. - $ids = $wpdb->get_col( - $wpdb->prepare("SELECT ID FROM {$wpdb->posts} WHERE post_type = %s", $old) - ); - if (empty($ids)) { - continue; - } - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-shot schema migration; per-row clean_post_cache() runs below + opentrust_cache_version bumped after maybe_upgrade. - $wpdb->update( - $wpdb->posts, - ['post_type' => $new], - ['post_type' => $old], - ['%s'], - ['%s'] - ); - foreach ($ids as $id) { - clean_post_cache((int) $id); - } - } - } - - /** - * Rewrite wp_postmeta.meta_key for every plugin-owned postmeta key, from - * the legacy `_ot_*` prefix to `_opentrust_*`. Idempotent: a bulk UPDATE - * keyed on the old meta_key matches nothing on a re-run, and - * opentrust_db_version stays unadvanced until the tail of maybe_upgrade() - * so an interrupted migration retries cleanly. - * - * The UPDATE is keyed on meta_key alone, so it catches postmeta on posts, - * revisions, and attachments alike. Affected post IDs are collected first - * so each WP_Post's meta cache can be invalidated — otherwise get_post_meta - * would serve stale `_ot_*` lookups until the cache expires. - * - * @deprecated 1.1.1 Drop in 2.0.0 once v1.0.x upgrades are no longer - * supported. Also remove the `if ($current < 5)` branch in - * maybe_upgrade() and OpenTrust_CPT::LEGACY_META_MAP. - */ - private static function rename_postmeta_keys_v5(): void { - global $wpdb; - foreach (OpenTrust_CPT::LEGACY_META_MAP as $old => $new) { - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-shot schema migration; per-row clean_post_cache() runs below + opentrust_cache_version bumped after maybe_upgrade. - $ids = $wpdb->get_col( - $wpdb->prepare("SELECT DISTINCT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s", $old) - ); - if (empty($ids)) { - continue; - } - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- One-shot schema migration; per-row clean_post_cache() runs below + opentrust_cache_version bumped after maybe_upgrade. - $wpdb->update( - $wpdb->postmeta, - ['meta_key' => $new], - ['meta_key' => $old], - ['%s'], - ['%s'] - ); - foreach ($ids as $id) { - clean_post_cache((int) $id); - } - } - } - - private static function backfill_uuids(): void { - $posts = get_posts([ - 'post_type' => OpenTrust_CPT::ALL, - 'post_status' => 'any', - 'posts_per_page' => -1, - 'fields' => 'ids', - 'meta_query' => [ - [ - 'key' => '_opentrust_uuid', - 'compare' => 'NOT EXISTS', - ], - ], - ]); - foreach ($posts as $post_id) { - update_post_meta((int) $post_id, '_opentrust_uuid', wp_generate_uuid4()); - } - } - public function maybe_flush_rewrites(): void { - if (get_transient('opentrust_flush_rewrite')) { - delete_transient('opentrust_flush_rewrite'); + if (get_transient('ettic_otc_flush_rewrite')) { + delete_transient('ettic_otc_flush_rewrite'); flush_rewrite_rules(); } } @@ -360,12 +179,12 @@ public function maybe_flush_rewrites(): void { // ────────────────────────────────────────────── public function maybe_render_trust_center(): void { - $page = get_query_var('opentrust'); + $page = get_query_var('ettic_otc'); if (!$page) { return; } - OpenTrust_Render::instance()->dispatch($page); + Ettic_OTC_Render::instance()->dispatch($page); exit; } @@ -375,12 +194,12 @@ public function maybe_render_trust_center(): void { public function invalidate_cache(): void { // Bump a single version counter instead of deleting locale-specific - // transient keys one by one. OpenTrust_Render::cache_key() includes + // transient keys one by one. Ettic_OTC_Render::cache_key() includes // this version in every key, so every cached locale variant is // instantly stale after the bump. Stale transients expire naturally // on their existing TTL and are garbage-collected by WordPress. - $version = (int) get_option('opentrust_cache_version', 1); - update_option('opentrust_cache_version', $version + 1, false); + $version = (int) get_option('ettic_otc_cache_version', 1); + update_option('ettic_otc_cache_version', $version + 1, false); } @@ -397,7 +216,7 @@ public function invalidate_cache(): void { public static function debug_log(string $message): void { if (defined('WP_DEBUG') && WP_DEBUG) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- debug-gated diagnostic, only fires under WP_DEBUG - error_log('[OpenTrust] ' . $message); + error_log('[Ettic_OTC] ' . $message); } } diff --git a/includes/class-opentrust-cpt.php b/includes/class-opentrust-cpt.php deleted file mode 100644 index 4bc5181..0000000 --- a/includes/class-opentrust-cpt.php +++ /dev/null @@ -1,1198 +0,0 @@ - self::POLICY, - self::LEGACY_CERTIFICATION => self::CERTIFICATION, - self::LEGACY_SUBPROCESSOR => self::SUBPROCESSOR, - self::LEGACY_DATA_PRACTICE => self::DATA_PRACTICE, - self::LEGACY_FAQ => self::FAQ, - ]; - - /** - * Legacy postmeta keys from v1.0–v1.1, mapped old `_ot_*` → new - * `_opentrust_*`. The 2-character `_ot_` prefix collided too easily in the - * shared wp_postmeta table; v5 of the schema renames every plugin-owned - * key. Single source of truth for the v4→v5 migration - * (OpenTrust::rename_postmeta_keys_v5()), the import back-compat remap - * (OpenTrust_IO::remap_legacy_meta_keys()), and uninstall cleanup. - * - * @deprecated 1.1.1 Drop in 2.0.0 once v1.x upgrades are no longer - * supported, alongside the `if ($current < 5)` migration branch. - */ - public const LEGACY_META_MAP = [ - // Shared identity. - '_ot_uuid' => '_opentrust_uuid', - // Certifications. - '_ot_cert_type' => '_opentrust_cert_type', - '_ot_cert_issuing_body' => '_opentrust_cert_issuing_body', - '_ot_cert_status' => '_opentrust_cert_status', - '_ot_cert_issue_date' => '_opentrust_cert_issue_date', - '_ot_cert_expiry_date' => '_opentrust_cert_expiry_date', - '_ot_cert_badge_id' => '_opentrust_cert_badge_id', - '_ot_cert_artifact_id' => '_opentrust_cert_artifact_id', - '_ot_cert_description' => '_opentrust_cert_description', - // Policies. - '_ot_policy_ref_id' => '_opentrust_policy_ref_id', - '_ot_policy_category' => '_opentrust_policy_category', - '_ot_policy_effective_date' => '_opentrust_policy_effective_date', - '_ot_policy_review_date' => '_opentrust_policy_review_date', - '_ot_policy_sort_order' => '_opentrust_policy_sort_order', - '_ot_policy_citations' => '_opentrust_policy_citations', - '_ot_policy_attachment_id' => '_opentrust_policy_attachment_id', - '_ot_version' => '_opentrust_version', - '_ot_version_summary' => '_opentrust_version_summary', - '_ot_policy_chat_summary' => '_opentrust_policy_chat_summary', - '_ot_policy_chat_summary_updated_at' => '_opentrust_policy_chat_summary_updated_at', - '_ot_policy_chat_summary_origin' => '_opentrust_policy_chat_summary_origin', - // Subprocessors. - '_ot_sub_purpose' => '_opentrust_sub_purpose', - '_ot_sub_data_processed' => '_opentrust_sub_data_processed', - '_ot_sub_country' => '_opentrust_sub_country', - '_ot_sub_website' => '_opentrust_sub_website', - '_ot_sub_dpa_signed' => '_opentrust_sub_dpa_signed', - // Data practices. - '_ot_dp_data_items' => '_opentrust_dp_data_items', - '_ot_dp_purpose' => '_opentrust_dp_purpose', - '_ot_dp_legal_basis' => '_opentrust_dp_legal_basis', - '_ot_dp_retention_period' => '_opentrust_dp_retention_period', - '_ot_dp_shared_with' => '_opentrust_dp_shared_with', - '_ot_dp_sort_order' => '_opentrust_dp_sort_order', - '_ot_dp_collected' => '_opentrust_dp_collected', - '_ot_dp_stored' => '_opentrust_dp_stored', - '_ot_dp_shared' => '_opentrust_dp_shared', - '_ot_dp_sold' => '_opentrust_dp_sold', - '_ot_dp_encrypted' => '_opentrust_dp_encrypted', - '_ot_dp_data_type' => '_opentrust_dp_data_type', - '_ot_dp_collection_method' => '_opentrust_dp_collection_method', - '_ot_dp_is_sensitive' => '_opentrust_dp_is_sensitive', - // FAQs. - '_ot_faq_related_policy' => '_opentrust_faq_related_policy', - // Catalog seeding + import dedupe. - '_ot_seed_slug' => '_opentrust_seed_slug', - '_ot_seeded' => '_opentrust_seeded', - '_ot_import_sha256' => '_opentrust_import_sha256', - ]; - - /** - * Every OpenTrust CPT slug, in the order they appear in the trust center - * page. Drives the render cache invalidator and the admin submenu fixer. - */ - public const ALL = [ - self::POLICY, - self::CERTIFICATION, - self::SUBPROCESSOR, - self::DATA_PRACTICE, - self::FAQ, - ]; - - /** - * The four CPTs whose content is indexed in the AI chat corpus. FAQs are - * intentionally excluded — they're not part of the retrieval surface. - */ - public const CORPUS = [ - self::POLICY, - self::CERTIFICATION, - self::SUBPROCESSOR, - self::DATA_PRACTICE, - ]; - - private static ?self $instance = null; - - public static function instance(): self { - return self::$instance ??= new self(); - } - - private function __construct() { - add_action('init', [self::class, 'register_post_types']); - add_action('add_meta_boxes', [$this, 'add_meta_boxes']); - add_action('save_post', [$this, 'ensure_uuid'], 5, 2); - add_action('save_post', [$this, 'save_meta'], 10, 2); - - // Admin columns. - add_filter('manage_' . self::CERTIFICATION . '_posts_columns', [$this, 'cert_columns']); - add_action('manage_' . self::CERTIFICATION . '_posts_custom_column', [$this, 'cert_column_content'], 10, 2); - add_filter('manage_' . self::POLICY . '_posts_columns', [$this, 'policy_columns']); - add_action('manage_' . self::POLICY . '_posts_custom_column', [$this, 'policy_column_content'], 10, 2); - add_filter('manage_' . self::SUBPROCESSOR . '_posts_columns', [$this, 'sub_columns']); - add_action('manage_' . self::SUBPROCESSOR . '_posts_custom_column', [$this, 'sub_column_content'], 10, 2); - add_filter('manage_' . self::DATA_PRACTICE . '_posts_columns', [$this, 'dp_columns']); - add_action('manage_' . self::DATA_PRACTICE . '_posts_custom_column', [$this, 'dp_column_content'], 10, 2); - add_filter('manage_' . self::FAQ . '_posts_columns', [$this, 'faq_columns']); - add_action('manage_' . self::FAQ . '_posts_custom_column', [$this, 'faq_column_content'], 10, 2); - - // Catalog-autofill title-field prompt for subprocessor / data-practice CPTs. - add_filter('enter_title_here', [$this, 'filter_enter_title_here'], 10, 2); - - // Policies get a curated block palette — focused writing surface, - // no marketing blocks, no layout chaos. - add_filter('allowed_block_types_all', [self::class, 'filter_policy_allowed_blocks'], 10, 2); - } - - /** - * Wire a flush callback to every event that can change the rendered or - * indexed output of one of the listed CPTs. Single registration point so - * the render cache and the chat corpus stay aligned on what counts as a - * cache-busting event: - * - * - save_post_{cpt} for inserts and updates - * - deleted_post / trashed_post / for content removal - * untrashed_post (filtered by post_type) - * - transition_post_status for publish ↔ draft / private - * transitions (filtered by CPT) - * - * The $callback may take any arity — extra args are silently ignored, so - * the same callable can be registered with both single-arg hooks (delete) - * and three-arg hooks (transition_post_status). - * - * @param array $cpts CPT slugs (e.g. self::ALL or self::CORPUS). - */ - public static function register_invalidator(array $cpts, callable $callback): void { - foreach ($cpts as $cpt) { - add_action("save_post_{$cpt}", $callback); - } - - $on_post_event = static function ($post_id) use ($cpts, $callback): void { - if (in_array(get_post_type((int) $post_id), $cpts, true)) { - $callback($post_id); - } - }; - add_action('deleted_post', $on_post_event); - add_action('trashed_post', $on_post_event); - add_action('untrashed_post', $on_post_event); - - add_action( - 'transition_post_status', - static function (string $new, string $old, \WP_Post $post) use ($cpts, $callback): void { - if (!in_array($post->post_type, $cpts, true)) { - return; - } - if ($new === 'publish' || $old === 'publish') { - $callback($post->ID); - } - }, - 10, - 3 - ); - } - - /** - * Replace the "Add title" prompt on subprocessor and data-practice new-post - * screens so users know the title field is also a catalog lookup. - */ - public function filter_enter_title_here(string $text, \WP_Post $post): string { - if ($post->post_type === self::SUBPROCESSOR) { - return __('Pick from the catalog or type your own subprocessor name', 'opentrust'); - } - if ($post->post_type === self::DATA_PRACTICE) { - return __('Pick from the catalog or type your own, e.g. Analytics or Transactional Email', 'opentrust'); - } - if ($post->post_type === self::CERTIFICATION) { - return __('Pick from the catalog or type your own, e.g. SOC 2 Type II or ISO 27001', 'opentrust'); - } - return $text; - } - - // ────────────────────────────────────────────── - // CPT Registration - // ────────────────────────────────────────────── - - public static function register_post_types(): void { - // ── Policies ── - register_post_type(self::POLICY, [ - 'labels' => [ - 'name' => __('Policies', 'opentrust'), - 'singular_name' => __('Policy', 'opentrust'), - 'add_new' => __('Add Policy', 'opentrust'), - 'add_new_item' => __('Add New Policy', 'opentrust'), - 'edit_item' => __('Edit Policy', 'opentrust'), - 'new_item' => __('New Policy', 'opentrust'), - 'view_item' => __('View Policy', 'opentrust'), - 'search_items' => __('Search Policies', 'opentrust'), - 'not_found' => __('No policies found.', 'opentrust'), - 'not_found_in_trash' => __('No policies in trash.', 'opentrust'), - 'all_items' => __('Policies', 'opentrust'), - ], - // public=>true is required so WPML and Polylang detect the post type - // as translatable content and offer per-language variants in their UIs. - // publicly_queryable + exclude_from_search + has_archive + rewrite are - // all disabled so the CPTs never leak to the frontend — the trust - // center stays the single rendering surface via template_redirect. - 'public' => true, - 'publicly_queryable' => false, - 'exclude_from_search' => true, - 'show_in_nav_menus' => false, - 'show_ui' => true, - 'show_in_menu' => 'opentrust', - 'show_in_rest' => true, - 'supports' => ['title', 'editor', 'revisions', 'excerpt'], - 'has_archive' => false, - 'rewrite' => false, - 'menu_icon' => 'dashicons-media-document', - 'menu_position' => 31, - ]); - - // ── Certifications ── - register_post_type(self::CERTIFICATION, [ - 'labels' => [ - 'name' => __('Certifications', 'opentrust'), - 'singular_name' => __('Certification', 'opentrust'), - 'add_new' => __('Add Certification', 'opentrust'), - 'add_new_item' => __('Add New Certification', 'opentrust'), - 'edit_item' => __('Edit Certification', 'opentrust'), - 'new_item' => __('New Certification', 'opentrust'), - 'search_items' => __('Search Certifications', 'opentrust'), - 'not_found' => __('No certifications found.', 'opentrust'), - 'not_found_in_trash' => __('No certifications in trash.', 'opentrust'), - 'all_items' => __('Certifications', 'opentrust'), - ], - // public=>true is required so WPML and Polylang detect the post type - // as translatable content and offer per-language variants in their UIs. - // publicly_queryable + exclude_from_search + has_archive + rewrite are - // all disabled so the CPTs never leak to the frontend — the trust - // center stays the single rendering surface via template_redirect. - 'public' => true, - 'publicly_queryable' => false, - 'exclude_from_search' => true, - 'show_in_nav_menus' => false, - 'show_ui' => true, - 'show_in_menu' => 'opentrust', - 'show_in_rest' => true, - 'supports' => ['title'], - 'has_archive' => false, - 'rewrite' => false, - 'menu_icon' => 'dashicons-awards', - 'menu_position' => 32, - ]); - - // ── Subprocessors ── - register_post_type(self::SUBPROCESSOR, [ - 'labels' => [ - 'name' => __('Subprocessors', 'opentrust'), - 'singular_name' => __('Subprocessor', 'opentrust'), - 'add_new' => __('Add Subprocessor', 'opentrust'), - 'add_new_item' => __('Add New Subprocessor', 'opentrust'), - 'edit_item' => __('Edit Subprocessor', 'opentrust'), - 'new_item' => __('New Subprocessor', 'opentrust'), - 'search_items' => __('Search Subprocessors', 'opentrust'), - 'not_found' => __('No subprocessors found.', 'opentrust'), - 'not_found_in_trash' => __('No subprocessors in trash.', 'opentrust'), - 'all_items' => __('Subprocessors', 'opentrust'), - ], - // public=>true is required so WPML and Polylang detect the post type - // as translatable content and offer per-language variants in their UIs. - // publicly_queryable + exclude_from_search + has_archive + rewrite are - // all disabled so the CPTs never leak to the frontend — the trust - // center stays the single rendering surface via template_redirect. - 'public' => true, - 'publicly_queryable' => false, - 'exclude_from_search' => true, - 'show_in_nav_menus' => false, - 'show_ui' => true, - 'show_in_menu' => 'opentrust', - 'show_in_rest' => true, - 'supports' => ['title'], - 'has_archive' => false, - 'rewrite' => false, - 'menu_icon' => 'dashicons-networking', - 'menu_position' => 33, - ]); - - // ── Data Practices ── - register_post_type(self::DATA_PRACTICE, [ - 'labels' => [ - 'name' => __('Data Practices', 'opentrust'), - 'singular_name' => __('Data Practice', 'opentrust'), - 'add_new' => __('Add Data Practice', 'opentrust'), - 'add_new_item' => __('Add New Data Practice', 'opentrust'), - 'edit_item' => __('Edit Data Practice', 'opentrust'), - 'new_item' => __('New Data Practice', 'opentrust'), - 'search_items' => __('Search Data Practices', 'opentrust'), - 'not_found' => __('No data practices found.', 'opentrust'), - 'not_found_in_trash' => __('No data practices in trash.', 'opentrust'), - 'all_items' => __('Data Practices', 'opentrust'), - ], - // public=>true is required so WPML and Polylang detect the post type - // as translatable content and offer per-language variants in their UIs. - // publicly_queryable + exclude_from_search + has_archive + rewrite are - // all disabled so the CPTs never leak to the frontend — the trust - // center stays the single rendering surface via template_redirect. - 'public' => true, - 'publicly_queryable' => false, - 'exclude_from_search' => true, - 'show_in_nav_menus' => false, - 'show_ui' => true, - 'show_in_menu' => 'opentrust', - 'show_in_rest' => true, - 'supports' => ['title'], - 'has_archive' => false, - 'rewrite' => false, - 'menu_icon' => 'dashicons-database', - 'menu_position' => 34, - ]); - - // ── FAQs ── - register_post_type(self::FAQ, [ - 'labels' => [ - 'name' => __('FAQs', 'opentrust'), - 'singular_name' => __('FAQ', 'opentrust'), - 'add_new' => __('Add FAQ', 'opentrust'), - 'add_new_item' => __('Add New FAQ', 'opentrust'), - 'edit_item' => __('Edit FAQ', 'opentrust'), - 'new_item' => __('New FAQ', 'opentrust'), - 'view_item' => __('View FAQ', 'opentrust'), - 'search_items' => __('Search FAQs', 'opentrust'), - 'not_found' => __('No FAQs found.', 'opentrust'), - 'not_found_in_trash' => __('No FAQs in trash.', 'opentrust'), - 'all_items' => __('FAQs', 'opentrust'), - ], - 'public' => true, - 'publicly_queryable' => false, - 'exclude_from_search' => true, - 'show_in_nav_menus' => false, - 'show_ui' => true, - 'show_in_menu' => 'opentrust', - 'show_in_rest' => true, - 'supports' => ['title', 'editor', 'page-attributes'], - 'has_archive' => false, - 'rewrite' => false, - 'menu_icon' => 'dashicons-format-chat', - 'menu_position' => 35, - ]); - } - - // ────────────────────────────────────────────── - // Meta Boxes - // ────────────────────────────────────────────── - - public function add_meta_boxes(): void { - add_meta_box('opentrust_cert_details', __('Certification Details', 'opentrust'), [$this, 'render_cert_meta_box'], self::CERTIFICATION, 'normal', 'high'); - add_meta_box('opentrust_policy_details', __('Policy Details', 'opentrust'), [$this, 'render_policy_meta_box'], self::POLICY, 'side', 'high'); - add_meta_box('opentrust_sub_details', __('Subprocessor Details', 'opentrust'), [$this, 'render_sub_meta_box'], self::SUBPROCESSOR, 'normal', 'high'); - add_meta_box('opentrust_dp_details', __('Data Practice Details', 'opentrust'), [$this, 'render_dp_meta_box'], self::DATA_PRACTICE, 'normal', 'high'); - add_meta_box('opentrust_faq_details', __('FAQ Details', 'opentrust'), [$this, 'render_faq_meta_box'], self::FAQ, 'side', 'high'); - } - - // ── Certification meta box ── - - public function render_cert_meta_box(\WP_Post $post): void { - wp_nonce_field('opentrust_save_cert', 'opentrust_cert_nonce'); - - $type = get_post_meta($post->ID, '_opentrust_cert_type', true) ?: 'compliant'; - $issuing_body = get_post_meta($post->ID, '_opentrust_cert_issuing_body', true) ?: ''; - $status = get_post_meta($post->ID, '_opentrust_cert_status', true) ?: 'active'; - $issue_date = get_post_meta($post->ID, '_opentrust_cert_issue_date', true) ?: ''; - $expiry_date = get_post_meta($post->ID, '_opentrust_cert_expiry_date', true) ?: ''; - $badge_id = (int) get_post_meta($post->ID, '_opentrust_cert_badge_id', true); - $badge_url = $badge_id ? wp_get_attachment_image_url($badge_id, 'thumbnail') : ''; - $description = get_post_meta($post->ID, '_opentrust_cert_description', true) ?: ''; - $artifact_id = (int) get_post_meta($post->ID, '_opentrust_cert_artifact_id', true); - $artifact_url = $artifact_id ? wp_get_attachment_url($artifact_id) : ''; - $artifact_name = $artifact_id ? get_the_title($artifact_id) : ''; - - $types = [ - 'certified' => __('Audited certification (issued by a third party)', 'opentrust'), - 'compliant' => __('Self-attested alignment (no external audit)', 'opentrust'), - ]; - - $statuses = [ - 'active' => __('Active / currently met', 'opentrust'), - 'in_progress' => __('In progress', 'opentrust'), - 'expired' => __('Expired / lapsed', 'opentrust'), - ]; - ?> -
- - -

-
- -
- - -

-
- -
- - -
- -
- - -
- -
- - -
- -
- - - - - -

-
- -
- -
- - -
- - - -

-
- -
- - -

-
- post) || $context->post->post_type !== self::POLICY) { - return $allowed; - } - return [ - 'core/paragraph', - 'core/heading', - 'core/list', - 'core/list-item', - 'core/table', - 'core/quote', - 'core/separator', - 'core/image', - 'core/code', - 'core/details', - ]; - } - - // ── Policy meta box (sidebar) ── - - public function render_policy_meta_box(\WP_Post $post): void { - wp_nonce_field('opentrust_save_policy', 'opentrust_policy_nonce'); - - $ref_id = get_post_meta($post->ID, '_opentrust_policy_ref_id', true) ?: ''; - $category = get_post_meta($post->ID, '_opentrust_policy_category', true) ?: 'other'; - $effective_date = get_post_meta($post->ID, '_opentrust_policy_effective_date', true) ?: ''; - $review_date = get_post_meta($post->ID, '_opentrust_policy_review_date', true) ?: ''; - $sort_order = metadata_exists('post', $post->ID, '_opentrust_policy_sort_order') - ? (int) get_post_meta($post->ID, '_opentrust_policy_sort_order', true) - : 10; - $version = (int) get_post_meta($post->ID, '_opentrust_version', true) ?: 1; - - $citations = get_post_meta($post->ID, '_opentrust_policy_citations', true); - $citations = is_array($citations) ? $citations : []; - - $attachment_id = (int) get_post_meta($post->ID, '_opentrust_policy_attachment_id', true); - $attachment_url = $attachment_id ? wp_get_attachment_url($attachment_id) : ''; - $attachment_name = $attachment_id ? get_the_title($attachment_id) : ''; - - $categories = OpenTrust_Render::policy_category_labels(); - ?> -
-

- -

-

- -

-
- - post_status): ?> -
- - -
- - -
- - -

-
- -
- - -
- -
- - -
- -
- - -
- -
- -
- $ot_citation): - $ot_citation_name = is_array($ot_citation) ? ($ot_citation['name'] ?? '') : (string) $ot_citation; - ?> - - - - - - - -
-

-
- -
- -
- - -
- - - -

-
- -
- - -

-
- ID, '_opentrust_sub_purpose', true) ?: ''; - $data_processed = get_post_meta($post->ID, '_opentrust_sub_data_processed', true) ?: ''; - $country = get_post_meta($post->ID, '_opentrust_sub_country', true) ?: ''; - $website = get_post_meta($post->ID, '_opentrust_sub_website', true) ?: ''; - $dpa_signed = (bool) get_post_meta($post->ID, '_opentrust_sub_dpa_signed', true); - ?> -
- - -

-
- -
- - -

-
- -
- - -
- -
- - -
- -
- -

-
- ID, '_opentrust_dp_data_items', true); - $data_items = is_array($data_items) ? $data_items : []; - $purpose = get_post_meta($post->ID, '_opentrust_dp_purpose', true) ?: ''; - $legal_basis = get_post_meta($post->ID, '_opentrust_dp_legal_basis', true) ?: ''; - $retention_period = get_post_meta($post->ID, '_opentrust_dp_retention_period', true) ?: ''; - $shared_with = get_post_meta($post->ID, '_opentrust_dp_shared_with', true); - $shared_with = is_array($shared_with) ? $shared_with : []; - $sort_order = (int) get_post_meta($post->ID, '_opentrust_dp_sort_order', true); - - $prop_collected = (bool) get_post_meta($post->ID, '_opentrust_dp_collected', true); - $prop_stored = (bool) get_post_meta($post->ID, '_opentrust_dp_stored', true); - $prop_shared = (bool) get_post_meta($post->ID, '_opentrust_dp_shared', true); - $prop_sold = (bool) get_post_meta($post->ID, '_opentrust_dp_sold', true); - $prop_encrypted = (bool) get_post_meta($post->ID, '_opentrust_dp_encrypted', true); - - $basis_options = OpenTrust_Render::legal_basis_labels(); - ?> - - -
- -
- $item): ?> - - - - - - - -
-
- - -
- - -
- - -
-
- - -
-
- - -
-
- - -
- -
- $entry): ?> - - - - - - - -
-
- - -
- -
- - - - - -
-

-
- - -
- - -

-
- post_type, self::ALL, true)) { - return; - } - if (get_post_meta($post_id, '_opentrust_uuid', true)) { - return; - } - update_post_meta($post_id, '_opentrust_uuid', wp_generate_uuid4()); - } - - // ────────────────────────────────────────────── - // Save Meta - // ────────────────────────────────────────────── - - public function save_meta(int $post_id, \WP_Post $post): void { - if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { - return; - } - - match ($post->post_type) { - self::CERTIFICATION => $this->save_cert_meta($post_id), - self::POLICY => $this->save_policy_meta($post_id), - self::SUBPROCESSOR => $this->save_sub_meta($post_id), - self::DATA_PRACTICE => $this->save_dp_meta($post_id), - self::FAQ => $this->save_faq_meta($post_id), - default => null, - }; - } - - private function save_cert_meta(int $post_id): void { - if (!isset($_POST['opentrust_cert_nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['opentrust_cert_nonce'] ) ), 'opentrust_save_cert')) { - return; - } - if (!current_user_can('edit_post', $post_id)) { - return; - } - - $valid_types = ['certified', 'compliant']; - $type = sanitize_text_field( wp_unslash( $_POST['opentrust_cert_type'] ?? 'compliant' ) ); - update_post_meta($post_id, '_opentrust_cert_type', in_array($type, $valid_types, true) ? $type : 'compliant'); - - update_post_meta($post_id, '_opentrust_cert_issuing_body', sanitize_text_field( wp_unslash( $_POST['opentrust_cert_issuing_body'] ?? '' ) )); - - $valid_statuses = ['active', 'in_progress', 'expired']; - $status = sanitize_text_field( wp_unslash( $_POST['opentrust_cert_status'] ?? 'active' ) ); - update_post_meta($post_id, '_opentrust_cert_status', in_array($status, $valid_statuses, true) ? $status : 'active'); - - update_post_meta($post_id, '_opentrust_cert_issue_date', sanitize_text_field( wp_unslash( $_POST['opentrust_cert_issue_date'] ?? '' ) )); - update_post_meta($post_id, '_opentrust_cert_expiry_date', sanitize_text_field( wp_unslash( $_POST['opentrust_cert_expiry_date'] ?? '' ) )); - update_post_meta($post_id, '_opentrust_cert_badge_id', absint( wp_unslash( $_POST['opentrust_cert_badge_id'] ?? 0 ) )); - update_post_meta($post_id, '_opentrust_cert_artifact_id', absint( wp_unslash( $_POST['opentrust_cert_artifact_id'] ?? 0 ) )); - update_post_meta($post_id, '_opentrust_cert_description', sanitize_textarea_field( wp_unslash( $_POST['opentrust_cert_description'] ?? '' ) )); - } - - private function save_policy_meta(int $post_id): void { - if (!isset($_POST['opentrust_policy_nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['opentrust_policy_nonce'] ) ), 'opentrust_save_policy')) { - return; - } - if (!current_user_can('edit_post', $post_id)) { - return; - } - - // Version bump — only when explicitly requested by the user. - if (!empty($_POST['opentrust_publish_new_version'])) { - $post = get_post($post_id); - if ($post && 'publish' === $post->post_status) { - $summary = sanitize_text_field( wp_unslash( $_POST['opentrust_version_summary'] ?? '' ) ); - OpenTrust_Version::bump_version($post_id, $summary); - } - } - - // Ensure first-publish posts get v1. - OpenTrust_Version::ensure_initial_version($post_id); - - $ref_id = sanitize_text_field( wp_unslash( $_POST['opentrust_policy_ref_id'] ?? '' ) ); - // Collapse internal whitespace runs so "POL 012" becomes "POL 012" on save. - $ref_id = trim((string) preg_replace('/\s+/u', ' ', $ref_id)); - if ($ref_id !== '') { - update_post_meta($post_id, '_opentrust_policy_ref_id', $ref_id); - } else { - delete_post_meta($post_id, '_opentrust_policy_ref_id'); - } - - $valid_categories = ['security', 'privacy', 'compliance', 'operational', 'other']; - $category = sanitize_text_field( wp_unslash( $_POST['opentrust_policy_category'] ?? 'other' ) ); - update_post_meta($post_id, '_opentrust_policy_category', in_array($category, $valid_categories, true) ? $category : 'other'); - - update_post_meta($post_id, '_opentrust_policy_effective_date', sanitize_text_field( wp_unslash( $_POST['opentrust_policy_effective_date'] ?? '' ) )); - update_post_meta($post_id, '_opentrust_policy_review_date', sanitize_text_field( wp_unslash( $_POST['opentrust_policy_review_date'] ?? '' ) )); - update_post_meta($post_id, '_opentrust_policy_sort_order', absint( wp_unslash( $_POST['opentrust_policy_sort_order'] ?? 0 ) )); - - // Framework citations — repeater array, shape mirrors opentrust_dp_data_items. - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Each element is individually sanitized below. - $raw_citations = wp_unslash( $_POST['opentrust_policy_citations'] ?? [] ); - $citations = []; - if (is_array($raw_citations)) { - foreach ($raw_citations as $entry) { - $name = sanitize_text_field(is_array($entry) ? ($entry['name'] ?? '') : (string) $entry); - $name = trim((string) preg_replace('/\s+/u', ' ', $name)); - if ($name !== '') { - $citations[] = ['name' => $name]; - } - } - } - if (!empty($citations)) { - update_post_meta($post_id, '_opentrust_policy_citations', $citations); - } else { - delete_post_meta($post_id, '_opentrust_policy_citations'); - } - - // PDF attachment — only accept a real attachment the user can read. - $attachment_id = absint( wp_unslash( $_POST['opentrust_policy_attachment_id'] ?? 0 ) ); - if ($attachment_id > 0 && get_post_type($attachment_id) === 'attachment') { - update_post_meta($post_id, '_opentrust_policy_attachment_id', $attachment_id); - } else { - delete_post_meta($post_id, '_opentrust_policy_attachment_id'); - } - } - - private function save_sub_meta(int $post_id): void { - if (!isset($_POST['opentrust_sub_nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['opentrust_sub_nonce'] ) ), 'opentrust_save_sub')) { - return; - } - if (!current_user_can('edit_post', $post_id)) { - return; - } - - update_post_meta($post_id, '_opentrust_sub_purpose', sanitize_textarea_field( wp_unslash( $_POST['opentrust_sub_purpose'] ?? '' ) )); - update_post_meta($post_id, '_opentrust_sub_data_processed', sanitize_textarea_field( wp_unslash( $_POST['opentrust_sub_data_processed'] ?? '' ) )); - update_post_meta($post_id, '_opentrust_sub_country', sanitize_text_field( wp_unslash( $_POST['opentrust_sub_country'] ?? '' ) )); - update_post_meta($post_id, '_opentrust_sub_website', esc_url_raw( wp_unslash( $_POST['opentrust_sub_website'] ?? '' ) )); - update_post_meta($post_id, '_opentrust_sub_dpa_signed', !empty($_POST['opentrust_sub_dpa_signed'])); - } - - private function save_dp_meta(int $post_id): void { - if (!isset($_POST['opentrust_dp_nonce']) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['opentrust_dp_nonce'] ) ), 'opentrust_save_dp')) { - return; - } - if (!current_user_can('edit_post', $post_id)) { - return; - } - - // Data Items (repeater array). - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Each element is individually sanitized below. - $raw_items = wp_unslash( $_POST['opentrust_dp_data_items'] ?? [] ); - $data_items = []; - if (is_array($raw_items)) { - foreach ($raw_items as $item) { - $name = sanitize_text_field($item['name'] ?? ''); - if ($name !== '') { - $data_items[] = ['name' => $name]; - } - } - } - update_post_meta($post_id, '_opentrust_dp_data_items', $data_items); - - // Purpose. - update_post_meta($post_id, '_opentrust_dp_purpose', sanitize_textarea_field( wp_unslash( $_POST['opentrust_dp_purpose'] ?? '' ) )); - - // Legal Basis. - $valid_bases = ['consent', 'contract', 'legitimate_interest', 'legal_obligation', 'vital_interest', 'public_interest']; - $basis = sanitize_text_field( wp_unslash( $_POST['opentrust_dp_legal_basis'] ?? '' ) ); - update_post_meta($post_id, '_opentrust_dp_legal_basis', in_array($basis, $valid_bases, true) ? $basis : ''); - - // Retention Period. - update_post_meta($post_id, '_opentrust_dp_retention_period', sanitize_text_field( wp_unslash( $_POST['opentrust_dp_retention_period'] ?? '' ) )); - - // Shared With (repeater array). - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Each element is individually sanitized below. - $raw_shared = wp_unslash( $_POST['opentrust_dp_shared_with'] ?? [] ); - $shared_items = []; - if (is_array($raw_shared)) { - foreach ($raw_shared as $entry) { - $name = sanitize_text_field($entry['name'] ?? ''); - if ($name !== '') { - $shared_items[] = ['name' => $name]; - } - } - } - update_post_meta($post_id, '_opentrust_dp_shared_with', $shared_items); - - // Sort order. - update_post_meta($post_id, '_opentrust_dp_sort_order', absint( wp_unslash( $_POST['opentrust_dp_sort_order'] ?? 0 ) )); - - // Property flags — the AI assistant reports these verbatim. Unchecked - // means explicit "No", not "unknown", so we always write the value. - update_post_meta($post_id, '_opentrust_dp_collected', !empty($_POST['opentrust_dp_collected'])); - update_post_meta($post_id, '_opentrust_dp_stored', !empty($_POST['opentrust_dp_stored'])); - update_post_meta($post_id, '_opentrust_dp_shared', !empty($_POST['opentrust_dp_shared'])); - update_post_meta($post_id, '_opentrust_dp_sold', !empty($_POST['opentrust_dp_sold'])); - update_post_meta($post_id, '_opentrust_dp_encrypted', !empty($_POST['opentrust_dp_encrypted'])); - - // Clean up legacy meta keys. - delete_post_meta($post_id, '_opentrust_dp_data_type'); - delete_post_meta($post_id, '_opentrust_dp_collection_method'); - delete_post_meta($post_id, '_opentrust_dp_is_sensitive'); - } - - // ────────────────────────────────────────────── - // Admin Columns - // ────────────────────────────────────────────── - - // Certifications - public function cert_columns(array $columns): array { - $new = []; - $new['cb'] = $columns['cb']; - $new['title'] = $columns['title']; - $new['opentrust_issuing_body'] = __('Issuing Body', 'opentrust'); - $new['opentrust_status'] = __('Status', 'opentrust'); - $new['opentrust_expiry'] = __('Expiry Date', 'opentrust'); - $new['date'] = $columns['date']; - return $new; - } - - public function cert_column_content(string $column, int $post_id): void { - match ($column) { - 'opentrust_issuing_body' => print(esc_html(get_post_meta($post_id, '_opentrust_cert_issuing_body', true) ?: '—')), - 'opentrust_status' => (function () use ($post_id): void { - $status = get_post_meta($post_id, '_opentrust_cert_status', true) ?: 'active'; - $type = get_post_meta($post_id, '_opentrust_cert_type', true) ?: 'compliant'; - $labels = $type === 'compliant' - ? OpenTrust_Render::cert_aligned_status_labels() - : OpenTrust_Render::cert_status_labels(); - $swatch = match ($status) { - 'active' => 'background:#dcfce7;color:#166534', - 'in_progress' => 'background:#fef9c3;color:#854d0e', - 'expired' => 'background:#f3f4f6;color:#6b7280', - default => '', - }; - printf( - '%3$s', - esc_attr($status), - esc_attr($swatch), - esc_html($labels[$status] ?? '') - ); - })(), - 'opentrust_expiry' => print(esc_html(get_post_meta($post_id, '_opentrust_cert_expiry_date', true) ?: '—')), - default => null, - }; - } - - // Policies - public function policy_columns(array $columns): array { - $new = []; - $new['cb'] = $columns['cb']; - $new['title'] = $columns['title']; - $new['opentrust_ref_id'] = __('ID', 'opentrust'); - $new['opentrust_category'] = __('Category', 'opentrust'); - $new['opentrust_version'] = __('Version', 'opentrust'); - $new['opentrust_pdf'] = __('PDF', 'opentrust'); - $new['date'] = $columns['date']; - return $new; - } - - public function policy_column_content(string $column, int $post_id): void { - // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Match arms emit either hard-coded HTML or values already passed through esc_html(); PHPCS misreads the IIFE/match-expression syntax. - match ($column) { - 'opentrust_ref_id' => (function () use ($post_id): void { - $ref = (string) get_post_meta($post_id, '_opentrust_policy_ref_id', true); - if ($ref === '') { - print ''; - return; - } - printf('%s', esc_html($ref)); - })(), - 'opentrust_category' => print(esc_html(OpenTrust_Render::policy_category_labels()[get_post_meta($post_id, '_opentrust_policy_category', true) ?: 'other'] ?? '')), - 'opentrust_version' => printf('v%s', esc_html((string) ((int) get_post_meta($post_id, '_opentrust_version', true) ?: 1))), - 'opentrust_pdf' => print(((int) get_post_meta($post_id, '_opentrust_policy_attachment_id', true)) > 0 ? '' : ''), - default => null, - }; - // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped - } - - // Subprocessors - public function sub_columns(array $columns): array { - $new = []; - $new['cb'] = $columns['cb']; - $new['title'] = $columns['title']; - $new['opentrust_purpose'] = __('Purpose', 'opentrust'); - $new['opentrust_country'] = __('Location', 'opentrust'); - $new['opentrust_dpa'] = __('DPA', 'opentrust'); - $new['date'] = $columns['date']; - return $new; - } - - public function sub_column_content(string $column, int $post_id): void { - match ($column) { - 'opentrust_purpose' => print(esc_html(wp_trim_words(get_post_meta($post_id, '_opentrust_sub_purpose', true) ?: '', 10))), - 'opentrust_country' => print(esc_html(get_post_meta($post_id, '_opentrust_sub_country', true) ?: '—')), - 'opentrust_dpa' => print((bool) get_post_meta($post_id, '_opentrust_sub_dpa_signed', true) ? '' : '—'), - default => null, - }; - } - - // Data Practices - public function dp_columns(array $columns): array { - $new = []; - $new['cb'] = $columns['cb']; - $new['title'] = $columns['title']; - $new['opentrust_dp_items'] = __('Data Items', 'opentrust'); - $new['opentrust_dp_sort'] = __('Order', 'opentrust'); - $new['date'] = $columns['date']; - return $new; - } - - public function dp_column_content(string $column, int $post_id): void { - match ($column) { - 'opentrust_dp_items' => print(esc_html((string) count((array) (get_post_meta($post_id, '_opentrust_dp_data_items', true) ?: [])))), - 'opentrust_dp_sort' => print(esc_html((string) ((int) get_post_meta($post_id, '_opentrust_dp_sort_order', true)))), - default => null, - }; - } - - // ── FAQ meta box ── - - public function render_faq_meta_box(\WP_Post $post): void { - wp_nonce_field('opentrust_save_faq', 'opentrust_faq_nonce'); - - $policy_id = (int) get_post_meta($post->ID, '_opentrust_faq_related_policy', true); - - $policies = get_posts([ - 'post_type' => self::POLICY, - 'posts_per_page' => -1, - 'post_status' => 'publish', - 'orderby' => 'title', - 'order' => 'ASC', - ]); - ?> -
- - -

-
- -
-

- - -

-
- 0 && get_post_type($related) === self::POLICY) { - update_post_meta($post_id, '_opentrust_faq_related_policy', $related); - } else { - delete_post_meta($post_id, '_opentrust_faq_related_policy'); - } - } - - // FAQs - public function faq_columns(array $columns): array { - $new = []; - $new['cb'] = $columns['cb']; - $new['title'] = $columns['title']; - $new['opentrust_faq_order'] = __('Order', 'opentrust'); - $new['date'] = $columns['date']; - return $new; - } - - public function faq_column_content(string $column, int $post_id): void { - match ($column) { - 'opentrust_faq_order' => print(esc_html((string) ((int) (get_post($post_id)->menu_order ?? 0)))), - default => null, - }; - } -} diff --git a/includes/data/certification-catalog.php b/includes/data/certification-catalog.php index 031eb3d..56c16a4 100644 --- a/includes/data/certification-catalog.php +++ b/includes/data/certification-catalog.php @@ -3,18 +3,18 @@ * Certification catalog. * * Curated list of common compliance frameworks, regulations, and - * certifications shown in the admin typeahead on the opentr_certification - * create screen. Each entry sets `_opentrust_cert_type` to either `certified` + * certifications shown in the admin typeahead on the eotc_certification + * create screen. Each entry sets `_ettic_otc_cert_type` to either `certified` * (third-party audited, has a certificate with dates and an issuing body) * or `compliant` (self-attested adherence to a standard or regulation). * - * Certified entries include `_opentrust_cert_issuing_body`. Compliant entries + * Certified entries include `_ettic_otc_cert_issuing_body`. Compliant entries * deliberately omit it because there is no auditor. * * Never auto-filled: issue date, expiry date, status, badge. Those are * user specific. * - * Extend without forking via the `opentrust_certification_catalog` filter. + * Extend without forking via the `ettic_otc_certification_catalog` filter. */ declare(strict_types=1); @@ -29,9 +29,9 @@ 'name' => 'SOC 2 Type I', 'aliases' => [ 'soc2 type 1', 'soc 2 type 1', 'soc2 type i' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'Point in time audit of the design of controls against the AICPA Trust Services Criteria (Security, Availability, Processing Integrity, Confidentiality, Privacy).', - '_opentrust_cert_issuing_body' => 'AICPA', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'Point in time audit of the design of controls against the AICPA Trust Services Criteria (Security, Availability, Processing Integrity, Confidentiality, Privacy).', + '_ettic_otc_cert_issuing_body' => 'AICPA', ], 'fields_review' => [], ], @@ -40,9 +40,9 @@ 'name' => 'SOC 2 Type II', 'aliases' => [ 'soc2 type 2', 'soc 2 type 2', 'soc2', 'soc 2' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'Audit of the design and operating effectiveness of controls over a period (typically 6 to 12 months) against the AICPA Trust Services Criteria.', - '_opentrust_cert_issuing_body' => 'AICPA', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'Audit of the design and operating effectiveness of controls over a period (typically 6 to 12 months) against the AICPA Trust Services Criteria.', + '_ettic_otc_cert_issuing_body' => 'AICPA', ], 'fields_review' => [], ], @@ -51,9 +51,9 @@ 'name' => 'SOC 1 Type II', 'aliases' => [ 'soc1 type 2', 'soc 1', 'ssae 18' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'Audit of controls relevant to a service organization\'s impact on user entities\' internal control over financial reporting, conducted under SSAE 18.', - '_opentrust_cert_issuing_body' => 'AICPA', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'Audit of controls relevant to a service organization\'s impact on user entities\' internal control over financial reporting, conducted under SSAE 18.', + '_ettic_otc_cert_issuing_body' => 'AICPA', ], 'fields_review' => [], ], @@ -62,9 +62,9 @@ 'name' => 'ISO/IEC 27001', 'aliases' => [ 'iso 27001', 'iso27001', 'isms' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'International standard for establishing, implementing, and maintaining an Information Security Management System (ISMS).', - '_opentrust_cert_issuing_body' => 'ISO/IEC accredited certification body', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'International standard for establishing, implementing, and maintaining an Information Security Management System (ISMS).', + '_ettic_otc_cert_issuing_body' => 'ISO/IEC accredited certification body', ], 'fields_review' => [], ], @@ -73,9 +73,9 @@ 'name' => 'ISO/IEC 27017', 'aliases' => [ 'iso 27017', 'iso27017' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'Code of practice providing additional information security controls for cloud service providers and cloud customers, extending ISO/IEC 27002.', - '_opentrust_cert_issuing_body' => 'ISO/IEC accredited certification body', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'Code of practice providing additional information security controls for cloud service providers and cloud customers, extending ISO/IEC 27002.', + '_ettic_otc_cert_issuing_body' => 'ISO/IEC accredited certification body', ], 'fields_review' => [], ], @@ -84,9 +84,9 @@ 'name' => 'ISO/IEC 27018', 'aliases' => [ 'iso 27018', 'iso27018' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'Code of practice for the protection of personally identifiable information (PII) in public clouds acting as PII processors.', - '_opentrust_cert_issuing_body' => 'ISO/IEC accredited certification body', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'Code of practice for the protection of personally identifiable information (PII) in public clouds acting as PII processors.', + '_ettic_otc_cert_issuing_body' => 'ISO/IEC accredited certification body', ], 'fields_review' => [], ], @@ -95,9 +95,9 @@ 'name' => 'ISO/IEC 27701', 'aliases' => [ 'iso 27701', 'iso27701', 'pims' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'Extension to ISO/IEC 27001 specifying requirements for a Privacy Information Management System (PIMS) covering PII controllers and processors.', - '_opentrust_cert_issuing_body' => 'ISO/IEC accredited certification body', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'Extension to ISO/IEC 27001 specifying requirements for a Privacy Information Management System (PIMS) covering PII controllers and processors.', + '_ettic_otc_cert_issuing_body' => 'ISO/IEC accredited certification body', ], 'fields_review' => [], ], @@ -106,9 +106,9 @@ 'name' => 'ISO 9001', 'aliases' => [ 'iso9001', 'quality management' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'International standard for Quality Management Systems (QMS), covering processes for consistent product and service delivery and continual improvement.', - '_opentrust_cert_issuing_body' => 'ISO accredited certification body', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'International standard for Quality Management Systems (QMS), covering processes for consistent product and service delivery and continual improvement.', + '_ettic_otc_cert_issuing_body' => 'ISO accredited certification body', ], 'fields_review' => [], ], @@ -117,8 +117,8 @@ 'name' => 'HIPAA', 'aliases' => [ 'hipaa', 'health insurance portability', 'phi' ], 'fields' => [ - '_opentrust_cert_type' => 'compliant', - '_opentrust_cert_description' => 'US federal law governing the protection and confidential handling of individually identifiable health information (PHI) by covered entities and business associates.', + '_ettic_otc_cert_type' => 'compliant', + '_ettic_otc_cert_description' => 'US federal law governing the protection and confidential handling of individually identifiable health information (PHI) by covered entities and business associates.', ], 'fields_review' => [], ], @@ -127,9 +127,9 @@ 'name' => 'PCI DSS', 'aliases' => [ 'pci', 'pci-dss', 'payment card industry' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'Payment Card Industry Data Security Standard defining technical and operational requirements for organizations that store, process, or transmit cardholder data.', - '_opentrust_cert_issuing_body' => 'PCI Security Standards Council', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'Payment Card Industry Data Security Standard defining technical and operational requirements for organizations that store, process, or transmit cardholder data.', + '_ettic_otc_cert_issuing_body' => 'PCI Security Standards Council', ], 'fields_review' => [], ], @@ -138,8 +138,8 @@ 'name' => 'GDPR', 'aliases' => [ 'gdpr', 'general data protection regulation', 'eu gdpr' ], 'fields' => [ - '_opentrust_cert_type' => 'compliant', - '_opentrust_cert_description' => 'European Union regulation governing the processing of personal data of individuals in the EU and EEA, including data subject rights and cross-border transfers.', + '_ettic_otc_cert_type' => 'compliant', + '_ettic_otc_cert_description' => 'European Union regulation governing the processing of personal data of individuals in the EU and EEA, including data subject rights and cross-border transfers.', ], 'fields_review' => [], ], @@ -148,8 +148,8 @@ 'name' => 'NIS2', 'aliases' => [ 'nis2', 'nis 2', 'network and information security directive', 'eu nis2' ], 'fields' => [ - '_opentrust_cert_type' => 'compliant', - '_opentrust_cert_description' => 'European Union directive on measures for a high common level of cybersecurity across the Union, setting risk management, incident reporting, and supply chain security requirements for essential and important entities.', + '_ettic_otc_cert_type' => 'compliant', + '_ettic_otc_cert_description' => 'European Union directive on measures for a high common level of cybersecurity across the Union, setting risk management, incident reporting, and supply chain security requirements for essential and important entities.', ], 'fields_review' => [], ], @@ -158,8 +158,8 @@ 'name' => 'CCPA', 'aliases' => [ 'ccpa', 'cpra', 'california consumer privacy act' ], 'fields' => [ - '_opentrust_cert_type' => 'compliant', - '_opentrust_cert_description' => 'California state law granting consumers rights over personal information collected by businesses, as amended by the California Privacy Rights Act (CPRA).', + '_ettic_otc_cert_type' => 'compliant', + '_ettic_otc_cert_description' => 'California state law granting consumers rights over personal information collected by businesses, as amended by the California Privacy Rights Act (CPRA).', ], 'fields_review' => [], ], @@ -168,9 +168,9 @@ 'name' => 'HITRUST CSF', 'aliases' => [ 'hitrust', 'hitrust csf', 'csf' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'Certifiable framework that harmonizes requirements from HIPAA, ISO, NIST, PCI, and other standards into a single set of controls for managing information risk.', - '_opentrust_cert_issuing_body' => 'HITRUST Alliance', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'Certifiable framework that harmonizes requirements from HIPAA, ISO, NIST, PCI, and other standards into a single set of controls for managing information risk.', + '_ettic_otc_cert_issuing_body' => 'HITRUST Alliance', ], 'fields_review' => [], ], @@ -179,9 +179,9 @@ 'name' => 'Cyber Essentials', 'aliases' => [ 'ce', 'uk cyber essentials' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'UK government-backed scheme covering five technical controls (firewalls, secure configuration, access control, malware protection, patch management), verified by self-assessment.', - '_opentrust_cert_issuing_body' => 'IASME (on behalf of UK NCSC)', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'UK government-backed scheme covering five technical controls (firewalls, secure configuration, access control, malware protection, patch management), verified by self-assessment.', + '_ettic_otc_cert_issuing_body' => 'IASME (on behalf of UK NCSC)', ], 'fields_review' => [], ], @@ -190,9 +190,9 @@ 'name' => 'Cyber Essentials Plus', 'aliases' => [ 'ce+', 'ce plus', 'cyber essentials +' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'Higher assurance tier of Cyber Essentials, adding hands-on technical verification and vulnerability testing of the same five control areas by an accredited assessor.', - '_opentrust_cert_issuing_body' => 'IASME (on behalf of UK NCSC)', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'Higher assurance tier of Cyber Essentials, adding hands-on technical verification and vulnerability testing of the same five control areas by an accredited assessor.', + '_ettic_otc_cert_issuing_body' => 'IASME (on behalf of UK NCSC)', ], 'fields_review' => [], ], @@ -201,9 +201,9 @@ 'name' => 'FedRAMP', 'aliases' => [ 'fedramp', 'federal risk and authorization management program' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'US government program that standardizes security assessment, authorization, and continuous monitoring for cloud products and services used by federal agencies.', - '_opentrust_cert_issuing_body' => 'US General Services Administration (GSA)', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'US government program that standardizes security assessment, authorization, and continuous monitoring for cloud products and services used by federal agencies.', + '_ettic_otc_cert_issuing_body' => 'US General Services Administration (GSA)', ], 'fields_review' => [], ], @@ -212,9 +212,9 @@ 'name' => 'CMMC', 'aliases' => [ 'cmmc', 'cybersecurity maturity model certification' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'US Department of Defense framework certifying contractor cybersecurity practices for the protection of Federal Contract Information (FCI) and Controlled Unclassified Information (CUI).', - '_opentrust_cert_issuing_body' => 'Cyber AB (on behalf of US DoD)', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'US Department of Defense framework certifying contractor cybersecurity practices for the protection of Federal Contract Information (FCI) and Controlled Unclassified Information (CUI).', + '_ettic_otc_cert_issuing_body' => 'Cyber AB (on behalf of US DoD)', ], 'fields_review' => [], ], @@ -223,9 +223,9 @@ 'name' => 'TISAX', 'aliases' => [ 'tisax', 'trusted information security assessment exchange' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'Information security assessment and exchange mechanism developed for the automotive industry, based on the VDA ISA catalog.', - '_opentrust_cert_issuing_body' => 'ENX Association', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'Information security assessment and exchange mechanism developed for the automotive industry, based on the VDA ISA catalog.', + '_ettic_otc_cert_issuing_body' => 'ENX Association', ], 'fields_review' => [], ], @@ -234,9 +234,9 @@ 'name' => 'BSI C5', 'aliases' => [ 'c5', 'cloud computing compliance criteria catalogue' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'German government catalog of minimum baseline security requirements for cloud service providers, assessed through an independent audit report.', - '_opentrust_cert_issuing_body' => 'Germany Federal Office for Information Security (BSI)', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'German government catalog of minimum baseline security requirements for cloud service providers, assessed through an independent audit report.', + '_ettic_otc_cert_issuing_body' => 'Germany Federal Office for Information Security (BSI)', ], 'fields_review' => [], ], @@ -245,9 +245,9 @@ 'name' => 'IRAP', 'aliases' => [ 'irap', 'information security registered assessors program' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'Australian government assessment of an ICT system against the Information Security Manual (ISM), performed by endorsed assessors for use by Australian government entities.', - '_opentrust_cert_issuing_body' => 'Australian Signals Directorate (ASD)', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'Australian government assessment of an ICT system against the Information Security Manual (ISM), performed by endorsed assessors for use by Australian government entities.', + '_ettic_otc_cert_issuing_body' => 'Australian Signals Directorate (ASD)', ], 'fields_review' => [], ], @@ -256,9 +256,9 @@ 'name' => 'HDS', 'aliases' => [ 'hds', 'hebergeur de donnees de sante', 'health data hosting' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'French certification required for hosting personal health data, covering infrastructure and application hosting activities under the French Public Health Code.', - '_opentrust_cert_issuing_body' => 'French Agence du Numerique en Sante (ANS)', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'French certification required for hosting personal health data, covering infrastructure and application hosting activities under the French Public Health Code.', + '_ettic_otc_cert_issuing_body' => 'French Agence du Numerique en Sante (ANS)', ], 'fields_review' => [], ], @@ -267,8 +267,8 @@ 'name' => 'CSA STAR Level 1', 'aliases' => [ 'star level 1', 'csa star 1', 'caiq' ], 'fields' => [ - '_opentrust_cert_type' => 'compliant', - '_opentrust_cert_description' => 'Self-assessment published to the Cloud Security Alliance STAR registry, documenting cloud security controls against the Cloud Controls Matrix (CCM) and CAIQ.', + '_ettic_otc_cert_type' => 'compliant', + '_ettic_otc_cert_description' => 'Self-assessment published to the Cloud Security Alliance STAR registry, documenting cloud security controls against the Cloud Controls Matrix (CCM) and CAIQ.', ], 'fields_review' => [], ], @@ -277,9 +277,9 @@ 'name' => 'CSA STAR Level 2', 'aliases' => [ 'star level 2', 'csa star 2', 'star certification' ], 'fields' => [ - '_opentrust_cert_type' => 'certified', - '_opentrust_cert_description' => 'Third-party audit of cloud security controls against the Cloud Controls Matrix, performed alongside an ISO/IEC 27001 certification or a SOC 2 attestation.', - '_opentrust_cert_issuing_body' => 'Cloud Security Alliance (via accredited auditor)', + '_ettic_otc_cert_type' => 'certified', + '_ettic_otc_cert_description' => 'Third-party audit of cloud security controls against the Cloud Controls Matrix, performed alongside an ISO/IEC 27001 certification or a SOC 2 attestation.', + '_ettic_otc_cert_issuing_body' => 'Cloud Security Alliance (via accredited auditor)', ], 'fields_review' => [], ], diff --git a/includes/data/data-practice-catalog.php b/includes/data/data-practice-catalog.php index 18108b0..a01636f 100644 --- a/includes/data/data-practice-catalog.php +++ b/includes/data/data-practice-catalog.php @@ -3,14 +3,14 @@ * Data-practice catalog. * * Templates for common data-practice categories. Used by the admin typeahead - * on the opentr_data_practice create screen. Every entry is a starting point — + * on the eotc_data_practice create screen. Every entry is a starting point — * users must review legal basis, retention, and shared-with lists before * publishing. All template fields live under `fields_review` so the UI marks * them as "verify before publishing". * - * Extend without forking via the `opentrust_data_practice_catalog` filter. + * Extend without forking via the `ettic_otc_data_practice_catalog` filter. * - * Legal basis values must match keys in OpenTrust_Render::legal_basis_labels(). + * Legal basis values must match keys in Ettic_OTC_Render::legal_basis_labels(). */ declare(strict_types=1); @@ -26,11 +26,11 @@ 'aliases' => [ 'analytics', 'web analytics', 'site analytics', 'ga', 'google analytics' ], 'fields' => [], 'fields_review' => [ - '_opentrust_dp_purpose' => 'Measure website traffic, understand visitor behavior, and improve content and navigation.', - '_opentrust_dp_legal_basis' => 'legitimate_interest', - '_opentrust_dp_retention_period' => '14 months', - '_opentrust_dp_data_items' => [ 'IP address', 'Browser and device', 'Page views', 'Referrer', 'Session duration' ], - '_opentrust_dp_shared_with' => [ 'Analytics provider' ], + '_ettic_otc_dp_purpose' => 'Measure website traffic, understand visitor behavior, and improve content and navigation.', + '_ettic_otc_dp_legal_basis' => 'legitimate_interest', + '_ettic_otc_dp_retention_period' => '14 months', + '_ettic_otc_dp_data_items' => [ 'IP address', 'Browser and device', 'Page views', 'Referrer', 'Session duration' ], + '_ettic_otc_dp_shared_with' => [ 'Analytics provider' ], ], ], @@ -39,11 +39,11 @@ 'aliases' => [ 'telemetry', 'product analytics', 'usage analytics', 'events' ], 'fields' => [], 'fields_review' => [ - '_opentrust_dp_purpose' => 'Track in-product feature usage to prioritize improvements and diagnose problems.', - '_opentrust_dp_legal_basis' => 'legitimate_interest', - '_opentrust_dp_retention_period' => '24 months', - '_opentrust_dp_data_items' => [ 'User ID', 'Feature events', 'Session metadata', 'Device and OS' ], - '_opentrust_dp_shared_with' => [ 'Product analytics provider' ], + '_ettic_otc_dp_purpose' => 'Track in-product feature usage to prioritize improvements and diagnose problems.', + '_ettic_otc_dp_legal_basis' => 'legitimate_interest', + '_ettic_otc_dp_retention_period' => '24 months', + '_ettic_otc_dp_data_items' => [ 'User ID', 'Feature events', 'Session metadata', 'Device and OS' ], + '_ettic_otc_dp_shared_with' => [ 'Product analytics provider' ], ], ], @@ -52,11 +52,11 @@ 'aliases' => [ 'error tracking', 'crash reporting', 'sentry', 'bug tracking', 'exceptions' ], 'fields' => [], 'fields_review' => [ - '_opentrust_dp_purpose' => 'Detect and diagnose application errors to maintain service reliability and security.', - '_opentrust_dp_legal_basis' => 'legitimate_interest', - '_opentrust_dp_retention_period' => '90 days', - '_opentrust_dp_data_items' => [ 'Stack trace', 'User ID', 'Request URL', 'Browser and device' ], - '_opentrust_dp_shared_with' => [ 'Error monitoring provider' ], + '_ettic_otc_dp_purpose' => 'Detect and diagnose application errors to maintain service reliability and security.', + '_ettic_otc_dp_legal_basis' => 'legitimate_interest', + '_ettic_otc_dp_retention_period' => '90 days', + '_ettic_otc_dp_data_items' => [ 'Stack trace', 'User ID', 'Request URL', 'Browser and device' ], + '_ettic_otc_dp_shared_with' => [ 'Error monitoring provider' ], ], ], @@ -65,11 +65,11 @@ 'aliases' => [ 'email', 'service email', 'notifications', 'receipts', 'password reset' ], 'fields' => [], 'fields_review' => [ - '_opentrust_dp_purpose' => 'Send account-related and service-related emails such as receipts, password resets, and notifications.', - '_opentrust_dp_legal_basis' => 'contract', - '_opentrust_dp_retention_period' => 'Duration of account', - '_opentrust_dp_data_items' => [ 'Email address', 'Name', 'Email content' ], - '_opentrust_dp_shared_with' => [ 'Email delivery provider' ], + '_ettic_otc_dp_purpose' => 'Send account-related and service-related emails such as receipts, password resets, and notifications.', + '_ettic_otc_dp_legal_basis' => 'contract', + '_ettic_otc_dp_retention_period' => 'Duration of account', + '_ettic_otc_dp_data_items' => [ 'Email address', 'Name', 'Email content' ], + '_ettic_otc_dp_shared_with' => [ 'Email delivery provider' ], ], ], @@ -78,11 +78,11 @@ 'aliases' => [ 'newsletter', 'marketing', 'promotional email', 'email marketing' ], 'fields' => [], 'fields_review' => [ - '_opentrust_dp_purpose' => 'Send product updates, newsletters, and promotional messages to users who have opted in.', - '_opentrust_dp_legal_basis' => 'consent', - '_opentrust_dp_retention_period' => 'Until unsubscribe', - '_opentrust_dp_data_items' => [ 'Email address', 'Name', 'Subscription preferences' ], - '_opentrust_dp_shared_with' => [ 'Email marketing provider' ], + '_ettic_otc_dp_purpose' => 'Send product updates, newsletters, and promotional messages to users who have opted in.', + '_ettic_otc_dp_legal_basis' => 'consent', + '_ettic_otc_dp_retention_period' => 'Until unsubscribe', + '_ettic_otc_dp_data_items' => [ 'Email address', 'Name', 'Subscription preferences' ], + '_ettic_otc_dp_shared_with' => [ 'Email marketing provider' ], ], ], @@ -91,11 +91,11 @@ 'aliases' => [ 'support', 'helpdesk', 'help desk', 'tickets', 'customer service' ], 'fields' => [], 'fields_review' => [ - '_opentrust_dp_purpose' => 'Respond to customer inquiries and maintain a record of support interactions.', - '_opentrust_dp_legal_basis' => 'contract', - '_opentrust_dp_retention_period' => '3 years after case closure', - '_opentrust_dp_data_items' => [ 'Name', 'Email address', 'Message contents', 'Account ID' ], - '_opentrust_dp_shared_with' => [ 'Support platform provider' ], + '_ettic_otc_dp_purpose' => 'Respond to customer inquiries and maintain a record of support interactions.', + '_ettic_otc_dp_legal_basis' => 'contract', + '_ettic_otc_dp_retention_period' => '3 years after case closure', + '_ettic_otc_dp_data_items' => [ 'Name', 'Email address', 'Message contents', 'Account ID' ], + '_ettic_otc_dp_shared_with' => [ 'Support platform provider' ], ], ], @@ -104,11 +104,11 @@ 'aliases' => [ 'payments', 'billing', 'stripe', 'subscriptions', 'checkout' ], 'fields' => [], 'fields_review' => [ - '_opentrust_dp_purpose' => 'Process payments for subscriptions or one-time purchases and manage billing records.', - '_opentrust_dp_legal_basis' => 'contract', - '_opentrust_dp_retention_period' => '7 years (tax and accounting obligation)', - '_opentrust_dp_data_items' => [ 'Name', 'Billing address', 'Email address', 'Payment method (tokenized)', 'Transaction history' ], - '_opentrust_dp_shared_with' => [ 'Payment processor' ], + '_ettic_otc_dp_purpose' => 'Process payments for subscriptions or one-time purchases and manage billing records.', + '_ettic_otc_dp_legal_basis' => 'contract', + '_ettic_otc_dp_retention_period' => '7 years (tax and accounting obligation)', + '_ettic_otc_dp_data_items' => [ 'Name', 'Billing address', 'Email address', 'Payment method (tokenized)', 'Transaction history' ], + '_ettic_otc_dp_shared_with' => [ 'Payment processor' ], ], ], @@ -117,11 +117,11 @@ 'aliases' => [ 'auth', 'login', 'sign in', 'signup', 'account', 'session' ], 'fields' => [], 'fields_review' => [ - '_opentrust_dp_purpose' => 'Create and maintain user accounts, authenticate sign-in attempts, and secure sessions.', - '_opentrust_dp_legal_basis' => 'contract', - '_opentrust_dp_retention_period' => 'Duration of account + 30 days', - '_opentrust_dp_data_items' => [ 'Email address', 'Password (hashed)', 'Session tokens', 'Last sign-in IP' ], - '_opentrust_dp_shared_with' => [ 'Authentication provider' ], + '_ettic_otc_dp_purpose' => 'Create and maintain user accounts, authenticate sign-in attempts, and secure sessions.', + '_ettic_otc_dp_legal_basis' => 'contract', + '_ettic_otc_dp_retention_period' => 'Duration of account + 30 days', + '_ettic_otc_dp_data_items' => [ 'Email address', 'Password (hashed)', 'Session tokens', 'Last sign-in IP' ], + '_ettic_otc_dp_shared_with' => [ 'Authentication provider' ], ], ], @@ -130,11 +130,11 @@ 'aliases' => [ 'session recording', 'replay', 'fullstory', 'logrocket', 'heatmaps' ], 'fields' => [], 'fields_review' => [ - '_opentrust_dp_purpose' => 'Record anonymized user sessions to diagnose usability issues and improve the product.', - '_opentrust_dp_legal_basis' => 'legitimate_interest', - '_opentrust_dp_retention_period' => '30 days', - '_opentrust_dp_data_items' => [ 'Mouse movements', 'Click events', 'Page navigation', 'Screen size' ], - '_opentrust_dp_shared_with' => [ 'Session replay provider' ], + '_ettic_otc_dp_purpose' => 'Record anonymized user sessions to diagnose usability issues and improve the product.', + '_ettic_otc_dp_legal_basis' => 'legitimate_interest', + '_ettic_otc_dp_retention_period' => '30 days', + '_ettic_otc_dp_data_items' => [ 'Mouse movements', 'Click events', 'Page navigation', 'Screen size' ], + '_ettic_otc_dp_shared_with' => [ 'Session replay provider' ], ], ], @@ -143,11 +143,11 @@ 'aliases' => [ 'crm', 'contacts', 'hubspot', 'salesforce', 'pipedrive' ], 'fields' => [], 'fields_review' => [ - '_opentrust_dp_purpose' => 'Manage customer relationships and track sales and account interactions.', - '_opentrust_dp_legal_basis' => 'legitimate_interest', - '_opentrust_dp_retention_period' => 'Duration of relationship + 3 years', - '_opentrust_dp_data_items' => [ 'Name', 'Email address', 'Company', 'Job title', 'Interaction history' ], - '_opentrust_dp_shared_with' => [ 'CRM provider' ], + '_ettic_otc_dp_purpose' => 'Manage customer relationships and track sales and account interactions.', + '_ettic_otc_dp_legal_basis' => 'legitimate_interest', + '_ettic_otc_dp_retention_period' => 'Duration of relationship + 3 years', + '_ettic_otc_dp_data_items' => [ 'Name', 'Email address', 'Company', 'Job title', 'Interaction history' ], + '_ettic_otc_dp_shared_with' => [ 'CRM provider' ], ], ], @@ -156,11 +156,11 @@ 'aliases' => [ 'audit log', 'security log', 'access log', 'siem' ], 'fields' => [], 'fields_review' => [ - '_opentrust_dp_purpose' => 'Detect and investigate security events, prevent abuse, and meet compliance obligations.', - '_opentrust_dp_legal_basis' => 'legitimate_interest', - '_opentrust_dp_retention_period' => '12 months', - '_opentrust_dp_data_items' => [ 'User ID', 'IP address', 'Action performed', 'Timestamp', 'User agent' ], - '_opentrust_dp_shared_with' => [ 'Logging and SIEM providers' ], + '_ettic_otc_dp_purpose' => 'Detect and investigate security events, prevent abuse, and meet compliance obligations.', + '_ettic_otc_dp_legal_basis' => 'legitimate_interest', + '_ettic_otc_dp_retention_period' => '12 months', + '_ettic_otc_dp_data_items' => [ 'User ID', 'IP address', 'Action performed', 'Timestamp', 'User agent' ], + '_ettic_otc_dp_shared_with' => [ 'Logging and SIEM providers' ], ], ], @@ -169,11 +169,11 @@ 'aliases' => [ 'hosting', 'cloud', 'aws', 'gcp', 'server' ], 'fields' => [], 'fields_review' => [ - '_opentrust_dp_purpose' => 'Store and serve application data, run backend services, and deliver the product to users.', - '_opentrust_dp_legal_basis' => 'contract', - '_opentrust_dp_retention_period' => 'Duration of account', - '_opentrust_dp_data_items' => [ 'All user-submitted data', 'Account information', 'Application state' ], - '_opentrust_dp_shared_with' => [ 'Cloud infrastructure provider' ], + '_ettic_otc_dp_purpose' => 'Store and serve application data, run backend services, and deliver the product to users.', + '_ettic_otc_dp_legal_basis' => 'contract', + '_ettic_otc_dp_retention_period' => 'Duration of account', + '_ettic_otc_dp_data_items' => [ 'All user-submitted data', 'Account information', 'Application state' ], + '_ettic_otc_dp_shared_with' => [ 'Cloud infrastructure provider' ], ], ], diff --git a/includes/data/faq-catalog.php b/includes/data/faq-catalog.php index 216d747..5d065a0 100644 --- a/includes/data/faq-catalog.php +++ b/includes/data/faq-catalog.php @@ -2,7 +2,7 @@ /** * Default FAQ catalog. * - * Generic, content-agnostic glossary entries seeded into the opentr_faq CPT on + * Generic, content-agnostic glossary entries seeded into the eotc_faq CPT on * first plugin activation. These explain universal trust-center concepts and * make no claims about the specific company running the plugin. * @@ -13,8 +13,8 @@ * block at seed time and stored as post_content).', * ] * - * Filterable via the `opentrust_faq_catalog` filter. Because seeding is gated - * by the `opentrust_faqs_seeded` option, editing this file after first + * Filterable via the `ettic_otc_faq_catalog` filter. Because seeding is gated + * by the `ettic_otc_faqs_seeded` option, editing this file after first * activation has no effect on existing installs. */ @@ -26,52 +26,52 @@ return [ 'what-is-a-trust-center' => [ - 'question' => __( 'What is a trust center?', 'opentrust' ), - 'answer' => __( 'A trust center is a public page where a company shares information about how it handles security, privacy, and compliance. It usually includes security policies, a list of subprocessors, compliance certifications, and details about how customer data is handled. The goal is to give customers, partners, and prospects one place to answer due diligence questions without having to email anyone.', 'opentrust' ), + 'question' => __( 'What is a trust center?', 'open-trust-center-by-ettic' ), + 'answer' => __( 'A trust center is a public page where a company shares information about how it handles security, privacy, and compliance. It usually includes security policies, a list of subprocessors, compliance certifications, and details about how customer data is handled. The goal is to give customers, partners, and prospects one place to answer due diligence questions without having to email anyone.', 'open-trust-center-by-ettic' ), ], 'what-is-a-dpa' => [ - 'question' => __( 'What is a Data Processing Agreement (DPA)?', 'opentrust' ), - 'answer' => __( 'A Data Processing Agreement, or DPA, is a contract between a company that collects personal data and a company that processes that data on its behalf. It defines what data can be processed, for what purpose, how long it can be kept, and what security measures must be in place. Under privacy laws like the GDPR, a DPA is required whenever one company processes personal data for another.', 'opentrust' ), + 'question' => __( 'What is a Data Processing Agreement (DPA)?', 'open-trust-center-by-ettic' ), + 'answer' => __( 'A Data Processing Agreement, or DPA, is a contract between a company that collects personal data and a company that processes that data on its behalf. It defines what data can be processed, for what purpose, how long it can be kept, and what security measures must be in place. Under privacy laws like the GDPR, a DPA is required whenever one company processes personal data for another.', 'open-trust-center-by-ettic' ), ], 'what-is-a-subprocessor' => [ - 'question' => __( 'What is a subprocessor?', 'opentrust' ), - 'answer' => __( 'A subprocessor is a third-party service that a company uses to help deliver its product, and that may come into contact with customer data along the way. Common examples include cloud hosting providers, email delivery services, analytics platforms, and customer support tools. Companies publish subprocessor lists so customers can see exactly which vendors may handle their data.', 'opentrust' ), + 'question' => __( 'What is a subprocessor?', 'open-trust-center-by-ettic' ), + 'answer' => __( 'A subprocessor is a third-party service that a company uses to help deliver its product, and that may come into contact with customer data along the way. Common examples include cloud hosting providers, email delivery services, analytics platforms, and customer support tools. Companies publish subprocessor lists so customers can see exactly which vendors may handle their data.', 'open-trust-center-by-ettic' ), ], 'controller-vs-processor' => [ - 'question' => __( 'What is the difference between a data controller and a data processor?', 'opentrust' ), - 'answer' => __( "The data controller is the party that decides why and how personal data is collected and used. The data processor is the party that handles that data on the controller's behalf, following the controller's instructions. A SaaS customer is usually the controller of their end-user data, while the SaaS vendor acts as the processor. Each role carries different legal responsibilities under privacy laws like the GDPR.", 'opentrust' ), + 'question' => __( 'What is the difference between a data controller and a data processor?', 'open-trust-center-by-ettic' ), + 'answer' => __( "The data controller is the party that decides why and how personal data is collected and used. The data processor is the party that handles that data on the controller's behalf, following the controller's instructions. A SaaS customer is usually the controller of their end-user data, while the SaaS vendor acts as the processor. Each role carries different legal responsibilities under privacy laws like the GDPR.", 'open-trust-center-by-ettic' ), ], 'what-is-personal-data' => [ - 'question' => __( 'What is personal data?', 'opentrust' ), - 'answer' => __( 'Personal data is any information that can be used to identify a living person, either on its own or when combined with other information. Obvious examples include names, email addresses, phone numbers, and home addresses. Less obvious examples include IP addresses, device identifiers, cookies, and location data. Privacy laws such as the GDPR and CCPA treat personal data as something that must be collected, stored, and shared with care.', 'opentrust' ), + 'question' => __( 'What is personal data?', 'open-trust-center-by-ettic' ), + 'answer' => __( 'Personal data is any information that can be used to identify a living person, either on its own or when combined with other information. Obvious examples include names, email addresses, phone numbers, and home addresses. Less obvious examples include IP addresses, device identifiers, cookies, and location data. Privacy laws such as the GDPR and CCPA treat personal data as something that must be collected, stored, and shared with care.', 'open-trust-center-by-ettic' ), ], 'what-is-responsible-disclosure' => [ - 'question' => __( 'What is responsible disclosure?', 'opentrust' ), - 'answer' => __( 'Responsible disclosure is the practice of reporting a security vulnerability privately to the company that owns the affected system, giving them a reasonable amount of time to fix it before any details are shared publicly. It protects users from being exposed to a known issue before a patch is available. Most trust centers include a contact address or form for reporting vulnerabilities this way.', 'opentrust' ), + 'question' => __( 'What is responsible disclosure?', 'open-trust-center-by-ettic' ), + 'answer' => __( 'Responsible disclosure is the practice of reporting a security vulnerability privately to the company that owns the affected system, giving them a reasonable amount of time to fix it before any details are shared publicly. It protects users from being exposed to a known issue before a patch is available. Most trust centers include a contact address or form for reporting vulnerabilities this way.', 'open-trust-center-by-ettic' ), ], 'what-is-a-compliance-certification' => [ - 'question' => __( 'What is a compliance certification?', 'opentrust' ), - 'answer' => __( "A compliance certification is a formal statement, usually issued by an independent auditor, confirming that a company meets the requirements of a specific security or privacy standard. Certifications give customers a way to trust a company's practices without having to inspect them directly. The scope, issuing body, and validity period are typically listed alongside each certification in a trust center.", 'opentrust' ), + 'question' => __( 'What is a compliance certification?', 'open-trust-center-by-ettic' ), + 'answer' => __( "A compliance certification is a formal statement, usually issued by an independent auditor, confirming that a company meets the requirements of a specific security or privacy standard. Certifications give customers a way to trust a company's practices without having to inspect them directly. The scope, issuing body, and validity period are typically listed alongside each certification in a trust center.", 'open-trust-center-by-ettic' ), ], 'what-is-a-security-policy' => [ - 'question' => __( 'What is a security policy?', 'opentrust' ), - 'answer' => __( 'A security policy is a written document that describes how a company protects its systems, data, and people. Policies commonly cover topics like access control, incident response, acceptable use, vendor management, and business continuity. Publishing policies in a trust center lets customers see how security is handled without needing to sign an NDA first.', 'opentrust' ), + 'question' => __( 'What is a security policy?', 'open-trust-center-by-ettic' ), + 'answer' => __( 'A security policy is a written document that describes how a company protects its systems, data, and people. Policies commonly cover topics like access control, incident response, acceptable use, vendor management, and business continuity. Publishing policies in a trust center lets customers see how security is handled without needing to sign an NDA first.', 'open-trust-center-by-ettic' ), ], 'why-publish-subprocessors' => [ - 'question' => __( 'Why do companies publish a list of subprocessors?', 'opentrust' ), - 'answer' => __( 'Publishing a subprocessor list is a transparency practice, and in many cases a legal requirement, that lets customers see every third party that may handle their data. It gives customers the chance to review new vendors before they start processing data, and it makes it easier to meet their own compliance obligations. Most trust centers also offer a way to be notified when the list changes.', 'opentrust' ), + 'question' => __( 'Why do companies publish a list of subprocessors?', 'open-trust-center-by-ettic' ), + 'answer' => __( 'Publishing a subprocessor list is a transparency practice, and in many cases a legal requirement, that lets customers see every third party that may handle their data. It gives customers the chance to review new vendors before they start processing data, and it makes it easier to meet their own compliance obligations. Most trust centers also offer a way to be notified when the list changes.', 'open-trust-center-by-ettic' ), ], 'what-is-a-data-practice' => [ - 'question' => __( 'What is a data practice?', 'opentrust' ), - 'answer' => __( 'A data practice describes a specific way a company collects, uses, stores, or shares information. Each practice usually spells out what data is involved, why it is collected, how long it is kept, and who it is shared with. Grouping data practices by category, such as account data, usage data, or support data, helps customers understand exactly what happens to their information.', 'opentrust' ), + 'question' => __( 'What is a data practice?', 'open-trust-center-by-ettic' ), + 'answer' => __( 'A data practice describes a specific way a company collects, uses, stores, or shares information. Each practice usually spells out what data is involved, why it is collected, how long it is kept, and who it is shared with. Grouping data practices by category, such as account data, usage data, or support data, helps customers understand exactly what happens to their information.', 'open-trust-center-by-ettic' ), ], ]; diff --git a/includes/data/subprocessor-catalog.php b/includes/data/subprocessor-catalog.php index f18c888..0502a5c 100644 --- a/includes/data/subprocessor-catalog.php +++ b/includes/data/subprocessor-catalog.php @@ -3,26 +3,26 @@ * Subprocessor catalog. * * Curated list of common SaaS vendors with factual information that can be - * auto-filled into the opentr_subprocessor meta box from the admin typeahead. + * auto-filled into the eotc_subprocessor meta box from the admin typeahead. * * Schema per entry: * 'slug' => [ * 'name' => 'Canonical Name', * 'aliases' => ['alt', 'nickname'], - * 'fields' => [ '_opentrust_sub_*' => string ], // verified facts - * 'fields_review' => [ '_opentrust_sub_*' => string ], // templates to verify + * 'fields' => [ '_ettic_otc_sub_*' => string ], // verified facts + * 'fields_review' => [ '_ettic_otc_sub_*' => string ], // templates to verify * ] * * Rules followed by this catalog: - * 1. `_opentrust_sub_country` is included only when the HQ / processing region is + * 1. `_ettic_otc_sub_country` is included only when the HQ / processing region is * unambiguous. Multi-region cloud infra (AWS, GCP, Azure, Cloudflare, * Fastly, Vercel, etc.) deliberately omits the key so the customer picks. - * 2. `_opentrust_sub_data_processed` is always a template in `fields_review` so + * 2. `_ettic_otc_sub_data_processed` is always a template in `fields_review` so * the UI marks it for user verification before publishing. The text is * a reasonable starting point only. - * 3. `_opentrust_sub_dpa_signed` is never touched because that is contract state. + * 3. `_ettic_otc_sub_dpa_signed` is never touched because that is contract state. * - * Extend without forking via the `opentrust_subprocessor_catalog` filter. + * Extend without forking via the `ettic_otc_subprocessor_catalog` filter. */ declare(strict_types=1); @@ -37,11 +37,11 @@ 'name' => 'Amazon Web Services', 'aliases' => [ 'aws', 'amazon web services', 'amazon aws' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Cloud infrastructure, compute, storage, and managed services.', - '_opentrust_sub_website' => 'https://aws.amazon.com', + '_ettic_otc_sub_purpose' => 'Cloud infrastructure, compute, storage, and managed services.', + '_ettic_otc_sub_website' => 'https://aws.amazon.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application data, user-generated content, system logs, and backups stored across compute, storage, and database services.', + '_ettic_otc_sub_data_processed' => 'Application data, user-generated content, system logs, and backups stored across compute, storage, and database services.', ], ], @@ -49,11 +49,11 @@ 'name' => 'Google Cloud Platform', 'aliases' => [ 'gcp', 'google cloud', 'google cloud platform' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Cloud infrastructure, compute, storage, and managed services.', - '_opentrust_sub_website' => 'https://cloud.google.com', + '_ettic_otc_sub_purpose' => 'Cloud infrastructure, compute, storage, and managed services.', + '_ettic_otc_sub_website' => 'https://cloud.google.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application data, user-generated content, system logs, and backups stored across compute, storage, and database services.', + '_ettic_otc_sub_data_processed' => 'Application data, user-generated content, system logs, and backups stored across compute, storage, and database services.', ], ], @@ -61,11 +61,11 @@ 'name' => 'Microsoft Azure', 'aliases' => [ 'azure', 'microsoft azure', 'ms azure' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Cloud infrastructure, compute, storage, and managed services.', - '_opentrust_sub_website' => 'https://azure.microsoft.com', + '_ettic_otc_sub_purpose' => 'Cloud infrastructure, compute, storage, and managed services.', + '_ettic_otc_sub_website' => 'https://azure.microsoft.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application data, user-generated content, system logs, and backups stored across compute, storage, and database services.', + '_ettic_otc_sub_data_processed' => 'Application data, user-generated content, system logs, and backups stored across compute, storage, and database services.', ], ], @@ -73,11 +73,11 @@ 'name' => 'Cloudflare', 'aliases' => [ 'cloudflare', 'cf' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Content delivery network, DDoS protection, DNS, and edge compute.', - '_opentrust_sub_website' => 'https://cloudflare.com', + '_ettic_otc_sub_purpose' => 'Content delivery network, DDoS protection, DNS, and edge compute.', + '_ettic_otc_sub_website' => 'https://cloudflare.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'HTTP request metadata, IP addresses, request headers, and cached content served to end users.', + '_ettic_otc_sub_data_processed' => 'HTTP request metadata, IP addresses, request headers, and cached content served to end users.', ], ], @@ -85,11 +85,11 @@ 'name' => 'Vercel', 'aliases' => [ 'vercel', 'zeit', 'zeit now' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Frontend hosting, serverless functions, and edge deployment platform.', - '_opentrust_sub_website' => 'https://vercel.com', + '_ettic_otc_sub_purpose' => 'Frontend hosting, serverless functions, and edge deployment platform.', + '_ettic_otc_sub_website' => 'https://vercel.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application source code, build artifacts, request logs, and runtime data for deployed sites and functions.', + '_ettic_otc_sub_data_processed' => 'Application source code, build artifacts, request logs, and runtime data for deployed sites and functions.', ], ], @@ -97,11 +97,11 @@ 'name' => 'Netlify', 'aliases' => [ 'netlify' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Frontend hosting, serverless functions, and continuous deployment platform.', - '_opentrust_sub_website' => 'https://netlify.com', + '_ettic_otc_sub_purpose' => 'Frontend hosting, serverless functions, and continuous deployment platform.', + '_ettic_otc_sub_website' => 'https://netlify.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application source code, build artifacts, request logs, and runtime data for deployed sites and functions.', + '_ettic_otc_sub_data_processed' => 'Application source code, build artifacts, request logs, and runtime data for deployed sites and functions.', ], ], @@ -109,11 +109,11 @@ 'name' => 'Fly.io', 'aliases' => [ 'fly', 'fly.io', 'flyio' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Application hosting platform running containers on a global edge network.', - '_opentrust_sub_website' => 'https://fly.io', + '_ettic_otc_sub_purpose' => 'Application hosting platform running containers on a global edge network.', + '_ettic_otc_sub_website' => 'https://fly.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application containers, runtime data, logs, and persistent volumes for deployed services.', + '_ettic_otc_sub_data_processed' => 'Application containers, runtime data, logs, and persistent volumes for deployed services.', ], ], @@ -121,11 +121,11 @@ 'name' => 'Railway', 'aliases' => [ 'railway', 'railway.app' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Application hosting and deployment platform for containers and databases.', - '_opentrust_sub_website' => 'https://railway.app', + '_ettic_otc_sub_purpose' => 'Application hosting and deployment platform for containers and databases.', + '_ettic_otc_sub_website' => 'https://railway.app', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application source, runtime data, environment variables, and database contents for deployed services.', + '_ettic_otc_sub_data_processed' => 'Application source, runtime data, environment variables, and database contents for deployed services.', ], ], @@ -133,11 +133,11 @@ 'name' => 'Render', 'aliases' => [ 'render', 'render.com' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Application hosting platform for web services, databases, and background workers.', - '_opentrust_sub_website' => 'https://render.com', + '_ettic_otc_sub_purpose' => 'Application hosting platform for web services, databases, and background workers.', + '_ettic_otc_sub_website' => 'https://render.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application source, runtime data, logs, and database contents for deployed services.', + '_ettic_otc_sub_data_processed' => 'Application source, runtime data, logs, and database contents for deployed services.', ], ], @@ -145,11 +145,11 @@ 'name' => 'DigitalOcean', 'aliases' => [ 'digitalocean', 'digital ocean', 'do' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Cloud infrastructure provider offering virtual servers, managed databases, and object storage.', - '_opentrust_sub_website' => 'https://digitalocean.com', + '_ettic_otc_sub_purpose' => 'Cloud infrastructure provider offering virtual servers, managed databases, and object storage.', + '_ettic_otc_sub_website' => 'https://digitalocean.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application data, user-generated content, system logs, and backups stored on compute and storage services.', + '_ettic_otc_sub_data_processed' => 'Application data, user-generated content, system logs, and backups stored on compute and storage services.', ], ], @@ -157,11 +157,11 @@ 'name' => 'Linode', 'aliases' => [ 'linode', 'akamai linode' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Cloud infrastructure provider offering virtual servers and managed services.', - '_opentrust_sub_website' => 'https://linode.com', + '_ettic_otc_sub_purpose' => 'Cloud infrastructure provider offering virtual servers and managed services.', + '_ettic_otc_sub_website' => 'https://linode.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application data, user-generated content, system logs, and backups stored on compute and storage services.', + '_ettic_otc_sub_data_processed' => 'Application data, user-generated content, system logs, and backups stored on compute and storage services.', ], ], @@ -169,12 +169,12 @@ 'name' => 'Hetzner', 'aliases' => [ 'hetzner', 'hetzner online', 'hetzner cloud' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Cloud and dedicated server hosting provider.', - '_opentrust_sub_country' => 'DE', - '_opentrust_sub_website' => 'https://hetzner.com', + '_ettic_otc_sub_purpose' => 'Cloud and dedicated server hosting provider.', + '_ettic_otc_sub_country' => 'DE', + '_ettic_otc_sub_website' => 'https://hetzner.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application data, user-generated content, system logs, and backups stored on compute and storage services.', + '_ettic_otc_sub_data_processed' => 'Application data, user-generated content, system logs, and backups stored on compute and storage services.', ], ], @@ -182,11 +182,11 @@ 'name' => 'Fastly', 'aliases' => [ 'fastly' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Content delivery network and edge compute platform.', - '_opentrust_sub_website' => 'https://fastly.com', + '_ettic_otc_sub_purpose' => 'Content delivery network and edge compute platform.', + '_ettic_otc_sub_website' => 'https://fastly.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'HTTP request metadata, IP addresses, request headers, and cached content served to end users.', + '_ettic_otc_sub_data_processed' => 'HTTP request metadata, IP addresses, request headers, and cached content served to end users.', ], ], @@ -194,12 +194,12 @@ 'name' => 'Datadog', 'aliases' => [ 'datadog', 'data dog' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Infrastructure monitoring, application performance monitoring, and log management.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://datadoghq.com', + '_ettic_otc_sub_purpose' => 'Infrastructure monitoring, application performance monitoring, and log management.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://datadoghq.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'System metrics, application logs, traces, and error events including user identifiers contained in telemetry.', + '_ettic_otc_sub_data_processed' => 'System metrics, application logs, traces, and error events including user identifiers contained in telemetry.', ], ], @@ -207,12 +207,12 @@ 'name' => 'New Relic', 'aliases' => [ 'new relic', 'newrelic' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Application performance monitoring and observability platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://newrelic.com', + '_ettic_otc_sub_purpose' => 'Application performance monitoring and observability platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://newrelic.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'System metrics, application logs, traces, and error events including user identifiers contained in telemetry.', + '_ettic_otc_sub_data_processed' => 'System metrics, application logs, traces, and error events including user identifiers contained in telemetry.', ], ], @@ -220,12 +220,12 @@ 'name' => 'Sentry', 'aliases' => [ 'sentry', 'sentry.io', 'getsentry' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Error tracking and application performance monitoring.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://sentry.io', + '_ettic_otc_sub_purpose' => 'Error tracking and application performance monitoring.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://sentry.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Error stack traces, breadcrumbs, request context, user identifiers, and device metadata captured at error time.', + '_ettic_otc_sub_data_processed' => 'Error stack traces, breadcrumbs, request context, user identifiers, and device metadata captured at error time.', ], ], @@ -233,12 +233,12 @@ 'name' => 'LogRocket', 'aliases' => [ 'logrocket', 'log rocket' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Session replay and frontend monitoring for web applications.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://logrocket.com', + '_ettic_otc_sub_purpose' => 'Session replay and frontend monitoring for web applications.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://logrocket.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Recorded user sessions, DOM events, console logs, network requests, and user identifiers from end-user browsers.', + '_ettic_otc_sub_data_processed' => 'Recorded user sessions, DOM events, console logs, network requests, and user identifiers from end-user browsers.', ], ], @@ -246,12 +246,12 @@ 'name' => 'FullStory', 'aliases' => [ 'fullstory', 'full story' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Digital experience analytics and session replay for web and mobile.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://fullstory.com', + '_ettic_otc_sub_purpose' => 'Digital experience analytics and session replay for web and mobile.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://fullstory.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Recorded user sessions, click and scroll events, page content, and user identifiers from end-user devices.', + '_ettic_otc_sub_data_processed' => 'Recorded user sessions, click and scroll events, page content, and user identifiers from end-user devices.', ], ], @@ -259,12 +259,12 @@ 'name' => 'Honeycomb', 'aliases' => [ 'honeycomb', 'honeycomb.io' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Observability platform for distributed tracing and event-based debugging.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://honeycomb.io', + '_ettic_otc_sub_purpose' => 'Observability platform for distributed tracing and event-based debugging.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://honeycomb.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application traces, spans, events, and telemetry including user identifiers contained in instrumentation.', + '_ettic_otc_sub_data_processed' => 'Application traces, spans, events, and telemetry including user identifiers contained in instrumentation.', ], ], @@ -272,11 +272,11 @@ 'name' => 'Grafana Cloud', 'aliases' => [ 'grafana', 'grafana cloud', 'grafana labs' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Hosted observability platform for metrics, logs, traces, and dashboards.', - '_opentrust_sub_website' => 'https://grafana.com', + '_ettic_otc_sub_purpose' => 'Hosted observability platform for metrics, logs, traces, and dashboards.', + '_ettic_otc_sub_website' => 'https://grafana.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'System metrics, application logs, traces, and dashboard data including user identifiers contained in telemetry.', + '_ettic_otc_sub_data_processed' => 'System metrics, application logs, traces, and dashboard data including user identifiers contained in telemetry.', ], ], @@ -284,11 +284,11 @@ 'name' => 'Better Stack', 'aliases' => [ 'better stack', 'betterstack', 'better uptime', 'logtail' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Log management, uptime monitoring, and incident management platform.', - '_opentrust_sub_website' => 'https://betterstack.com', + '_ettic_otc_sub_purpose' => 'Log management, uptime monitoring, and incident management platform.', + '_ettic_otc_sub_website' => 'https://betterstack.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application logs, uptime check results, and incident metadata including user identifiers contained in log content.', + '_ettic_otc_sub_data_processed' => 'Application logs, uptime check results, and incident metadata including user identifiers contained in log content.', ], ], @@ -296,11 +296,11 @@ 'name' => 'Axiom', 'aliases' => [ 'axiom', 'axiom.co' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Cloud-native log management and event data platform.', - '_opentrust_sub_website' => 'https://axiom.co', + '_ettic_otc_sub_purpose' => 'Cloud-native log management and event data platform.', + '_ettic_otc_sub_website' => 'https://axiom.co', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application logs and event data including user identifiers contained in log content.', + '_ettic_otc_sub_data_processed' => 'Application logs and event data including user identifiers contained in log content.', ], ], @@ -308,11 +308,11 @@ 'name' => 'Baselime', 'aliases' => [ 'baselime' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Observability platform for serverless and cloud-native applications.', - '_opentrust_sub_website' => 'https://baselime.io', + '_ettic_otc_sub_purpose' => 'Observability platform for serverless and cloud-native applications.', + '_ettic_otc_sub_website' => 'https://baselime.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application logs, traces, and telemetry including user identifiers contained in instrumentation.', + '_ettic_otc_sub_data_processed' => 'Application logs, traces, and telemetry including user identifiers contained in instrumentation.', ], ], @@ -320,12 +320,12 @@ 'name' => 'Postmark', 'aliases' => [ 'postmark', 'postmarkapp' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Transactional email delivery service.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://postmarkapp.com', + '_ettic_otc_sub_purpose' => 'Transactional email delivery service.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://postmarkapp.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Recipient email addresses, message content, delivery metadata, and bounce or complaint events.', + '_ettic_otc_sub_data_processed' => 'Recipient email addresses, message content, delivery metadata, and bounce or complaint events.', ], ], @@ -333,12 +333,12 @@ 'name' => 'SendGrid', 'aliases' => [ 'sendgrid', 'twilio sendgrid' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Transactional and marketing email delivery service.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://sendgrid.com', + '_ettic_otc_sub_purpose' => 'Transactional and marketing email delivery service.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://sendgrid.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Recipient email addresses, message content, delivery metadata, and engagement events.', + '_ettic_otc_sub_data_processed' => 'Recipient email addresses, message content, delivery metadata, and engagement events.', ], ], @@ -346,12 +346,12 @@ 'name' => 'Resend', 'aliases' => [ 'resend', 'resend.com' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Transactional email delivery service for developers.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://resend.com', + '_ettic_otc_sub_purpose' => 'Transactional email delivery service for developers.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://resend.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Recipient email addresses, message content, delivery metadata, and bounce or complaint events.', + '_ettic_otc_sub_data_processed' => 'Recipient email addresses, message content, delivery metadata, and bounce or complaint events.', ], ], @@ -359,12 +359,12 @@ 'name' => 'Mailgun', 'aliases' => [ 'mailgun', 'sinch mailgun' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Transactional email delivery and validation service.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://mailgun.com', + '_ettic_otc_sub_purpose' => 'Transactional email delivery and validation service.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://mailgun.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Recipient email addresses, message content, delivery metadata, and engagement events.', + '_ettic_otc_sub_data_processed' => 'Recipient email addresses, message content, delivery metadata, and engagement events.', ], ], @@ -372,11 +372,11 @@ 'name' => 'Amazon SES', 'aliases' => [ 'ses', 'amazon ses', 'aws ses', 'simple email service' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Transactional email delivery service from Amazon Web Services.', - '_opentrust_sub_website' => 'https://aws.amazon.com/ses', + '_ettic_otc_sub_purpose' => 'Transactional email delivery service from Amazon Web Services.', + '_ettic_otc_sub_website' => 'https://aws.amazon.com/ses', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Recipient email addresses, message content, delivery metadata, and bounce or complaint events.', + '_ettic_otc_sub_data_processed' => 'Recipient email addresses, message content, delivery metadata, and bounce or complaint events.', ], ], @@ -384,12 +384,12 @@ 'name' => 'Loops', 'aliases' => [ 'loops', 'loops.so' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Transactional and marketing email platform for SaaS companies.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://loops.so', + '_ettic_otc_sub_purpose' => 'Transactional and marketing email platform for SaaS companies.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://loops.so', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Contact email addresses, names, user properties, message content, and engagement events.', + '_ettic_otc_sub_data_processed' => 'Contact email addresses, names, user properties, message content, and engagement events.', ], ], @@ -397,12 +397,12 @@ 'name' => 'Brevo', 'aliases' => [ 'brevo', 'sendinblue', 'send in blue' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Transactional and marketing email, SMS, and customer messaging platform.', - '_opentrust_sub_country' => 'FR', - '_opentrust_sub_website' => 'https://brevo.com', + '_ettic_otc_sub_purpose' => 'Transactional and marketing email, SMS, and customer messaging platform.', + '_ettic_otc_sub_country' => 'FR', + '_ettic_otc_sub_website' => 'https://brevo.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Contact email addresses, phone numbers, names, message content, and engagement events.', + '_ettic_otc_sub_data_processed' => 'Contact email addresses, phone numbers, names, message content, and engagement events.', ], ], @@ -410,12 +410,12 @@ 'name' => 'Mailchimp', 'aliases' => [ 'mailchimp', 'mail chimp', 'intuit mailchimp' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Marketing email and audience management platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://mailchimp.com', + '_ettic_otc_sub_purpose' => 'Marketing email and audience management platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://mailchimp.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Subscriber email addresses, names, audience segmentation data, message content, and engagement events.', + '_ettic_otc_sub_data_processed' => 'Subscriber email addresses, names, audience segmentation data, message content, and engagement events.', ], ], @@ -423,12 +423,12 @@ 'name' => 'Kit', 'aliases' => [ 'convertkit', 'convert kit', 'kit' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Email marketing platform for creators and publishers.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://kit.com', + '_ettic_otc_sub_purpose' => 'Email marketing platform for creators and publishers.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://kit.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Subscriber email addresses, names, tags, message content, and engagement events.', + '_ettic_otc_sub_data_processed' => 'Subscriber email addresses, names, tags, message content, and engagement events.', ], ], @@ -436,12 +436,12 @@ 'name' => 'Customer.io', 'aliases' => [ 'customer.io', 'customerio', 'customer io' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Customer messaging platform for email, SMS, and push notifications.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://customer.io', + '_ettic_otc_sub_purpose' => 'Customer messaging platform for email, SMS, and push notifications.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://customer.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer profiles, email addresses, behavioral events, message content, and engagement data.', + '_ettic_otc_sub_data_processed' => 'Customer profiles, email addresses, behavioral events, message content, and engagement data.', ], ], @@ -449,12 +449,12 @@ 'name' => 'Klaviyo', 'aliases' => [ 'klaviyo' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Marketing automation and customer data platform for email and SMS.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://klaviyo.com', + '_ettic_otc_sub_purpose' => 'Marketing automation and customer data platform for email and SMS.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://klaviyo.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer profiles, email addresses, phone numbers, purchase history, and engagement events.', + '_ettic_otc_sub_data_processed' => 'Customer profiles, email addresses, phone numbers, purchase history, and engagement events.', ], ], @@ -462,12 +462,12 @@ 'name' => 'Stripe', 'aliases' => [ 'stripe' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Payment processing and billing infrastructure.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://stripe.com', + '_ettic_otc_sub_purpose' => 'Payment processing and billing infrastructure.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://stripe.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Payment card details (tokenized), billing address, customer email, and transaction metadata.', + '_ettic_otc_sub_data_processed' => 'Payment card details (tokenized), billing address, customer email, and transaction metadata.', ], ], @@ -475,12 +475,12 @@ 'name' => 'Paddle', 'aliases' => [ 'paddle', 'paddle.com' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Merchant of record payment and subscription platform for software companies.', - '_opentrust_sub_country' => 'GB', - '_opentrust_sub_website' => 'https://paddle.com', + '_ettic_otc_sub_purpose' => 'Merchant of record payment and subscription platform for software companies.', + '_ettic_otc_sub_country' => 'GB', + '_ettic_otc_sub_website' => 'https://paddle.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Payment card details (tokenized), billing address, customer email, tax information, and transaction metadata.', + '_ettic_otc_sub_data_processed' => 'Payment card details (tokenized), billing address, customer email, tax information, and transaction metadata.', ], ], @@ -488,12 +488,12 @@ 'name' => 'Lemon Squeezy', 'aliases' => [ 'lemon squeezy', 'lemonsqueezy' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Merchant of record payment and subscription platform for digital products.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://lemonsqueezy.com', + '_ettic_otc_sub_purpose' => 'Merchant of record payment and subscription platform for digital products.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://lemonsqueezy.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Payment card details (tokenized), billing address, customer email, tax information, and transaction metadata.', + '_ettic_otc_sub_data_processed' => 'Payment card details (tokenized), billing address, customer email, tax information, and transaction metadata.', ], ], @@ -501,12 +501,12 @@ 'name' => 'Braintree', 'aliases' => [ 'braintree', 'paypal braintree' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Payment processing platform owned by PayPal.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://braintreepayments.com', + '_ettic_otc_sub_purpose' => 'Payment processing platform owned by PayPal.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://braintreepayments.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Payment card details (tokenized), billing address, customer email, and transaction metadata.', + '_ettic_otc_sub_data_processed' => 'Payment card details (tokenized), billing address, customer email, and transaction metadata.', ], ], @@ -514,12 +514,12 @@ 'name' => 'PayPal', 'aliases' => [ 'paypal', 'pay pal' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Online payment processing and digital wallet service.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://paypal.com', + '_ettic_otc_sub_purpose' => 'Online payment processing and digital wallet service.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://paypal.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Payer account identifiers, email addresses, billing address, and transaction metadata.', + '_ettic_otc_sub_data_processed' => 'Payer account identifiers, email addresses, billing address, and transaction metadata.', ], ], @@ -527,12 +527,12 @@ 'name' => 'Chargebee', 'aliases' => [ 'chargebee' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Subscription billing and revenue management platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://chargebee.com', + '_ettic_otc_sub_purpose' => 'Subscription billing and revenue management platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://chargebee.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer profiles, billing address, email, subscription data, and transaction metadata.', + '_ettic_otc_sub_data_processed' => 'Customer profiles, billing address, email, subscription data, and transaction metadata.', ], ], @@ -540,12 +540,12 @@ 'name' => 'Recurly', 'aliases' => [ 'recurly' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Subscription billing and revenue management platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://recurly.com', + '_ettic_otc_sub_purpose' => 'Subscription billing and revenue management platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://recurly.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer profiles, billing address, email, subscription data, and transaction metadata.', + '_ettic_otc_sub_data_processed' => 'Customer profiles, billing address, email, subscription data, and transaction metadata.', ], ], @@ -553,11 +553,11 @@ 'name' => 'Google Analytics', 'aliases' => [ 'google analytics', 'ga', 'ga4', 'universal analytics' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Web and app analytics platform.', - '_opentrust_sub_website' => 'https://analytics.google.com', + '_ettic_otc_sub_purpose' => 'Web and app analytics platform.', + '_ettic_otc_sub_website' => 'https://analytics.google.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Page views, user interactions, device and browser metadata, IP addresses, and pseudonymous user identifiers.', + '_ettic_otc_sub_data_processed' => 'Page views, user interactions, device and browser metadata, IP addresses, and pseudonymous user identifiers.', ], ], @@ -565,12 +565,12 @@ 'name' => 'PostHog', 'aliases' => [ 'posthog', 'post hog' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Product analytics, session replay, and feature flag platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://posthog.com', + '_ettic_otc_sub_purpose' => 'Product analytics, session replay, and feature flag platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://posthog.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Product usage events, user identifiers, session recordings, and device metadata.', + '_ettic_otc_sub_data_processed' => 'Product usage events, user identifiers, session recordings, and device metadata.', ], ], @@ -578,12 +578,12 @@ 'name' => 'Plausible Analytics', 'aliases' => [ 'plausible', 'plausible analytics' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Privacy-focused web analytics platform.', - '_opentrust_sub_country' => 'EE', - '_opentrust_sub_website' => 'https://plausible.io', + '_ettic_otc_sub_purpose' => 'Privacy-focused web analytics platform.', + '_ettic_otc_sub_country' => 'EE', + '_ettic_otc_sub_website' => 'https://plausible.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Aggregated page views, referrers, and anonymized device and browser metadata.', + '_ettic_otc_sub_data_processed' => 'Aggregated page views, referrers, and anonymized device and browser metadata.', ], ], @@ -591,11 +591,11 @@ 'name' => 'Fathom Analytics', 'aliases' => [ 'fathom', 'fathom analytics' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Privacy-focused web analytics platform.', - '_opentrust_sub_website' => 'https://usefathom.com', + '_ettic_otc_sub_purpose' => 'Privacy-focused web analytics platform.', + '_ettic_otc_sub_website' => 'https://usefathom.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Aggregated page views, referrers, and anonymized device and browser metadata.', + '_ettic_otc_sub_data_processed' => 'Aggregated page views, referrers, and anonymized device and browser metadata.', ], ], @@ -603,12 +603,12 @@ 'name' => 'Mixpanel', 'aliases' => [ 'mixpanel' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Product analytics platform for tracking user behavior.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://mixpanel.com', + '_ettic_otc_sub_purpose' => 'Product analytics platform for tracking user behavior.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://mixpanel.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Product usage events, user identifiers, user properties, and device metadata.', + '_ettic_otc_sub_data_processed' => 'Product usage events, user identifiers, user properties, and device metadata.', ], ], @@ -616,12 +616,12 @@ 'name' => 'Amplitude', 'aliases' => [ 'amplitude' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Product analytics platform for tracking user behavior.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://amplitude.com', + '_ettic_otc_sub_purpose' => 'Product analytics platform for tracking user behavior.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://amplitude.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Product usage events, user identifiers, user properties, and device metadata.', + '_ettic_otc_sub_data_processed' => 'Product usage events, user identifiers, user properties, and device metadata.', ], ], @@ -629,12 +629,12 @@ 'name' => 'Heap', 'aliases' => [ 'heap', 'heap analytics' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Product analytics platform with automatic event capture.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://heap.io', + '_ettic_otc_sub_purpose' => 'Product analytics platform with automatic event capture.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://heap.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Product usage events, user identifiers, user properties, and device metadata.', + '_ettic_otc_sub_data_processed' => 'Product usage events, user identifiers, user properties, and device metadata.', ], ], @@ -642,12 +642,12 @@ 'name' => 'Segment', 'aliases' => [ 'segment', 'twilio segment' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Customer data platform for collecting and routing analytics events.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://segment.com', + '_ettic_otc_sub_purpose' => 'Customer data platform for collecting and routing analytics events.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://segment.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer profiles, user identifiers, event data, and traits forwarded to downstream tools.', + '_ettic_otc_sub_data_processed' => 'Customer profiles, user identifiers, event data, and traits forwarded to downstream tools.', ], ], @@ -655,12 +655,12 @@ 'name' => 'RudderStack', 'aliases' => [ 'rudderstack', 'rudder stack' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Customer data platform for collecting and routing analytics events.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://rudderstack.com', + '_ettic_otc_sub_purpose' => 'Customer data platform for collecting and routing analytics events.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://rudderstack.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer profiles, user identifiers, event data, and traits forwarded to downstream tools.', + '_ettic_otc_sub_data_processed' => 'Customer profiles, user identifiers, event data, and traits forwarded to downstream tools.', ], ], @@ -668,12 +668,12 @@ 'name' => 'Intercom', 'aliases' => [ 'intercom' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Customer messaging and support platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://intercom.com', + '_ettic_otc_sub_purpose' => 'Customer messaging and support platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://intercom.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer contact details, conversation history, user attributes, and product usage events.', + '_ettic_otc_sub_data_processed' => 'Customer contact details, conversation history, user attributes, and product usage events.', ], ], @@ -681,12 +681,12 @@ 'name' => 'Zendesk', 'aliases' => [ 'zendesk' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Customer support ticketing and help desk platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://zendesk.com', + '_ettic_otc_sub_purpose' => 'Customer support ticketing and help desk platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://zendesk.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer contact details, support ticket content, conversation history, and attachments.', + '_ettic_otc_sub_data_processed' => 'Customer contact details, support ticket content, conversation history, and attachments.', ], ], @@ -694,12 +694,12 @@ 'name' => 'HubSpot', 'aliases' => [ 'hubspot', 'hub spot' ], 'fields' => [ - '_opentrust_sub_purpose' => 'CRM, marketing, sales, and customer service platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://hubspot.com', + '_ettic_otc_sub_purpose' => 'CRM, marketing, sales, and customer service platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://hubspot.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Contact details, company records, email correspondence, marketing engagement, and sales pipeline data.', + '_ettic_otc_sub_data_processed' => 'Contact details, company records, email correspondence, marketing engagement, and sales pipeline data.', ], ], @@ -707,12 +707,12 @@ 'name' => 'Front', 'aliases' => [ 'front', 'frontapp' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Shared inbox and customer communication platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://front.com', + '_ettic_otc_sub_purpose' => 'Shared inbox and customer communication platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://front.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer contact details, email and message content, conversation history, and attachments.', + '_ettic_otc_sub_data_processed' => 'Customer contact details, email and message content, conversation history, and attachments.', ], ], @@ -720,12 +720,12 @@ 'name' => 'Help Scout', 'aliases' => [ 'help scout', 'helpscout' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Customer support help desk and shared inbox platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://helpscout.com', + '_ettic_otc_sub_purpose' => 'Customer support help desk and shared inbox platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://helpscout.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer contact details, support ticket content, conversation history, and attachments.', + '_ettic_otc_sub_data_processed' => 'Customer contact details, support ticket content, conversation history, and attachments.', ], ], @@ -733,12 +733,12 @@ 'name' => 'Crisp', 'aliases' => [ 'crisp', 'crisp chat' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Customer messaging and live chat platform.', - '_opentrust_sub_country' => 'FR', - '_opentrust_sub_website' => 'https://crisp.chat', + '_ettic_otc_sub_purpose' => 'Customer messaging and live chat platform.', + '_ettic_otc_sub_country' => 'FR', + '_ettic_otc_sub_website' => 'https://crisp.chat', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer contact details, chat transcripts, user attributes, and conversation history.', + '_ettic_otc_sub_data_processed' => 'Customer contact details, chat transcripts, user attributes, and conversation history.', ], ], @@ -746,11 +746,11 @@ 'name' => 'Freshdesk', 'aliases' => [ 'freshdesk', 'fresh desk', 'freshworks' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Customer support ticketing and help desk platform.', - '_opentrust_sub_website' => 'https://freshworks.com/freshdesk', + '_ettic_otc_sub_purpose' => 'Customer support ticketing and help desk platform.', + '_ettic_otc_sub_website' => 'https://freshworks.com/freshdesk', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer contact details, support ticket content, conversation history, and attachments.', + '_ettic_otc_sub_data_processed' => 'Customer contact details, support ticket content, conversation history, and attachments.', ], ], @@ -758,12 +758,12 @@ 'name' => 'Pipedrive', 'aliases' => [ 'pipedrive' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Sales CRM and pipeline management platform.', - '_opentrust_sub_country' => 'EE', - '_opentrust_sub_website' => 'https://pipedrive.com', + '_ettic_otc_sub_purpose' => 'Sales CRM and pipeline management platform.', + '_ettic_otc_sub_country' => 'EE', + '_ettic_otc_sub_website' => 'https://pipedrive.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Contact details, company records, deal pipeline data, and email correspondence.', + '_ettic_otc_sub_data_processed' => 'Contact details, company records, deal pipeline data, and email correspondence.', ], ], @@ -771,12 +771,12 @@ 'name' => 'Salesforce', 'aliases' => [ 'salesforce', 'sfdc' ], 'fields' => [ - '_opentrust_sub_purpose' => 'CRM and enterprise cloud platform for sales, service, and marketing.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://salesforce.com', + '_ettic_otc_sub_purpose' => 'CRM and enterprise cloud platform for sales, service, and marketing.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://salesforce.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Contact details, company records, sales pipeline data, customer interactions, and case history.', + '_ettic_otc_sub_data_processed' => 'Contact details, company records, sales pipeline data, customer interactions, and case history.', ], ], @@ -784,12 +784,12 @@ 'name' => 'Auth0', 'aliases' => [ 'auth0', 'okta auth0' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Identity and access management platform for authentication.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://auth0.com', + '_ettic_otc_sub_purpose' => 'Identity and access management platform for authentication.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://auth0.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User credentials, email addresses, profile attributes, session tokens, and authentication logs.', + '_ettic_otc_sub_data_processed' => 'User credentials, email addresses, profile attributes, session tokens, and authentication logs.', ], ], @@ -797,12 +797,12 @@ 'name' => 'Clerk', 'aliases' => [ 'clerk', 'clerk.dev', 'clerk.com' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Authentication and user management platform for web and mobile apps.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://clerk.com', + '_ettic_otc_sub_purpose' => 'Authentication and user management platform for web and mobile apps.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://clerk.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User credentials, email addresses, phone numbers, profile attributes, and authentication logs.', + '_ettic_otc_sub_data_processed' => 'User credentials, email addresses, phone numbers, profile attributes, and authentication logs.', ], ], @@ -810,12 +810,12 @@ 'name' => 'WorkOS', 'aliases' => [ 'workos', 'work os' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Enterprise authentication, SSO, and directory sync platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://workos.com', + '_ettic_otc_sub_purpose' => 'Enterprise authentication, SSO, and directory sync platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://workos.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User credentials, email addresses, profile attributes, directory records, and authentication logs.', + '_ettic_otc_sub_data_processed' => 'User credentials, email addresses, profile attributes, directory records, and authentication logs.', ], ], @@ -823,12 +823,12 @@ 'name' => 'Stytch', 'aliases' => [ 'stytch' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Passwordless authentication and user management platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://stytch.com', + '_ettic_otc_sub_purpose' => 'Passwordless authentication and user management platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://stytch.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User credentials, email addresses, phone numbers, profile attributes, and authentication logs.', + '_ettic_otc_sub_data_processed' => 'User credentials, email addresses, phone numbers, profile attributes, and authentication logs.', ], ], @@ -836,12 +836,12 @@ 'name' => 'Descope', 'aliases' => [ 'descope' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Authentication and user management platform with visual flow builder.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://descope.com', + '_ettic_otc_sub_purpose' => 'Authentication and user management platform with visual flow builder.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://descope.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User credentials, email addresses, phone numbers, profile attributes, and authentication logs.', + '_ettic_otc_sub_data_processed' => 'User credentials, email addresses, phone numbers, profile attributes, and authentication logs.', ], ], @@ -849,11 +849,11 @@ 'name' => 'Firebase Authentication', 'aliases' => [ 'firebase auth', 'firebase authentication' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Authentication service from Google Firebase.', - '_opentrust_sub_website' => 'https://firebase.google.com/products/auth', + '_ettic_otc_sub_purpose' => 'Authentication service from Google Firebase.', + '_ettic_otc_sub_website' => 'https://firebase.google.com/products/auth', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User credentials, email addresses, phone numbers, profile attributes, and authentication logs.', + '_ettic_otc_sub_data_processed' => 'User credentials, email addresses, phone numbers, profile attributes, and authentication logs.', ], ], @@ -861,11 +861,11 @@ 'name' => 'Supabase', 'aliases' => [ 'supabase' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed Postgres database, authentication, storage, and backend platform.', - '_opentrust_sub_website' => 'https://supabase.com', + '_ettic_otc_sub_purpose' => 'Managed Postgres database, authentication, storage, and backend platform.', + '_ettic_otc_sub_website' => 'https://supabase.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application database contents, user credentials, uploaded files, and authentication logs.', + '_ettic_otc_sub_data_processed' => 'Application database contents, user credentials, uploaded files, and authentication logs.', ], ], @@ -873,11 +873,11 @@ 'name' => 'Firebase', 'aliases' => [ 'firebase', 'google firebase' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Google backend platform offering database, authentication, hosting, and analytics.', - '_opentrust_sub_website' => 'https://firebase.google.com', + '_ettic_otc_sub_purpose' => 'Google backend platform offering database, authentication, hosting, and analytics.', + '_ettic_otc_sub_website' => 'https://firebase.google.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application database contents, user credentials, uploaded files, and analytics events.', + '_ettic_otc_sub_data_processed' => 'Application database contents, user credentials, uploaded files, and analytics events.', ], ], @@ -885,11 +885,11 @@ 'name' => 'MongoDB Atlas', 'aliases' => [ 'mongodb atlas', 'mongo atlas', 'atlas' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed MongoDB database service.', - '_opentrust_sub_website' => 'https://mongodb.com/atlas', + '_ettic_otc_sub_purpose' => 'Managed MongoDB database service.', + '_ettic_otc_sub_website' => 'https://mongodb.com/atlas', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application database contents, query logs, and backups.', + '_ettic_otc_sub_data_processed' => 'Application database contents, query logs, and backups.', ], ], @@ -897,12 +897,12 @@ 'name' => 'PlanetScale', 'aliases' => [ 'planetscale', 'planet scale' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed MySQL database platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://planetscale.com', + '_ettic_otc_sub_purpose' => 'Managed MySQL database platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://planetscale.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application database contents, query logs, and backups.', + '_ettic_otc_sub_data_processed' => 'Application database contents, query logs, and backups.', ], ], @@ -910,12 +910,12 @@ 'name' => 'Neon', 'aliases' => [ 'neon', 'neon.tech', 'neon database' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed serverless Postgres database platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://neon.tech', + '_ettic_otc_sub_purpose' => 'Managed serverless Postgres database platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://neon.tech', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application database contents, query logs, and backups.', + '_ettic_otc_sub_data_processed' => 'Application database contents, query logs, and backups.', ], ], @@ -923,11 +923,11 @@ 'name' => 'Upstash', 'aliases' => [ 'upstash' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed serverless Redis, Kafka, and vector database platform.', - '_opentrust_sub_website' => 'https://upstash.com', + '_ettic_otc_sub_purpose' => 'Managed serverless Redis, Kafka, and vector database platform.', + '_ettic_otc_sub_website' => 'https://upstash.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application cache data, queue messages, and database contents.', + '_ettic_otc_sub_data_processed' => 'Application cache data, queue messages, and database contents.', ], ], @@ -935,11 +935,11 @@ 'name' => 'Turso', 'aliases' => [ 'turso' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed SQLite-compatible edge database platform.', - '_opentrust_sub_website' => 'https://turso.tech', + '_ettic_otc_sub_purpose' => 'Managed SQLite-compatible edge database platform.', + '_ettic_otc_sub_website' => 'https://turso.tech', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application database contents, query logs, and backups.', + '_ettic_otc_sub_data_processed' => 'Application database contents, query logs, and backups.', ], ], @@ -947,12 +947,12 @@ 'name' => 'OpenAI', 'aliases' => [ 'openai', 'open ai', 'chatgpt api' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Large language model API provider.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://openai.com', + '_ettic_otc_sub_purpose' => 'Large language model API provider.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://openai.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Prompt content, completions, and API request metadata submitted to model endpoints.', + '_ettic_otc_sub_data_processed' => 'Prompt content, completions, and API request metadata submitted to model endpoints.', ], ], @@ -960,12 +960,12 @@ 'name' => 'Anthropic', 'aliases' => [ 'anthropic', 'claude' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Large language model API provider, maker of Claude.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://anthropic.com', + '_ettic_otc_sub_purpose' => 'Large language model API provider, maker of Claude.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://anthropic.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Prompt content, completions, and API request metadata submitted to model endpoints.', + '_ettic_otc_sub_data_processed' => 'Prompt content, completions, and API request metadata submitted to model endpoints.', ], ], @@ -973,11 +973,11 @@ 'name' => 'Google AI', 'aliases' => [ 'google ai', 'gemini', 'gemini api', 'google generative ai' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Large language model API provider for Gemini models.', - '_opentrust_sub_website' => 'https://ai.google.dev', + '_ettic_otc_sub_purpose' => 'Large language model API provider for Gemini models.', + '_ettic_otc_sub_website' => 'https://ai.google.dev', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Prompt content, completions, and API request metadata submitted to model endpoints.', + '_ettic_otc_sub_data_processed' => 'Prompt content, completions, and API request metadata submitted to model endpoints.', ], ], @@ -985,12 +985,12 @@ 'name' => 'Mistral AI', 'aliases' => [ 'mistral', 'mistral ai' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Large language model API provider.', - '_opentrust_sub_country' => 'FR', - '_opentrust_sub_website' => 'https://mistral.ai', + '_ettic_otc_sub_purpose' => 'Large language model API provider.', + '_ettic_otc_sub_country' => 'FR', + '_ettic_otc_sub_website' => 'https://mistral.ai', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Prompt content, completions, and API request metadata submitted to model endpoints.', + '_ettic_otc_sub_data_processed' => 'Prompt content, completions, and API request metadata submitted to model endpoints.', ], ], @@ -998,12 +998,12 @@ 'name' => 'Perplexity', 'aliases' => [ 'perplexity', 'perplexity ai' ], 'fields' => [ - '_opentrust_sub_purpose' => 'AI-powered search and language model API provider.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://perplexity.ai', + '_ettic_otc_sub_purpose' => 'AI-powered search and language model API provider.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://perplexity.ai', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Prompt content, search queries, completions, and API request metadata.', + '_ettic_otc_sub_data_processed' => 'Prompt content, search queries, completions, and API request metadata.', ], ], @@ -1011,11 +1011,11 @@ 'name' => 'OpenRouter', 'aliases' => [ 'openrouter', 'open router' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Unified API gateway for accessing multiple large language model providers.', - '_opentrust_sub_website' => 'https://openrouter.ai', + '_ettic_otc_sub_purpose' => 'Unified API gateway for accessing multiple large language model providers.', + '_ettic_otc_sub_website' => 'https://openrouter.ai', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Prompt content, completions, and API request metadata routed to upstream model providers.', + '_ettic_otc_sub_data_processed' => 'Prompt content, completions, and API request metadata routed to upstream model providers.', ], ], @@ -1023,12 +1023,12 @@ 'name' => 'Replicate', 'aliases' => [ 'replicate', 'replicate.com' ], 'fields' => [ - '_opentrust_sub_purpose' => 'API platform for running open-source machine learning models.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://replicate.com', + '_ettic_otc_sub_purpose' => 'API platform for running open-source machine learning models.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://replicate.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Model inputs (including images, audio, and text), outputs, and API request metadata.', + '_ettic_otc_sub_data_processed' => 'Model inputs (including images, audio, and text), outputs, and API request metadata.', ], ], @@ -1036,12 +1036,12 @@ 'name' => 'Slack', 'aliases' => [ 'slack' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Team messaging and collaboration platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://slack.com', + '_ettic_otc_sub_purpose' => 'Team messaging and collaboration platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://slack.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User profiles, message content, channel membership, and uploaded files.', + '_ettic_otc_sub_data_processed' => 'User profiles, message content, channel membership, and uploaded files.', ], ], @@ -1049,12 +1049,12 @@ 'name' => 'Linear', 'aliases' => [ 'linear', 'linear.app' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Issue tracking and project management tool for software teams.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://linear.app', + '_ettic_otc_sub_purpose' => 'Issue tracking and project management tool for software teams.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://linear.app', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User profiles, issue content, project data, and comments.', + '_ettic_otc_sub_data_processed' => 'User profiles, issue content, project data, and comments.', ], ], @@ -1062,12 +1062,12 @@ 'name' => 'Notion', 'aliases' => [ 'notion', 'notion.so' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Collaborative workspace for notes, documents, and databases.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://notion.so', + '_ettic_otc_sub_purpose' => 'Collaborative workspace for notes, documents, and databases.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://notion.so', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User profiles, document content, database records, and uploaded files.', + '_ettic_otc_sub_data_processed' => 'User profiles, document content, database records, and uploaded files.', ], ], @@ -1075,12 +1075,12 @@ 'name' => 'Asana', 'aliases' => [ 'asana' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Work and project management platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://asana.com', + '_ettic_otc_sub_purpose' => 'Work and project management platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://asana.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User profiles, task content, project data, and comments.', + '_ettic_otc_sub_data_processed' => 'User profiles, task content, project data, and comments.', ], ], @@ -1088,11 +1088,11 @@ 'name' => 'Trello', 'aliases' => [ 'trello', 'atlassian trello' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Kanban-style project management tool.', - '_opentrust_sub_website' => 'https://trello.com', + '_ettic_otc_sub_purpose' => 'Kanban-style project management tool.', + '_ettic_otc_sub_website' => 'https://trello.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User profiles, card content, board data, and attachments.', + '_ettic_otc_sub_data_processed' => 'User profiles, card content, board data, and attachments.', ], ], @@ -1100,12 +1100,12 @@ 'name' => 'Zoom', 'aliases' => [ 'zoom', 'zoom video' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Video conferencing and communications platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://zoom.us', + '_ettic_otc_sub_purpose' => 'Video conferencing and communications platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://zoom.us', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User profiles, meeting metadata, call recordings, and chat transcripts.', + '_ettic_otc_sub_data_processed' => 'User profiles, meeting metadata, call recordings, and chat transcripts.', ], ], @@ -1113,11 +1113,11 @@ 'name' => 'Google Workspace', 'aliases' => [ 'google workspace', 'gsuite', 'g suite' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Productivity suite including email, documents, calendar, and storage.', - '_opentrust_sub_website' => 'https://workspace.google.com', + '_ettic_otc_sub_purpose' => 'Productivity suite including email, documents, calendar, and storage.', + '_ettic_otc_sub_website' => 'https://workspace.google.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Employee email, documents, calendar events, contacts, and files stored in Drive.', + '_ettic_otc_sub_data_processed' => 'Employee email, documents, calendar events, contacts, and files stored in Drive.', ], ], @@ -1125,11 +1125,11 @@ 'name' => 'Microsoft 365', 'aliases' => [ 'microsoft 365', 'm365', 'office 365', 'o365' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Productivity suite including email, documents, calendar, and storage.', - '_opentrust_sub_website' => 'https://microsoft.com/microsoft-365', + '_ettic_otc_sub_purpose' => 'Productivity suite including email, documents, calendar, and storage.', + '_ettic_otc_sub_website' => 'https://microsoft.com/microsoft-365', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Employee email, documents, calendar events, contacts, and files stored in OneDrive or SharePoint.', + '_ettic_otc_sub_data_processed' => 'Employee email, documents, calendar events, contacts, and files stored in OneDrive or SharePoint.', ], ], @@ -1137,12 +1137,12 @@ 'name' => 'GitHub', 'aliases' => [ 'github', 'git hub' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Source code hosting and collaboration platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://github.com', + '_ettic_otc_sub_purpose' => 'Source code hosting and collaboration platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://github.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Source code, issues, pull requests, user profiles, and CI artifacts.', + '_ettic_otc_sub_data_processed' => 'Source code, issues, pull requests, user profiles, and CI artifacts.', ], ], @@ -1150,12 +1150,12 @@ 'name' => 'GitLab', 'aliases' => [ 'gitlab', 'git lab' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Source code hosting, CI/CD, and DevOps platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://about.gitlab.com', + '_ettic_otc_sub_purpose' => 'Source code hosting, CI/CD, and DevOps platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://about.gitlab.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Source code, issues, merge requests, user profiles, and CI artifacts.', + '_ettic_otc_sub_data_processed' => 'Source code, issues, merge requests, user profiles, and CI artifacts.', ], ], @@ -1163,11 +1163,11 @@ 'name' => 'Bitbucket', 'aliases' => [ 'bitbucket', 'atlassian bitbucket' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Source code hosting and collaboration platform from Atlassian.', - '_opentrust_sub_website' => 'https://bitbucket.org', + '_ettic_otc_sub_purpose' => 'Source code hosting and collaboration platform from Atlassian.', + '_ettic_otc_sub_website' => 'https://bitbucket.org', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Source code, issues, pull requests, user profiles, and CI artifacts.', + '_ettic_otc_sub_data_processed' => 'Source code, issues, pull requests, user profiles, and CI artifacts.', ], ], @@ -1175,12 +1175,12 @@ 'name' => 'CircleCI', 'aliases' => [ 'circleci', 'circle ci' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Continuous integration and delivery platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://circleci.com', + '_ettic_otc_sub_purpose' => 'Continuous integration and delivery platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://circleci.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Source code, build logs, environment variables, and CI artifacts.', + '_ettic_otc_sub_data_processed' => 'Source code, build logs, environment variables, and CI artifacts.', ], ], @@ -1188,12 +1188,12 @@ 'name' => 'Buildkite', 'aliases' => [ 'buildkite', 'build kite' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Continuous integration and delivery platform with self-hosted agents.', - '_opentrust_sub_country' => 'AU', - '_opentrust_sub_website' => 'https://buildkite.com', + '_ettic_otc_sub_purpose' => 'Continuous integration and delivery platform with self-hosted agents.', + '_ettic_otc_sub_country' => 'AU', + '_ettic_otc_sub_website' => 'https://buildkite.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Build metadata, pipeline configuration, logs, and job artifacts.', + '_ettic_otc_sub_data_processed' => 'Build metadata, pipeline configuration, logs, and job artifacts.', ], ], @@ -1201,12 +1201,12 @@ 'name' => 'OneSignal', 'aliases' => [ 'onesignal', 'one signal' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Push notification and customer messaging platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://onesignal.com', + '_ettic_otc_sub_purpose' => 'Push notification and customer messaging platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://onesignal.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Device tokens, user identifiers, subscription preferences, and notification content.', + '_ettic_otc_sub_data_processed' => 'Device tokens, user identifiers, subscription preferences, and notification content.', ], ], @@ -1214,12 +1214,12 @@ 'name' => 'Knock', 'aliases' => [ 'knock', 'knock.app' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Notification infrastructure for product messaging across channels.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://knock.app', + '_ettic_otc_sub_purpose' => 'Notification infrastructure for product messaging across channels.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://knock.app', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User profiles, notification content, delivery metadata, and channel preferences.', + '_ettic_otc_sub_data_processed' => 'User profiles, notification content, delivery metadata, and channel preferences.', ], ], @@ -1227,12 +1227,12 @@ 'name' => 'Courier', 'aliases' => [ 'courier', 'courier.com' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Notification infrastructure for product messaging across channels.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://courier.com', + '_ettic_otc_sub_purpose' => 'Notification infrastructure for product messaging across channels.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://courier.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User profiles, notification content, delivery metadata, and channel preferences.', + '_ettic_otc_sub_data_processed' => 'User profiles, notification content, delivery metadata, and channel preferences.', ], ], @@ -1240,12 +1240,12 @@ 'name' => 'Cloudinary', 'aliases' => [ 'cloudinary' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Media management, image and video optimization, and delivery platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://cloudinary.com', + '_ettic_otc_sub_purpose' => 'Media management, image and video optimization, and delivery platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://cloudinary.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Uploaded images, videos, and associated metadata.', + '_ettic_otc_sub_data_processed' => 'Uploaded images, videos, and associated metadata.', ], ], @@ -1253,12 +1253,12 @@ 'name' => 'imgix', 'aliases' => [ 'imgix' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Real-time image processing and delivery platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://imgix.com', + '_ettic_otc_sub_purpose' => 'Real-time image processing and delivery platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://imgix.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Source images and associated metadata processed for delivery.', + '_ettic_otc_sub_data_processed' => 'Source images and associated metadata processed for delivery.', ], ], @@ -1266,11 +1266,11 @@ 'name' => 'Uploadcare', 'aliases' => [ 'uploadcare', 'upload care' ], 'fields' => [ - '_opentrust_sub_purpose' => 'File upload, storage, and media processing platform.', - '_opentrust_sub_website' => 'https://uploadcare.com', + '_ettic_otc_sub_purpose' => 'File upload, storage, and media processing platform.', + '_ettic_otc_sub_website' => 'https://uploadcare.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Uploaded files, images, videos, and associated metadata.', + '_ettic_otc_sub_data_processed' => 'Uploaded files, images, videos, and associated metadata.', ], ], @@ -1278,12 +1278,12 @@ 'name' => 'Bunny.net', 'aliases' => [ 'bunny', 'bunny.net', 'bunnycdn', 'bunny cdn' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Content delivery network and edge storage platform.', - '_opentrust_sub_country' => 'SI', - '_opentrust_sub_website' => 'https://bunny.net', + '_ettic_otc_sub_purpose' => 'Content delivery network and edge storage platform.', + '_ettic_otc_sub_country' => 'SI', + '_ettic_otc_sub_website' => 'https://bunny.net', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'HTTP request metadata, IP addresses, and cached content served to end users.', + '_ettic_otc_sub_data_processed' => 'HTTP request metadata, IP addresses, and cached content served to end users.', ], ], @@ -1291,12 +1291,12 @@ 'name' => 'Twilio', 'aliases' => [ 'twilio' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Communications API platform for SMS, voice, and messaging.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://twilio.com', + '_ettic_otc_sub_purpose' => 'Communications API platform for SMS, voice, and messaging.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://twilio.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Phone numbers, message content, call metadata, and delivery events.', + '_ettic_otc_sub_data_processed' => 'Phone numbers, message content, call metadata, and delivery events.', ], ], @@ -1304,12 +1304,12 @@ 'name' => 'Algolia', 'aliases' => [ 'algolia' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Hosted search and discovery API platform.', - '_opentrust_sub_country' => 'FR', - '_opentrust_sub_website' => 'https://algolia.com', + '_ettic_otc_sub_purpose' => 'Hosted search and discovery API platform.', + '_ettic_otc_sub_country' => 'FR', + '_ettic_otc_sub_website' => 'https://algolia.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Indexed content, search queries, and analytics events from end users.', + '_ettic_otc_sub_data_processed' => 'Indexed content, search queries, and analytics events from end users.', ], ], @@ -1317,11 +1317,11 @@ 'name' => 'Typesense', 'aliases' => [ 'typesense', 'typesense cloud' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Open-source search engine with managed cloud hosting.', - '_opentrust_sub_website' => 'https://typesense.org', + '_ettic_otc_sub_purpose' => 'Open-source search engine with managed cloud hosting.', + '_ettic_otc_sub_website' => 'https://typesense.org', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Indexed content and search queries from end users.', + '_ettic_otc_sub_data_processed' => 'Indexed content and search queries from end users.', ], ], @@ -1329,12 +1329,12 @@ 'name' => 'Meilisearch', 'aliases' => [ 'meilisearch', 'meili search', 'meili' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Open-source search engine with managed cloud hosting.', - '_opentrust_sub_country' => 'FR', - '_opentrust_sub_website' => 'https://meilisearch.com', + '_ettic_otc_sub_purpose' => 'Open-source search engine with managed cloud hosting.', + '_ettic_otc_sub_country' => 'FR', + '_ettic_otc_sub_website' => 'https://meilisearch.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Indexed content and search queries from end users.', + '_ettic_otc_sub_data_processed' => 'Indexed content and search queries from end users.', ], ], @@ -1342,12 +1342,12 @@ 'name' => 'Figma', 'aliases' => [ 'figma' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Collaborative interface design and prototyping platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://figma.com', + '_ettic_otc_sub_purpose' => 'Collaborative interface design and prototyping platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://figma.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User profiles, design files, comments, and collaboration metadata.', + '_ettic_otc_sub_data_processed' => 'User profiles, design files, comments, and collaboration metadata.', ], ], @@ -1355,12 +1355,12 @@ 'name' => 'Canva', 'aliases' => [ 'canva' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Online graphic design and publishing platform.', - '_opentrust_sub_country' => 'AU', - '_opentrust_sub_website' => 'https://canva.com', + '_ettic_otc_sub_purpose' => 'Online graphic design and publishing platform.', + '_ettic_otc_sub_country' => 'AU', + '_ettic_otc_sub_website' => 'https://canva.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User profiles, design files, uploaded assets, and collaboration metadata.', + '_ettic_otc_sub_data_processed' => 'User profiles, design files, uploaded assets, and collaboration metadata.', ], ], @@ -1368,12 +1368,12 @@ 'name' => 'Dropbox', 'aliases' => [ 'dropbox', 'drop box' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Cloud file storage and sharing platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://dropbox.com', + '_ettic_otc_sub_purpose' => 'Cloud file storage and sharing platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://dropbox.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Uploaded files, folder metadata, sharing links, and user profiles.', + '_ettic_otc_sub_data_processed' => 'Uploaded files, folder metadata, sharing links, and user profiles.', ], ], @@ -1381,12 +1381,12 @@ 'name' => 'Box', 'aliases' => [ 'box', 'box.com' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Cloud content management and file sharing platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://box.com', + '_ettic_otc_sub_purpose' => 'Cloud content management and file sharing platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://box.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Uploaded files, folder metadata, sharing links, and user profiles.', + '_ettic_otc_sub_data_processed' => 'Uploaded files, folder metadata, sharing links, and user profiles.', ], ], @@ -1394,12 +1394,12 @@ 'name' => 'Automattic', 'aliases' => [ 'automattic', 'automattic inc' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Parent company operating WordPress.com, Jetpack, Akismet, WooCommerce.com, and Tumblr.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://automattic.com', + '_ettic_otc_sub_purpose' => 'Parent company operating WordPress.com, Jetpack, Akismet, WooCommerce.com, and Tumblr.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://automattic.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Site content, visitor metadata, comments, backups, and account information.', + '_ettic_otc_sub_data_processed' => 'Site content, visitor metadata, comments, backups, and account information.', ], ], @@ -1407,12 +1407,12 @@ 'name' => 'Jetpack', 'aliases' => [ 'jetpack', 'jetpack.com' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Security, performance, backups, site stats, and content delivery for WordPress sites.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://jetpack.com', + '_ettic_otc_sub_purpose' => 'Security, performance, backups, site stats, and content delivery for WordPress sites.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://jetpack.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Site content, visitor metadata, comments, backup archives, and usage analytics from the connected WordPress site.', + '_ettic_otc_sub_data_processed' => 'Site content, visitor metadata, comments, backup archives, and usage analytics from the connected WordPress site.', ], ], @@ -1420,12 +1420,12 @@ 'name' => 'Akismet', 'aliases' => [ 'akismet', 'akismet anti-spam' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Spam detection for comments and form submissions on WordPress sites.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://akismet.com', + '_ettic_otc_sub_purpose' => 'Spam detection for comments and form submissions on WordPress sites.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://akismet.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Comment content, author names, email addresses, IP addresses, and user agent strings submitted for spam classification.', + '_ettic_otc_sub_data_processed' => 'Comment content, author names, email addresses, IP addresses, and user agent strings submitted for spam classification.', ], ], @@ -1433,12 +1433,12 @@ 'name' => 'WordPress.com', 'aliases' => [ 'wordpress.com', 'wordpress com', 'wp.com', 'wpcom' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed WordPress hosting, publishing, and site management service operated by Automattic.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://wordpress.com', + '_ettic_otc_sub_purpose' => 'Managed WordPress hosting, publishing, and site management service operated by Automattic.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://wordpress.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Site content, media uploads, visitor analytics, comments, and author account information.', + '_ettic_otc_sub_data_processed' => 'Site content, media uploads, visitor analytics, comments, and author account information.', ], ], @@ -1446,12 +1446,12 @@ 'name' => 'Meta', 'aliases' => [ 'meta', 'meta platforms', 'facebook', 'instagram', 'whatsapp', 'facebook business' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Social platform, advertising, and business tools from Meta, covering Facebook, Instagram, and WhatsApp.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://about.meta.com', + '_ettic_otc_sub_purpose' => 'Social platform, advertising, and business tools from Meta, covering Facebook, Instagram, and WhatsApp.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://about.meta.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Advertising events, custom audience data, Meta Pixel and Conversions API events, and authentication data when Facebook Login is used.', + '_ettic_otc_sub_data_processed' => 'Advertising events, custom audience data, Meta Pixel and Conversions API events, and authentication data when Facebook Login is used.', ], ], @@ -1459,12 +1459,12 @@ 'name' => 'MOSA Cloud', 'aliases' => [ 'mosa', 'mosa cloud', 'mosa.cloud', 'mosacloud' ], 'fields' => [ - '_opentrust_sub_purpose' => 'EU-based productivity suite with documents, file storage, email, and video meetings, built on open-source infrastructure.', - '_opentrust_sub_country' => 'NL', - '_opentrust_sub_website' => 'https://mosa.cloud', + '_ettic_otc_sub_purpose' => 'EU-based productivity suite with documents, file storage, email, and video meetings, built on open-source infrastructure.', + '_ettic_otc_sub_country' => 'NL', + '_ettic_otc_sub_website' => 'https://mosa.cloud', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Documents, spreadsheets, uploaded files, email messages, meeting recordings and transcripts, and team communications.', + '_ettic_otc_sub_data_processed' => 'Documents, spreadsheets, uploaded files, email messages, meeting recordings and transcripts, and team communications.', ], ], @@ -1474,12 +1474,12 @@ 'name' => 'WP Engine', 'aliases' => [ 'wpengine', 'wp engine' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed WordPress hosting and site infrastructure.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://wpengine.com', + '_ettic_otc_sub_purpose' => 'Managed WordPress hosting and site infrastructure.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://wpengine.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Website files, database contents, visitor IP addresses, server logs, and account billing information.', + '_ettic_otc_sub_data_processed' => 'Website files, database contents, visitor IP addresses, server logs, and account billing information.', ], ], @@ -1487,12 +1487,12 @@ 'name' => 'Kinsta', 'aliases' => [ 'kinsta' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed WordPress and application hosting built on Google Cloud.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://kinsta.com', + '_ettic_otc_sub_purpose' => 'Managed WordPress and application hosting built on Google Cloud.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://kinsta.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Website files, database contents, visitor IP addresses, access logs, and account billing information.', + '_ettic_otc_sub_data_processed' => 'Website files, database contents, visitor IP addresses, access logs, and account billing information.', ], ], @@ -1500,12 +1500,12 @@ 'name' => 'Pantheon', 'aliases' => [ 'pantheon', 'getpantheon' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed WordPress and Drupal website operations platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://pantheon.io', + '_ettic_otc_sub_purpose' => 'Managed WordPress and Drupal website operations platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://pantheon.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Website codebase, database contents, visitor request logs, and customer account details.', + '_ettic_otc_sub_data_processed' => 'Website codebase, database contents, visitor request logs, and customer account details.', ], ], @@ -1513,12 +1513,12 @@ 'name' => 'Pressable', 'aliases' => [ 'pressable', 'automattic pressable' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed WordPress hosting operated by Automattic.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://pressable.com', + '_ettic_otc_sub_purpose' => 'Managed WordPress hosting operated by Automattic.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://pressable.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Website files, database contents, visitor IP addresses, access logs, and billing details.', + '_ettic_otc_sub_data_processed' => 'Website files, database contents, visitor IP addresses, access logs, and billing details.', ], ], @@ -1526,11 +1526,11 @@ 'name' => 'SiteGround', 'aliases' => [ 'siteground', 'site ground' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Shared, cloud, and managed WordPress hosting services.', - '_opentrust_sub_website' => 'https://siteground.com', + '_ettic_otc_sub_purpose' => 'Shared, cloud, and managed WordPress hosting services.', + '_ettic_otc_sub_website' => 'https://siteground.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Website files, database contents, visitor IP addresses, server logs, and customer account information.', + '_ettic_otc_sub_data_processed' => 'Website files, database contents, visitor IP addresses, server logs, and customer account information.', ], ], @@ -1538,11 +1538,11 @@ 'name' => 'Cloudways', 'aliases' => [ 'cloudways' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed cloud hosting platform for web applications and WordPress.', - '_opentrust_sub_website' => 'https://cloudways.com', + '_ettic_otc_sub_purpose' => 'Managed cloud hosting platform for web applications and WordPress.', + '_ettic_otc_sub_website' => 'https://cloudways.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Website files, database contents, visitor logs, and customer account and billing details.', + '_ettic_otc_sub_data_processed' => 'Website files, database contents, visitor logs, and customer account and billing details.', ], ], @@ -1550,12 +1550,12 @@ 'name' => 'Flywheel', 'aliases' => [ 'flywheel', 'getflywheel' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed WordPress hosting for designers and agencies, owned by WP Engine.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://getflywheel.com', + '_ettic_otc_sub_purpose' => 'Managed WordPress hosting for designers and agencies, owned by WP Engine.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://getflywheel.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Website files, database contents, visitor IP addresses, access logs, and account billing information.', + '_ettic_otc_sub_data_processed' => 'Website files, database contents, visitor IP addresses, access logs, and account billing information.', ], ], @@ -1563,12 +1563,12 @@ 'name' => 'WooCommerce.com', 'aliases' => [ 'woocommerce', 'woo' ], 'fields' => [ - '_opentrust_sub_purpose' => 'WooCommerce extension marketplace and account services operated by Automattic.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://woocommerce.com', + '_ettic_otc_sub_purpose' => 'WooCommerce extension marketplace and account services operated by Automattic.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://woocommerce.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer account details, license keys, purchase history, and billing information.', + '_ettic_otc_sub_data_processed' => 'Customer account details, license keys, purchase history, and billing information.', ], ], @@ -1578,12 +1578,12 @@ 'name' => 'Bugsnag', 'aliases' => [ 'bugsnag', 'smartbear bugsnag' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Error monitoring and application stability reporting.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://bugsnag.com', + '_ettic_otc_sub_purpose' => 'Error monitoring and application stability reporting.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://bugsnag.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application error stack traces, user identifiers, device and browser metadata, and request context.', + '_ettic_otc_sub_data_processed' => 'Application error stack traces, user identifiers, device and browser metadata, and request context.', ], ], @@ -1591,12 +1591,12 @@ 'name' => 'Rollbar', 'aliases' => [ 'rollbar' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Error tracking and real-time exception monitoring.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://rollbar.com', + '_ettic_otc_sub_purpose' => 'Error tracking and real-time exception monitoring.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://rollbar.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application error stack traces, user identifiers, environment metadata, and request payloads.', + '_ettic_otc_sub_data_processed' => 'Application error stack traces, user identifiers, environment metadata, and request payloads.', ], ], @@ -1604,12 +1604,12 @@ 'name' => 'AppSignal', 'aliases' => [ 'appsignal' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Application performance monitoring and error tracking.', - '_opentrust_sub_country' => 'NL', - '_opentrust_sub_website' => 'https://appsignal.com', + '_ettic_otc_sub_purpose' => 'Application performance monitoring and error tracking.', + '_ettic_otc_sub_country' => 'NL', + '_ettic_otc_sub_website' => 'https://appsignal.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Performance metrics, error stack traces, request metadata, and user identifiers.', + '_ettic_otc_sub_data_processed' => 'Performance metrics, error stack traces, request metadata, and user identifiers.', ], ], @@ -1617,12 +1617,12 @@ 'name' => 'Raygun', 'aliases' => [ 'raygun' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Crash reporting, real user monitoring, and application performance monitoring.', - '_opentrust_sub_country' => 'NZ', - '_opentrust_sub_website' => 'https://raygun.com', + '_ettic_otc_sub_purpose' => 'Crash reporting, real user monitoring, and application performance monitoring.', + '_ettic_otc_sub_country' => 'NZ', + '_ettic_otc_sub_website' => 'https://raygun.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Error diagnostics, session traces, user identifiers, and browser and device metadata.', + '_ettic_otc_sub_data_processed' => 'Error diagnostics, session traces, user identifiers, and browser and device metadata.', ], ], @@ -1630,12 +1630,12 @@ 'name' => 'Pingdom', 'aliases' => [ 'pingdom', 'solarwinds pingdom' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Website uptime, page speed, and synthetic transaction monitoring.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://pingdom.com', + '_ettic_otc_sub_purpose' => 'Website uptime, page speed, and synthetic transaction monitoring.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://pingdom.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Monitored URLs, response timing metrics, and alert contact details.', + '_ettic_otc_sub_data_processed' => 'Monitored URLs, response timing metrics, and alert contact details.', ], ], @@ -1643,12 +1643,12 @@ 'name' => 'UptimeRobot', 'aliases' => [ 'uptimerobot', 'uptime robot' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Website and service uptime monitoring with alerting.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://uptimerobot.com', + '_ettic_otc_sub_purpose' => 'Website and service uptime monitoring with alerting.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://uptimerobot.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Monitored URLs, response status, latency metrics, and alert contact details.', + '_ettic_otc_sub_data_processed' => 'Monitored URLs, response status, latency metrics, and alert contact details.', ], ], @@ -1656,12 +1656,12 @@ 'name' => 'Statuspage', 'aliases' => [ 'statuspage', 'atlassian statuspage' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Hosted status pages and incident communication for services.', - '_opentrust_sub_country' => 'AU', - '_opentrust_sub_website' => 'https://atlassian.com/software/statuspage', + '_ettic_otc_sub_purpose' => 'Hosted status pages and incident communication for services.', + '_ettic_otc_sub_country' => 'AU', + '_ettic_otc_sub_website' => 'https://atlassian.com/software/statuspage', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Subscriber email addresses, incident notes, component statuses, and administrator account details.', + '_ettic_otc_sub_data_processed' => 'Subscriber email addresses, incident notes, component statuses, and administrator account details.', ], ], @@ -1669,12 +1669,12 @@ 'name' => 'Highlight.io', 'aliases' => [ 'highlight', 'highlight.io' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Session replay, error monitoring, and application observability.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://highlight.io', + '_ettic_otc_sub_purpose' => 'Session replay, error monitoring, and application observability.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://highlight.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Session recordings, DOM events, console logs, error traces, and user identifiers.', + '_ettic_otc_sub_data_processed' => 'Session recordings, DOM events, console logs, error traces, and user identifiers.', ], ], @@ -1682,12 +1682,12 @@ 'name' => 'Loggly', 'aliases' => [ 'loggly', 'solarwinds loggly' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Cloud-based log aggregation and analysis.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://loggly.com', + '_ettic_otc_sub_purpose' => 'Cloud-based log aggregation and analysis.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://loggly.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application and server log events, request metadata, and any identifiers contained in logs.', + '_ettic_otc_sub_data_processed' => 'Application and server log events, request metadata, and any identifiers contained in logs.', ], ], @@ -1695,12 +1695,12 @@ 'name' => 'Papertrail', 'aliases' => [ 'papertrail', 'solarwinds papertrail' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Cloud-hosted log management and live tail for servers and apps.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://papertrail.com', + '_ettic_otc_sub_purpose' => 'Cloud-hosted log management and live tail for servers and apps.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://papertrail.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Syslog and application log events, hostnames, and any identifiers included in log lines.', + '_ettic_otc_sub_data_processed' => 'Syslog and application log events, hostnames, and any identifiers included in log lines.', ], ], @@ -1710,12 +1710,12 @@ 'name' => 'ActiveCampaign', 'aliases' => [ 'activecampaign', 'active campaign' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Email marketing, marketing automation, and sales CRM.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://activecampaign.com', + '_ettic_otc_sub_purpose' => 'Email marketing, marketing automation, and sales CRM.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://activecampaign.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Contact names, email addresses, engagement history, tags, and campaign interaction data.', + '_ettic_otc_sub_data_processed' => 'Contact names, email addresses, engagement history, tags, and campaign interaction data.', ], ], @@ -1723,12 +1723,12 @@ 'name' => 'MailerLite', 'aliases' => [ 'mailerlite', 'mailer lite' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Email marketing, newsletters, and automation.', - '_opentrust_sub_country' => 'LT', - '_opentrust_sub_website' => 'https://mailerlite.com', + '_ettic_otc_sub_purpose' => 'Email marketing, newsletters, and automation.', + '_ettic_otc_sub_country' => 'LT', + '_ettic_otc_sub_website' => 'https://mailerlite.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Subscriber email addresses, names, segmentation fields, and email engagement metrics.', + '_ettic_otc_sub_data_processed' => 'Subscriber email addresses, names, segmentation fields, and email engagement metrics.', ], ], @@ -1736,12 +1736,12 @@ 'name' => 'AWeber', 'aliases' => [ 'aweber' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Email marketing and autoresponder platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://aweber.com', + '_ettic_otc_sub_purpose' => 'Email marketing and autoresponder platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://aweber.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Subscriber email addresses, names, list membership, and campaign engagement data.', + '_ettic_otc_sub_data_processed' => 'Subscriber email addresses, names, list membership, and campaign engagement data.', ], ], @@ -1749,12 +1749,12 @@ 'name' => 'GetResponse', 'aliases' => [ 'getresponse', 'get response' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Email marketing, automation, and landing pages.', - '_opentrust_sub_country' => 'PL', - '_opentrust_sub_website' => 'https://getresponse.com', + '_ettic_otc_sub_purpose' => 'Email marketing, automation, and landing pages.', + '_ettic_otc_sub_country' => 'PL', + '_ettic_otc_sub_website' => 'https://getresponse.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Subscriber email addresses, names, segmentation data, and campaign engagement metrics.', + '_ettic_otc_sub_data_processed' => 'Subscriber email addresses, names, segmentation data, and campaign engagement metrics.', ], ], @@ -1762,12 +1762,12 @@ 'name' => 'Campaign Monitor', 'aliases' => [ 'campaignmonitor', 'campaign monitor' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Email marketing and transactional email delivery.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://campaignmonitor.com', + '_ettic_otc_sub_purpose' => 'Email marketing and transactional email delivery.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://campaignmonitor.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Subscriber email addresses, names, list data, and email engagement metrics.', + '_ettic_otc_sub_data_processed' => 'Subscriber email addresses, names, list data, and email engagement metrics.', ], ], @@ -1775,12 +1775,12 @@ 'name' => 'Drip', 'aliases' => [ 'drip', 'getdrip' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Email marketing and automation focused on ecommerce.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://drip.com', + '_ettic_otc_sub_purpose' => 'Email marketing and automation focused on ecommerce.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://drip.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer email addresses, purchase history, browsing events, and segmentation data.', + '_ettic_otc_sub_data_processed' => 'Customer email addresses, purchase history, browsing events, and segmentation data.', ], ], @@ -1788,12 +1788,12 @@ 'name' => 'Beehiiv', 'aliases' => [ 'beehiiv' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Newsletter publishing, audience growth, and monetization platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://beehiiv.com', + '_ettic_otc_sub_purpose' => 'Newsletter publishing, audience growth, and monetization platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://beehiiv.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Subscriber email addresses, names, engagement metrics, and referral data.', + '_ettic_otc_sub_data_processed' => 'Subscriber email addresses, names, engagement metrics, and referral data.', ], ], @@ -1801,12 +1801,12 @@ 'name' => 'EmailOctopus', 'aliases' => [ 'emailoctopus', 'email octopus' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Email marketing and newsletter delivery service.', - '_opentrust_sub_country' => 'GB', - '_opentrust_sub_website' => 'https://emailoctopus.com', + '_ettic_otc_sub_purpose' => 'Email marketing and newsletter delivery service.', + '_ettic_otc_sub_country' => 'GB', + '_ettic_otc_sub_website' => 'https://emailoctopus.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Subscriber email addresses, names, list membership, and campaign engagement metrics.', + '_ettic_otc_sub_data_processed' => 'Subscriber email addresses, names, list membership, and campaign engagement metrics.', ], ], @@ -1814,12 +1814,12 @@ 'name' => 'Close', 'aliases' => [ 'close', 'closecrm', 'close.io' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Sales CRM with built-in calling, email, and SMS.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://close.com', + '_ettic_otc_sub_purpose' => 'Sales CRM with built-in calling, email, and SMS.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://close.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Lead and contact details, call recordings, email threads, and sales pipeline data.', + '_ettic_otc_sub_data_processed' => 'Lead and contact details, call recordings, email threads, and sales pipeline data.', ], ], @@ -1827,12 +1827,12 @@ 'name' => 'Attio', 'aliases' => [ 'attio' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Customer relationship management and customer data platform.', - '_opentrust_sub_country' => 'GB', - '_opentrust_sub_website' => 'https://attio.com', + '_ettic_otc_sub_purpose' => 'Customer relationship management and customer data platform.', + '_ettic_otc_sub_country' => 'GB', + '_ettic_otc_sub_website' => 'https://attio.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Contact and company records, email metadata, and relationship activity history.', + '_ettic_otc_sub_data_processed' => 'Contact and company records, email metadata, and relationship activity history.', ], ], @@ -1840,12 +1840,12 @@ 'name' => 'Copper', 'aliases' => [ 'copper', 'coppercrm' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Customer relationship management integrated with Google Workspace.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://copper.com', + '_ettic_otc_sub_purpose' => 'Customer relationship management integrated with Google Workspace.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://copper.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Contact details, email threads, calendar events, and sales pipeline records.', + '_ettic_otc_sub_data_processed' => 'Contact details, email threads, calendar events, and sales pipeline records.', ], ], @@ -1855,12 +1855,12 @@ 'name' => 'Square', 'aliases' => [ 'square', 'squareup' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Payment processing, point of sale, and merchant services.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://squareup.com', + '_ettic_otc_sub_purpose' => 'Payment processing, point of sale, and merchant services.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://squareup.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Tokenized payment card details, customer names, billing information, and transaction records.', + '_ettic_otc_sub_data_processed' => 'Tokenized payment card details, customer names, billing information, and transaction records.', ], ], @@ -1868,12 +1868,12 @@ 'name' => 'Adyen', 'aliases' => [ 'adyen' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Global payment processing and unified commerce platform.', - '_opentrust_sub_country' => 'NL', - '_opentrust_sub_website' => 'https://adyen.com', + '_ettic_otc_sub_purpose' => 'Global payment processing and unified commerce platform.', + '_ettic_otc_sub_country' => 'NL', + '_ettic_otc_sub_website' => 'https://adyen.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Tokenized payment card details, billing addresses, customer identifiers, and transaction metadata.', + '_ettic_otc_sub_data_processed' => 'Tokenized payment card details, billing addresses, customer identifiers, and transaction metadata.', ], ], @@ -1881,12 +1881,12 @@ 'name' => 'Mollie', 'aliases' => [ 'mollie' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Payment processing for European businesses.', - '_opentrust_sub_country' => 'NL', - '_opentrust_sub_website' => 'https://mollie.com', + '_ettic_otc_sub_purpose' => 'Payment processing for European businesses.', + '_ettic_otc_sub_country' => 'NL', + '_ettic_otc_sub_website' => 'https://mollie.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Tokenized payment details, customer names, billing information, and transaction records.', + '_ettic_otc_sub_data_processed' => 'Tokenized payment details, customer names, billing information, and transaction records.', ], ], @@ -1894,12 +1894,12 @@ 'name' => 'GoCardless', 'aliases' => [ 'gocardless', 'go cardless' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Bank debit and recurring payment collection.', - '_opentrust_sub_country' => 'GB', - '_opentrust_sub_website' => 'https://gocardless.com', + '_ettic_otc_sub_purpose' => 'Bank debit and recurring payment collection.', + '_ettic_otc_sub_country' => 'GB', + '_ettic_otc_sub_website' => 'https://gocardless.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Bank account details, customer names, billing addresses, and mandate and transaction records.', + '_ettic_otc_sub_data_processed' => 'Bank account details, customer names, billing addresses, and mandate and transaction records.', ], ], @@ -1907,12 +1907,12 @@ 'name' => 'Checkout.com', 'aliases' => [ 'checkout', 'checkout.com' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Global online payment processing and acquiring.', - '_opentrust_sub_country' => 'GB', - '_opentrust_sub_website' => 'https://checkout.com', + '_ettic_otc_sub_purpose' => 'Global online payment processing and acquiring.', + '_ettic_otc_sub_country' => 'GB', + '_ettic_otc_sub_website' => 'https://checkout.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Tokenized payment card details, billing addresses, customer identifiers, and transaction metadata.', + '_ettic_otc_sub_data_processed' => 'Tokenized payment card details, billing addresses, customer identifiers, and transaction metadata.', ], ], @@ -1920,12 +1920,12 @@ 'name' => 'Wise Business', 'aliases' => [ 'wise', 'transferwise', 'wise business' ], 'fields' => [ - '_opentrust_sub_purpose' => 'International business payments, multi-currency accounts, and FX.', - '_opentrust_sub_country' => 'GB', - '_opentrust_sub_website' => 'https://wise.com/business', + '_ettic_otc_sub_purpose' => 'International business payments, multi-currency accounts, and FX.', + '_ettic_otc_sub_country' => 'GB', + '_ettic_otc_sub_website' => 'https://wise.com/business', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Business and beneficiary bank details, payer and payee names, and transaction records.', + '_ettic_otc_sub_data_processed' => 'Business and beneficiary bank details, payer and payee names, and transaction records.', ], ], @@ -1935,12 +1935,12 @@ 'name' => 'Okta', 'aliases' => [ 'okta' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Workforce and customer identity, single sign-on, and access management.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://okta.com', + '_ettic_otc_sub_purpose' => 'Workforce and customer identity, single sign-on, and access management.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://okta.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User directory attributes, authentication events, group memberships, and session metadata.', + '_ettic_otc_sub_data_processed' => 'User directory attributes, authentication events, group memberships, and session metadata.', ], ], @@ -1948,12 +1948,12 @@ 'name' => 'OneLogin', 'aliases' => [ 'onelogin', 'one login' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Identity and access management with single sign-on and MFA.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://onelogin.com', + '_ettic_otc_sub_purpose' => 'Identity and access management with single sign-on and MFA.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://onelogin.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User directory attributes, authentication events, and session and device metadata.', + '_ettic_otc_sub_data_processed' => 'User directory attributes, authentication events, and session and device metadata.', ], ], @@ -1961,12 +1961,12 @@ 'name' => 'Duo Security', 'aliases' => [ 'duo', 'duo security', 'cisco duo' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Multi-factor authentication and device trust.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://duo.com', + '_ettic_otc_sub_purpose' => 'Multi-factor authentication and device trust.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://duo.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User identifiers, device fingerprints, authentication events, and phone numbers for MFA.', + '_ettic_otc_sub_data_processed' => 'User identifiers, device fingerprints, authentication events, and phone numbers for MFA.', ], ], @@ -1974,12 +1974,12 @@ 'name' => 'Kinde', 'aliases' => [ 'kinde' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Customer authentication, user management, and feature flags for applications.', - '_opentrust_sub_country' => 'AU', - '_opentrust_sub_website' => 'https://kinde.com', + '_ettic_otc_sub_purpose' => 'Customer authentication, user management, and feature flags for applications.', + '_ettic_otc_sub_country' => 'AU', + '_ettic_otc_sub_website' => 'https://kinde.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'End user email addresses, names, authentication events, and profile attributes.', + '_ettic_otc_sub_data_processed' => 'End user email addresses, names, authentication events, and profile attributes.', ], ], @@ -1987,12 +1987,12 @@ 'name' => 'SuperTokens', 'aliases' => [ 'supertokens', 'super tokens' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Open-source authentication platform with managed and self-hosted options.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://supertokens.com', + '_ettic_otc_sub_purpose' => 'Open-source authentication platform with managed and self-hosted options.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://supertokens.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'End user email addresses, hashed credentials, session tokens, and profile attributes.', + '_ettic_otc_sub_data_processed' => 'End user email addresses, hashed credentials, session tokens, and profile attributes.', ], ], @@ -2000,12 +2000,12 @@ 'name' => '1Password Business', 'aliases' => [ '1password', 'onepassword', '1pw' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Password and secrets management for teams.', - '_opentrust_sub_country' => 'CA', - '_opentrust_sub_website' => 'https://1password.com/business', + '_ettic_otc_sub_purpose' => 'Password and secrets management for teams.', + '_ettic_otc_sub_country' => 'CA', + '_ettic_otc_sub_website' => 'https://1password.com/business', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Encrypted vault contents, user account details, and access and audit logs.', + '_ettic_otc_sub_data_processed' => 'Encrypted vault contents, user account details, and access and audit logs.', ], ], @@ -2013,12 +2013,12 @@ 'name' => 'Bitwarden Business', 'aliases' => [ 'bitwarden' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Open-source password and secrets management for teams.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://bitwarden.com/products/business', + '_ettic_otc_sub_purpose' => 'Open-source password and secrets management for teams.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://bitwarden.com/products/business', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Encrypted vault contents, user account details, and access and audit logs.', + '_ettic_otc_sub_data_processed' => 'Encrypted vault contents, user account details, and access and audit logs.', ], ], @@ -2028,11 +2028,11 @@ 'name' => 'Elastic Cloud', 'aliases' => [ 'elastic', 'elasticsearch', 'elastic cloud' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed Elasticsearch, search, and observability service.', - '_opentrust_sub_website' => 'https://elastic.co/cloud', + '_ettic_otc_sub_purpose' => 'Managed Elasticsearch, search, and observability service.', + '_ettic_otc_sub_website' => 'https://elastic.co/cloud', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Indexed documents, search queries, and any identifiers contained in ingested data.', + '_ettic_otc_sub_data_processed' => 'Indexed documents, search queries, and any identifiers contained in ingested data.', ], ], @@ -2040,11 +2040,11 @@ 'name' => 'Redis Cloud', 'aliases' => [ 'redis', 'redis cloud', 'redis enterprise' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed Redis database and in-memory data platform.', - '_opentrust_sub_website' => 'https://redis.io/cloud', + '_ettic_otc_sub_purpose' => 'Managed Redis database and in-memory data platform.', + '_ettic_otc_sub_website' => 'https://redis.io/cloud', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Cached and stored key-value data, including any identifiers written by the application.', + '_ettic_otc_sub_data_processed' => 'Cached and stored key-value data, including any identifiers written by the application.', ], ], @@ -2052,11 +2052,11 @@ 'name' => 'CockroachDB Cloud', 'aliases' => [ 'cockroach', 'cockroachdb', 'cockroach cloud' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed distributed SQL database service.', - '_opentrust_sub_website' => 'https://cockroachlabs.com/product/cockroachdb-cloud', + '_ettic_otc_sub_purpose' => 'Managed distributed SQL database service.', + '_ettic_otc_sub_website' => 'https://cockroachlabs.com/product/cockroachdb-cloud', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application database contents, including any personal data written by the application.', + '_ettic_otc_sub_data_processed' => 'Application database contents, including any personal data written by the application.', ], ], @@ -2064,11 +2064,11 @@ 'name' => 'Fauna', 'aliases' => [ 'fauna', 'faunadb' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Serverless distributed document and relational database.', - '_opentrust_sub_website' => 'https://fauna.com', + '_ettic_otc_sub_purpose' => 'Serverless distributed document and relational database.', + '_ettic_otc_sub_website' => 'https://fauna.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application database contents, including any personal data written by the application.', + '_ettic_otc_sub_data_processed' => 'Application database contents, including any personal data written by the application.', ], ], @@ -2076,11 +2076,11 @@ 'name' => 'Xata', 'aliases' => [ 'xata' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Serverless database platform with built-in search and analytics.', - '_opentrust_sub_website' => 'https://xata.io', + '_ettic_otc_sub_purpose' => 'Serverless database platform with built-in search and analytics.', + '_ettic_otc_sub_website' => 'https://xata.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Application database contents, including any personal data written by the application.', + '_ettic_otc_sub_data_processed' => 'Application database contents, including any personal data written by the application.', ], ], @@ -2090,12 +2090,12 @@ 'name' => 'Cohere', 'aliases' => [ 'cohere' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Large language model APIs for text generation, embeddings, and retrieval.', - '_opentrust_sub_country' => 'CA', - '_opentrust_sub_website' => 'https://cohere.com', + '_ettic_otc_sub_purpose' => 'Large language model APIs for text generation, embeddings, and retrieval.', + '_ettic_otc_sub_country' => 'CA', + '_ettic_otc_sub_website' => 'https://cohere.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Prompt and completion text, embeddings input, and API usage metadata.', + '_ettic_otc_sub_data_processed' => 'Prompt and completion text, embeddings input, and API usage metadata.', ], ], @@ -2103,12 +2103,12 @@ 'name' => 'Hugging Face', 'aliases' => [ 'huggingface', 'hugging face', 'hf' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Machine learning model hosting, inference APIs, and collaboration platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://huggingface.co', + '_ettic_otc_sub_purpose' => 'Machine learning model hosting, inference APIs, and collaboration platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://huggingface.co', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Model inference inputs and outputs, uploaded datasets, and account metadata.', + '_ettic_otc_sub_data_processed' => 'Model inference inputs and outputs, uploaded datasets, and account metadata.', ], ], @@ -2116,12 +2116,12 @@ 'name' => 'Groq', 'aliases' => [ 'groq' ], 'fields' => [ - '_opentrust_sub_purpose' => 'High-speed inference API for large language models.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://groq.com', + '_ettic_otc_sub_purpose' => 'High-speed inference API for large language models.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://groq.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Prompt and completion text and API usage metadata.', + '_ettic_otc_sub_data_processed' => 'Prompt and completion text and API usage metadata.', ], ], @@ -2129,12 +2129,12 @@ 'name' => 'Together AI', 'aliases' => [ 'together', 'together ai', 'togetherai' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Inference and fine-tuning APIs for open-source large language models.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://together.ai', + '_ettic_otc_sub_purpose' => 'Inference and fine-tuning APIs for open-source large language models.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://together.ai', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Prompt and completion text, fine-tuning datasets, and API usage metadata.', + '_ettic_otc_sub_data_processed' => 'Prompt and completion text, fine-tuning datasets, and API usage metadata.', ], ], @@ -2142,12 +2142,12 @@ 'name' => 'Fireworks AI', 'aliases' => [ 'fireworks', 'fireworks ai' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Inference platform for open-source generative AI models.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://fireworks.ai', + '_ettic_otc_sub_purpose' => 'Inference platform for open-source generative AI models.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://fireworks.ai', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Prompt and completion text, fine-tuning datasets, and API usage metadata.', + '_ettic_otc_sub_data_processed' => 'Prompt and completion text, fine-tuning datasets, and API usage metadata.', ], ], @@ -2155,12 +2155,12 @@ 'name' => 'ElevenLabs', 'aliases' => [ 'elevenlabs', 'eleven labs', '11labs' ], 'fields' => [ - '_opentrust_sub_purpose' => 'AI voice synthesis, text to speech, and voice cloning APIs.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://elevenlabs.io', + '_ettic_otc_sub_purpose' => 'AI voice synthesis, text to speech, and voice cloning APIs.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://elevenlabs.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Submitted text, voice samples, generated audio, and API usage metadata.', + '_ettic_otc_sub_data_processed' => 'Submitted text, voice samples, generated audio, and API usage metadata.', ], ], @@ -2168,12 +2168,12 @@ 'name' => 'AssemblyAI', 'aliases' => [ 'assemblyai', 'assembly ai' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Speech to text, transcription, and audio intelligence APIs.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://assemblyai.com', + '_ettic_otc_sub_purpose' => 'Speech to text, transcription, and audio intelligence APIs.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://assemblyai.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Uploaded audio recordings, generated transcripts, and API usage metadata.', + '_ettic_otc_sub_data_processed' => 'Uploaded audio recordings, generated transcripts, and API usage metadata.', ], ], @@ -2181,12 +2181,12 @@ 'name' => 'Deepgram', 'aliases' => [ 'deepgram' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Speech to text and voice AI APIs for real-time and batch transcription.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://deepgram.com', + '_ettic_otc_sub_purpose' => 'Speech to text and voice AI APIs for real-time and batch transcription.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://deepgram.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Uploaded audio, streamed voice data, generated transcripts, and API usage metadata.', + '_ettic_otc_sub_data_processed' => 'Uploaded audio, streamed voice data, generated transcripts, and API usage metadata.', ], ], @@ -2194,12 +2194,12 @@ 'name' => 'Pinecone', 'aliases' => [ 'pinecone' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed vector database for similarity search and retrieval augmented generation.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://pinecone.io', + '_ettic_otc_sub_purpose' => 'Managed vector database for similarity search and retrieval augmented generation.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://pinecone.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Stored vector embeddings, associated metadata, and query inputs.', + '_ettic_otc_sub_data_processed' => 'Stored vector embeddings, associated metadata, and query inputs.', ], ], @@ -2207,11 +2207,11 @@ 'name' => 'Weaviate', 'aliases' => [ 'weaviate' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed vector database and AI-native search engine.', - '_opentrust_sub_website' => 'https://weaviate.io', + '_ettic_otc_sub_purpose' => 'Managed vector database and AI-native search engine.', + '_ettic_otc_sub_website' => 'https://weaviate.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Stored vector embeddings, associated metadata, and query inputs.', + '_ettic_otc_sub_data_processed' => 'Stored vector embeddings, associated metadata, and query inputs.', ], ], @@ -2219,11 +2219,11 @@ 'name' => 'Qdrant', 'aliases' => [ 'qdrant' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed vector database for semantic search and AI applications.', - '_opentrust_sub_website' => 'https://qdrant.tech', + '_ettic_otc_sub_purpose' => 'Managed vector database for semantic search and AI applications.', + '_ettic_otc_sub_website' => 'https://qdrant.tech', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Stored vector embeddings, associated metadata, and query inputs.', + '_ettic_otc_sub_data_processed' => 'Stored vector embeddings, associated metadata, and query inputs.', ], ], @@ -2233,12 +2233,12 @@ 'name' => 'Mux', 'aliases' => [ 'mux' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Video streaming infrastructure, encoding, and playback analytics.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://mux.com', + '_ettic_otc_sub_purpose' => 'Video streaming infrastructure, encoding, and playback analytics.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://mux.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Uploaded video assets, viewer IP addresses, playback events, and device metadata.', + '_ettic_otc_sub_data_processed' => 'Uploaded video assets, viewer IP addresses, playback events, and device metadata.', ], ], @@ -2246,12 +2246,12 @@ 'name' => 'Vimeo', 'aliases' => [ 'vimeo' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Video hosting, streaming, and player services.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://vimeo.com', + '_ettic_otc_sub_purpose' => 'Video hosting, streaming, and player services.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://vimeo.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Uploaded video assets, viewer IP addresses, playback events, and account information.', + '_ettic_otc_sub_data_processed' => 'Uploaded video assets, viewer IP addresses, playback events, and account information.', ], ], @@ -2259,12 +2259,12 @@ 'name' => 'Wistia', 'aliases' => [ 'wistia' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Business video hosting, player, and engagement analytics.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://wistia.com', + '_ettic_otc_sub_purpose' => 'Business video hosting, player, and engagement analytics.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://wistia.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Uploaded video assets, viewer IP addresses, engagement events, and lead capture data.', + '_ettic_otc_sub_data_processed' => 'Uploaded video assets, viewer IP addresses, engagement events, and lead capture data.', ], ], @@ -2272,12 +2272,12 @@ 'name' => 'Daily.co', 'aliases' => [ 'daily', 'daily.co' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Real-time video and audio APIs for embedded calls and meetings.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://daily.co', + '_ettic_otc_sub_purpose' => 'Real-time video and audio APIs for embedded calls and meetings.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://daily.co', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Participant audio and video streams, session metadata, and optional recordings.', + '_ettic_otc_sub_data_processed' => 'Participant audio and video streams, session metadata, and optional recordings.', ], ], @@ -2285,12 +2285,12 @@ 'name' => 'LiveKit', 'aliases' => [ 'livekit', 'live kit' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Real-time audio, video, and data infrastructure built on WebRTC.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://livekit.io', + '_ettic_otc_sub_purpose' => 'Real-time audio, video, and data infrastructure built on WebRTC.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://livekit.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Participant audio and video streams, session metadata, and optional recordings.', + '_ettic_otc_sub_data_processed' => 'Participant audio and video streams, session metadata, and optional recordings.', ], ], @@ -2298,11 +2298,11 @@ 'name' => 'Agora', 'aliases' => [ 'agora', 'agora.io' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Real-time voice, video, and interactive streaming APIs.', - '_opentrust_sub_website' => 'https://agora.io', + '_ettic_otc_sub_purpose' => 'Real-time voice, video, and interactive streaming APIs.', + '_ettic_otc_sub_website' => 'https://agora.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Participant audio and video streams, session metadata, and optional recordings.', + '_ettic_otc_sub_data_processed' => 'Participant audio and video streams, session metadata, and optional recordings.', ], ], @@ -2312,12 +2312,12 @@ 'name' => 'Jira', 'aliases' => [ 'jira', 'atlassian jira' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Issue tracking and agile project management.', - '_opentrust_sub_country' => 'AU', - '_opentrust_sub_website' => 'https://atlassian.com/software/jira', + '_ettic_otc_sub_purpose' => 'Issue tracking and agile project management.', + '_ettic_otc_sub_country' => 'AU', + '_ettic_otc_sub_website' => 'https://atlassian.com/software/jira', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User account details, issue contents, comments, attachments, and project metadata.', + '_ettic_otc_sub_data_processed' => 'User account details, issue contents, comments, attachments, and project metadata.', ], ], @@ -2325,12 +2325,12 @@ 'name' => 'Confluence', 'aliases' => [ 'confluence', 'atlassian confluence' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Team wiki and documentation collaboration.', - '_opentrust_sub_country' => 'AU', - '_opentrust_sub_website' => 'https://atlassian.com/software/confluence', + '_ettic_otc_sub_purpose' => 'Team wiki and documentation collaboration.', + '_ettic_otc_sub_country' => 'AU', + '_ettic_otc_sub_website' => 'https://atlassian.com/software/confluence', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User account details, page contents, comments, attachments, and space metadata.', + '_ettic_otc_sub_data_processed' => 'User account details, page contents, comments, attachments, and space metadata.', ], ], @@ -2338,12 +2338,12 @@ 'name' => 'ClickUp', 'aliases' => [ 'clickup', 'click up' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Project management, task tracking, and team collaboration.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://clickup.com', + '_ettic_otc_sub_purpose' => 'Project management, task tracking, and team collaboration.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://clickup.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User account details, task contents, comments, attachments, and workspace metadata.', + '_ettic_otc_sub_data_processed' => 'User account details, task contents, comments, attachments, and workspace metadata.', ], ], @@ -2351,12 +2351,12 @@ 'name' => 'Monday.com', 'aliases' => [ 'monday', 'monday.com' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Work management and team collaboration platform.', - '_opentrust_sub_country' => 'IL', - '_opentrust_sub_website' => 'https://monday.com', + '_ettic_otc_sub_purpose' => 'Work management and team collaboration platform.', + '_ettic_otc_sub_country' => 'IL', + '_ettic_otc_sub_website' => 'https://monday.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User account details, board and item contents, comments, and attachments.', + '_ettic_otc_sub_data_processed' => 'User account details, board and item contents, comments, and attachments.', ], ], @@ -2364,12 +2364,12 @@ 'name' => 'Airtable', 'aliases' => [ 'airtable' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Cloud database and spreadsheet collaboration platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://airtable.com', + '_ettic_otc_sub_purpose' => 'Cloud database and spreadsheet collaboration platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://airtable.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User account details, base records, attachments, and workspace metadata.', + '_ettic_otc_sub_data_processed' => 'User account details, base records, attachments, and workspace metadata.', ], ], @@ -2377,12 +2377,12 @@ 'name' => 'Coda', 'aliases' => [ 'coda', 'coda.io' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Collaborative documents combining text, tables, and automations.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://coda.io', + '_ettic_otc_sub_purpose' => 'Collaborative documents combining text, tables, and automations.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://coda.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User account details, document contents, comments, and attachments.', + '_ettic_otc_sub_data_processed' => 'User account details, document contents, comments, and attachments.', ], ], @@ -2390,11 +2390,11 @@ 'name' => 'Miro', 'aliases' => [ 'miro', 'realtimeboard' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Online collaborative whiteboard and visual workspace.', - '_opentrust_sub_website' => 'https://miro.com', + '_ettic_otc_sub_purpose' => 'Online collaborative whiteboard and visual workspace.', + '_ettic_otc_sub_website' => 'https://miro.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User account details, board contents, comments, and attachments.', + '_ettic_otc_sub_data_processed' => 'User account details, board contents, comments, and attachments.', ], ], @@ -2402,12 +2402,12 @@ 'name' => 'Loom', 'aliases' => [ 'loom', 'useloom' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Asynchronous video messaging and screen recording.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://loom.com', + '_ettic_otc_sub_purpose' => 'Asynchronous video messaging and screen recording.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://loom.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Recorded video and audio, viewer identifiers, engagement events, and account details.', + '_ettic_otc_sub_data_processed' => 'Recorded video and audio, viewer identifiers, engagement events, and account details.', ], ], @@ -2415,12 +2415,12 @@ 'name' => 'Microsoft Teams', 'aliases' => [ 'teams', 'msteams', 'ms teams' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Team chat, meetings, and collaboration within Microsoft 365.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://microsoft.com/microsoft-teams', + '_ettic_otc_sub_purpose' => 'Team chat, meetings, and collaboration within Microsoft 365.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://microsoft.com/microsoft-teams', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User account details, chat messages, meeting recordings, files, and presence information.', + '_ettic_otc_sub_data_processed' => 'User account details, chat messages, meeting recordings, files, and presence information.', ], ], @@ -2430,12 +2430,12 @@ 'name' => 'Gusto', 'aliases' => [ 'gusto' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Payroll, benefits, and HR administration for US employers.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://gusto.com', + '_ettic_otc_sub_purpose' => 'Payroll, benefits, and HR administration for US employers.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://gusto.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Employee names, Social Security numbers, bank details, compensation, and tax information.', + '_ettic_otc_sub_data_processed' => 'Employee names, Social Security numbers, bank details, compensation, and tax information.', ], ], @@ -2443,12 +2443,12 @@ 'name' => 'Deel', 'aliases' => [ 'deel' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Global payroll, contractor management, and employer of record services.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://deel.com', + '_ettic_otc_sub_purpose' => 'Global payroll, contractor management, and employer of record services.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://deel.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Worker names, tax identifiers, bank details, contracts, and compensation records.', + '_ettic_otc_sub_data_processed' => 'Worker names, tax identifiers, bank details, contracts, and compensation records.', ], ], @@ -2456,12 +2456,12 @@ 'name' => 'Remote', 'aliases' => [ 'remote', 'remote.com' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Global employer of record, payroll, and contractor management.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://remote.com', + '_ettic_otc_sub_purpose' => 'Global employer of record, payroll, and contractor management.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://remote.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Worker names, tax identifiers, bank details, contracts, and compensation records.', + '_ettic_otc_sub_data_processed' => 'Worker names, tax identifiers, bank details, contracts, and compensation records.', ], ], @@ -2469,12 +2469,12 @@ 'name' => 'Rippling', 'aliases' => [ 'rippling' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Unified HR, payroll, IT, and finance platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://rippling.com', + '_ettic_otc_sub_purpose' => 'Unified HR, payroll, IT, and finance platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://rippling.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Employee personal details, tax identifiers, bank information, device data, and app access logs.', + '_ettic_otc_sub_data_processed' => 'Employee personal details, tax identifiers, bank information, device data, and app access logs.', ], ], @@ -2482,12 +2482,12 @@ 'name' => 'BambooHR', 'aliases' => [ 'bamboohr', 'bamboo hr' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Human resources information system for small and medium businesses.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://bamboohr.com', + '_ettic_otc_sub_purpose' => 'Human resources information system for small and medium businesses.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://bamboohr.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Employee personal details, employment history, compensation, and performance records.', + '_ettic_otc_sub_data_processed' => 'Employee personal details, employment history, compensation, and performance records.', ], ], @@ -2495,12 +2495,12 @@ 'name' => 'Personio', 'aliases' => [ 'personio' ], 'fields' => [ - '_opentrust_sub_purpose' => 'HR management, recruiting, and payroll for European businesses.', - '_opentrust_sub_country' => 'DE', - '_opentrust_sub_website' => 'https://personio.com', + '_ettic_otc_sub_purpose' => 'HR management, recruiting, and payroll for European businesses.', + '_ettic_otc_sub_country' => 'DE', + '_ettic_otc_sub_website' => 'https://personio.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Employee personal details, employment records, compensation, and applicant data.', + '_ettic_otc_sub_data_processed' => 'Employee personal details, employment records, compensation, and applicant data.', ], ], @@ -2510,12 +2510,12 @@ 'name' => 'DocuSign', 'aliases' => [ 'docusign', 'docu sign' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Electronic signature and agreement management.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://docusign.com', + '_ettic_otc_sub_purpose' => 'Electronic signature and agreement management.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://docusign.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Signer names, email addresses, IP addresses, signed documents, and audit trails.', + '_ettic_otc_sub_data_processed' => 'Signer names, email addresses, IP addresses, signed documents, and audit trails.', ], ], @@ -2523,12 +2523,12 @@ 'name' => 'Dropbox Sign', 'aliases' => [ 'dropbox sign', 'hellosign' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Electronic signature service, formerly HelloSign.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://dropbox.com/sign', + '_ettic_otc_sub_purpose' => 'Electronic signature service, formerly HelloSign.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://dropbox.com/sign', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Signer names, email addresses, IP addresses, signed documents, and audit trails.', + '_ettic_otc_sub_data_processed' => 'Signer names, email addresses, IP addresses, signed documents, and audit trails.', ], ], @@ -2536,12 +2536,12 @@ 'name' => 'PandaDoc', 'aliases' => [ 'pandadoc', 'panda doc' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Document automation, proposals, and electronic signature.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://pandadoc.com', + '_ettic_otc_sub_purpose' => 'Document automation, proposals, and electronic signature.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://pandadoc.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Signer names, email addresses, IP addresses, document contents, and audit trails.', + '_ettic_otc_sub_data_processed' => 'Signer names, email addresses, IP addresses, document contents, and audit trails.', ], ], @@ -2551,12 +2551,12 @@ 'name' => 'LaunchDarkly', 'aliases' => [ 'launchdarkly', 'launch darkly' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Feature flag management and progressive delivery platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://launchdarkly.com', + '_ettic_otc_sub_purpose' => 'Feature flag management and progressive delivery platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://launchdarkly.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'End user identifiers, targeting attributes, and feature evaluation events.', + '_ettic_otc_sub_data_processed' => 'End user identifiers, targeting attributes, and feature evaluation events.', ], ], @@ -2564,12 +2564,12 @@ 'name' => 'Statsig', 'aliases' => [ 'statsig' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Feature flags, experimentation, and product analytics.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://statsig.com', + '_ettic_otc_sub_purpose' => 'Feature flags, experimentation, and product analytics.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://statsig.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'End user identifiers, targeting attributes, and product event data.', + '_ettic_otc_sub_data_processed' => 'End user identifiers, targeting attributes, and product event data.', ], ], @@ -2577,12 +2577,12 @@ 'name' => 'GrowthBook', 'aliases' => [ 'growthbook', 'growth book' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Open-source feature flags and A/B testing platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://growthbook.io', + '_ettic_otc_sub_purpose' => 'Open-source feature flags and A/B testing platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://growthbook.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'End user identifiers, targeting attributes, and experiment event data.', + '_ettic_otc_sub_data_processed' => 'End user identifiers, targeting attributes, and experiment event data.', ], ], @@ -2590,12 +2590,12 @@ 'name' => 'Split', 'aliases' => [ 'split', 'split.io' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Feature delivery and experimentation platform.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://split.io', + '_ettic_otc_sub_purpose' => 'Feature delivery and experimentation platform.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://split.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'End user identifiers, targeting attributes, and feature evaluation events.', + '_ettic_otc_sub_data_processed' => 'End user identifiers, targeting attributes, and feature evaluation events.', ], ], @@ -2603,12 +2603,12 @@ 'name' => 'ConfigCat', 'aliases' => [ 'configcat', 'config cat' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Feature flag and configuration management service.', - '_opentrust_sub_country' => 'HU', - '_opentrust_sub_website' => 'https://configcat.com', + '_ettic_otc_sub_purpose' => 'Feature flag and configuration management service.', + '_ettic_otc_sub_country' => 'HU', + '_ettic_otc_sub_website' => 'https://configcat.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'End user identifiers, targeting attributes, and feature evaluation events.', + '_ettic_otc_sub_data_processed' => 'End user identifiers, targeting attributes, and feature evaluation events.', ], ], @@ -2618,12 +2618,12 @@ 'name' => 'Inngest', 'aliases' => [ 'inngest' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Durable workflow and background job orchestration for developers.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://inngest.com', + '_ettic_otc_sub_purpose' => 'Durable workflow and background job orchestration for developers.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://inngest.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Event payloads, function inputs and outputs, and execution logs.', + '_ettic_otc_sub_data_processed' => 'Event payloads, function inputs and outputs, and execution logs.', ], ], @@ -2631,12 +2631,12 @@ 'name' => 'Trigger.dev', 'aliases' => [ 'trigger', 'trigger.dev' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Background jobs and workflow orchestration platform for developers.', - '_opentrust_sub_country' => 'GB', - '_opentrust_sub_website' => 'https://trigger.dev', + '_ettic_otc_sub_purpose' => 'Background jobs and workflow orchestration platform for developers.', + '_ettic_otc_sub_country' => 'GB', + '_ettic_otc_sub_website' => 'https://trigger.dev', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Event payloads, job inputs and outputs, and execution logs.', + '_ettic_otc_sub_data_processed' => 'Event payloads, job inputs and outputs, and execution logs.', ], ], @@ -2644,12 +2644,12 @@ 'name' => 'Temporal Cloud', 'aliases' => [ 'temporal', 'temporal cloud', 'temporal.io' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed durable execution and workflow orchestration service.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://temporal.io/cloud', + '_ettic_otc_sub_purpose' => 'Managed durable execution and workflow orchestration service.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://temporal.io/cloud', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Workflow inputs and outputs, activity payloads, and execution history.', + '_ettic_otc_sub_data_processed' => 'Workflow inputs and outputs, activity payloads, and execution history.', ], ], @@ -2657,12 +2657,12 @@ 'name' => 'Hatchet', 'aliases' => [ 'hatchet' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Distributed task queue and background job orchestration.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://hatchet.run', + '_ettic_otc_sub_purpose' => 'Distributed task queue and background job orchestration.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://hatchet.run', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Task payloads, job inputs and outputs, and execution logs.', + '_ettic_otc_sub_data_processed' => 'Task payloads, job inputs and outputs, and execution logs.', ], ], @@ -2672,12 +2672,12 @@ 'name' => 'Zapier', 'aliases' => [ 'zapier' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Automation platform connecting SaaS apps with triggers and actions.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://zapier.com', + '_ettic_otc_sub_purpose' => 'Automation platform connecting SaaS apps with triggers and actions.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://zapier.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Data passed between connected applications, including any personal data in workflow payloads.', + '_ettic_otc_sub_data_processed' => 'Data passed between connected applications, including any personal data in workflow payloads.', ], ], @@ -2685,12 +2685,12 @@ 'name' => 'Make', 'aliases' => [ 'make', 'integromat' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Visual automation and integration platform, formerly Integromat.', - '_opentrust_sub_country' => 'CZ', - '_opentrust_sub_website' => 'https://make.com', + '_ettic_otc_sub_purpose' => 'Visual automation and integration platform, formerly Integromat.', + '_ettic_otc_sub_country' => 'CZ', + '_ettic_otc_sub_website' => 'https://make.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Data passed between connected applications, including any personal data in scenario payloads.', + '_ettic_otc_sub_data_processed' => 'Data passed between connected applications, including any personal data in scenario payloads.', ], ], @@ -2698,12 +2698,12 @@ 'name' => 'n8n Cloud', 'aliases' => [ 'n8n', 'n8n cloud' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed workflow automation platform based on n8n.', - '_opentrust_sub_country' => 'DE', - '_opentrust_sub_website' => 'https://n8n.io/cloud', + '_ettic_otc_sub_purpose' => 'Managed workflow automation platform based on n8n.', + '_ettic_otc_sub_country' => 'DE', + '_ettic_otc_sub_website' => 'https://n8n.io/cloud', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Data passed between connected applications, including any personal data in workflow payloads.', + '_ettic_otc_sub_data_processed' => 'Data passed between connected applications, including any personal data in workflow payloads.', ], ], @@ -2711,12 +2711,12 @@ 'name' => 'Pipedream', 'aliases' => [ 'pipedream' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Integration and workflow automation platform for developers.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://pipedream.com', + '_ettic_otc_sub_purpose' => 'Integration and workflow automation platform for developers.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://pipedream.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Event payloads, workflow inputs and outputs, and execution logs.', + '_ettic_otc_sub_data_processed' => 'Event payloads, workflow inputs and outputs, and execution logs.', ], ], @@ -2724,12 +2724,12 @@ 'name' => 'Retool', 'aliases' => [ 'retool' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Low-code platform for building internal tools and applications.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://retool.com', + '_ettic_otc_sub_purpose' => 'Low-code platform for building internal tools and applications.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://retool.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'User account details, query results from connected data sources, and audit logs.', + '_ettic_otc_sub_data_processed' => 'User account details, query results from connected data sources, and audit logs.', ], ], @@ -2739,12 +2739,12 @@ 'name' => 'Webflow', 'aliases' => [ 'webflow' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Visual website builder, CMS, and hosting.', - '_opentrust_sub_country' => 'US', - '_opentrust_sub_website' => 'https://webflow.com', + '_ettic_otc_sub_purpose' => 'Visual website builder, CMS, and hosting.', + '_ettic_otc_sub_country' => 'US', + '_ettic_otc_sub_website' => 'https://webflow.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Website content, form submissions, visitor analytics, and customer account details.', + '_ettic_otc_sub_data_processed' => 'Website content, form submissions, visitor analytics, and customer account details.', ], ], @@ -2752,12 +2752,12 @@ 'name' => 'Framer', 'aliases' => [ 'framer' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Website builder and design tool with hosting.', - '_opentrust_sub_country' => 'NL', - '_opentrust_sub_website' => 'https://framer.com', + '_ettic_otc_sub_purpose' => 'Website builder and design tool with hosting.', + '_ettic_otc_sub_country' => 'NL', + '_ettic_otc_sub_website' => 'https://framer.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Website content, form submissions, visitor analytics, and customer account details.', + '_ettic_otc_sub_data_processed' => 'Website content, form submissions, visitor analytics, and customer account details.', ], ], @@ -2765,12 +2765,12 @@ 'name' => 'Shopify', 'aliases' => [ 'shopify' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Ecommerce platform for online stores and retail point of sale.', - '_opentrust_sub_country' => 'CA', - '_opentrust_sub_website' => 'https://shopify.com', + '_ettic_otc_sub_purpose' => 'Ecommerce platform for online stores and retail point of sale.', + '_ettic_otc_sub_country' => 'CA', + '_ettic_otc_sub_website' => 'https://shopify.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Customer names, email addresses, shipping addresses, order history, and payment metadata.', + '_ettic_otc_sub_data_processed' => 'Customer names, email addresses, shipping addresses, order history, and payment metadata.', ], ], @@ -2778,12 +2778,12 @@ 'name' => 'Contentful', 'aliases' => [ 'contentful' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Headless content management system and content delivery APIs.', - '_opentrust_sub_country' => 'DE', - '_opentrust_sub_website' => 'https://contentful.com', + '_ettic_otc_sub_purpose' => 'Headless content management system and content delivery APIs.', + '_ettic_otc_sub_country' => 'DE', + '_ettic_otc_sub_website' => 'https://contentful.com', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Content entries, media assets, and editor account details.', + '_ettic_otc_sub_data_processed' => 'Content entries, media assets, and editor account details.', ], ], @@ -2791,12 +2791,12 @@ 'name' => 'Sanity', 'aliases' => [ 'sanity', 'sanity.io' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Headless content platform with structured content and real-time APIs.', - '_opentrust_sub_country' => 'NO', - '_opentrust_sub_website' => 'https://sanity.io', + '_ettic_otc_sub_purpose' => 'Headless content platform with structured content and real-time APIs.', + '_ettic_otc_sub_country' => 'NO', + '_ettic_otc_sub_website' => 'https://sanity.io', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Content documents, media assets, and editor account details.', + '_ettic_otc_sub_data_processed' => 'Content documents, media assets, and editor account details.', ], ], @@ -2804,12 +2804,12 @@ 'name' => 'Strapi Cloud', 'aliases' => [ 'strapi', 'strapi cloud' ], 'fields' => [ - '_opentrust_sub_purpose' => 'Managed hosting for the Strapi headless CMS.', - '_opentrust_sub_country' => 'FR', - '_opentrust_sub_website' => 'https://strapi.io/cloud', + '_ettic_otc_sub_purpose' => 'Managed hosting for the Strapi headless CMS.', + '_ettic_otc_sub_country' => 'FR', + '_ettic_otc_sub_website' => 'https://strapi.io/cloud', ], 'fields_review' => [ - '_opentrust_sub_data_processed' => 'Content entries, media assets, and editor account details.', + '_ettic_otc_sub_data_processed' => 'Content entries, media assets, and editor account details.', ], ], diff --git a/includes/providers/class-opentrust-chat-provider-anthropic.php b/includes/providers/class-ettic-otc-chat-provider-anthropic.php similarity index 97% rename from includes/providers/class-opentrust-chat-provider-anthropic.php rename to includes/providers/class-ettic-otc-chat-provider-anthropic.php index 322da1d..1a7dacd 100644 --- a/includes/providers/class-opentrust-chat-provider-anthropic.php +++ b/includes/providers/class-ettic-otc-chat-provider-anthropic.php @@ -13,7 +13,7 @@ exit; } -final class OpenTrust_Chat_Provider_Anthropic extends OpenTrust_Chat_Provider { +final class Ettic_OTC_Chat_Provider_Anthropic extends Ettic_OTC_Chat_Provider { private const MODELS_ENDPOINT = 'https://api.anthropic.com/v1/models'; private const MESSAGES_ENDPOINT = 'https://api.anthropic.com/v1/messages'; @@ -41,7 +41,7 @@ public function slug(): string { } public function label(): string { - return __('Anthropic', 'opentrust'); + return __('Anthropic', 'open-trust-center-by-ettic'); } public function allowed_hosts(): array { @@ -60,7 +60,7 @@ protected function auth_headers(string $key): array { } protected function no_models_message(): string { - return __('No models available — your account may not be authorized.', 'opentrust'); + return __('No models available — your account may not be authorized.', 'open-trust-center-by-ettic'); } public function curate_models(mixed $raw): array { @@ -124,7 +124,7 @@ protected function initialize_turn_loop(array $args, callable $on_chunk): ?array $tools = is_array($args['tools'] ?? null) ? $args['tools'] : []; if ($api_key === '' || $model === '' || empty($messages)) { - $on_chunk(['type' => 'error', 'data' => ['message' => __('Anthropic adapter missing required args.', 'opentrust')]]); + $on_chunk(['type' => 'error', 'data' => ['message' => __('Anthropic adapter missing required args.', 'open-trust-center-by-ettic')]]); return null; } @@ -216,7 +216,7 @@ function (string $line) use (&$turn_state): void { if (empty($response['ok'])) { $on_chunk([ 'type' => 'error', - 'data' => ['message' => $response['error'] ?? __('Anthropic request failed.', 'opentrust')], + 'data' => ['message' => $response['error'] ?? __('Anthropic request failed.', 'open-trust-center-by-ettic')], ]); return null; } @@ -411,8 +411,8 @@ private function handle_anthropic_sse_line(string $line, array &$state): void { $state['tool_intent_emitted'] = true; $tool_name = (string) ($block['name'] ?? ''); $intent_label = $tool_name === 'search_documents' - ? __('Searching documents…', 'opentrust') - : __('Reading documents…', 'opentrust'); + ? __('Searching documents…', 'open-trust-center-by-ettic') + : __('Reading documents…', 'open-trust-center-by-ettic'); ($state['on_chunk'])([ 'type' => 'tool_intent', 'data' => [ @@ -482,14 +482,14 @@ private function handle_anthropic_sse_line(string $line, array &$state): void { } /** - * Transform a citation delta into a normalized OpenTrust citation event. + * Transform a citation delta into a normalized Ettic_OTC citation event. * * Anthropic emits `search_result_location` citations for content returned * by tools that produced search_result blocks. The canonical URL lives on * the citation object directly as `source` — no corpus lookup needed for * the URL. We do still reverse-look-up the doc id from the URL so the * front-end de-dup-by-id keeps subprocessors that share an anchor URL - * (`/trust-center/#ot-subprocessors`) as separate citations. + * (`/trust-center/#ettic-otc-subprocessors`) as separate citations. * * Error blocks (source = `about:none`) drop silently. */ diff --git a/includes/providers/class-opentrust-chat-provider-openai.php b/includes/providers/class-ettic-otc-chat-provider-openai.php similarity index 97% rename from includes/providers/class-opentrust-chat-provider-openai.php rename to includes/providers/class-ettic-otc-chat-provider-openai.php index d8dcf57..613ee35 100644 --- a/includes/providers/class-opentrust-chat-provider-openai.php +++ b/includes/providers/class-ettic-otc-chat-provider-openai.php @@ -13,7 +13,7 @@ exit; } -class OpenTrust_Chat_Provider_OpenAI extends OpenTrust_Chat_Provider { +class Ettic_OTC_Chat_Provider_OpenAI extends Ettic_OTC_Chat_Provider { protected const API_BASE = 'https://api.openai.com'; protected const MODELS_ENDPOINT = 'https://api.openai.com/v1/models'; @@ -63,7 +63,7 @@ public function slug(): string { } public function label(): string { - return __('OpenAI', 'opentrust'); + return __('OpenAI', 'open-trust-center-by-ettic'); } public function allowed_hosts(): array { @@ -163,12 +163,12 @@ protected function initialize_turn_loop(array $args, callable $on_chunk): ?array $tools = is_array($args['tools'] ?? null) ? $args['tools'] : []; if ($api_key === '' || $model === '' || empty($messages)) { - $on_chunk(['type' => 'error', 'data' => ['message' => __('OpenAI adapter missing required args.', 'opentrust')]]); + $on_chunk(['type' => 'error', 'data' => ['message' => __('OpenAI adapter missing required args.', 'open-trust-center-by-ettic')]]); return null; } // The system prompt arrives pre-built with the corpus index already - // appended (see OpenTrust_Chat::build_system_prompt). Tack on a tail + // appended (see Ettic_OTC_Chat::build_system_prompt). Tack on a tail // that sets up the inline-citation contract — Anthropic uses native // search_result_location citations and doesn't need this block. $system_full = $this->append_citation_instructions($system); @@ -250,7 +250,7 @@ function (string $line) use (&$turn_state): void { if (empty($response['ok'])) { $on_chunk([ 'type' => 'error', - 'data' => ['message' => $response['error'] ?? __('OpenAI request failed.', 'opentrust')], + 'data' => ['message' => $response['error'] ?? __('OpenAI request failed.', 'open-trust-center-by-ettic')], ]); return null; } @@ -358,7 +358,7 @@ protected function extra_stream_headers(): array { /** * Append the OpenAI/OpenRouter-specific citation contract to the base * system prompt. The base prompt already contains the corpus index and - * the role rules — see OpenTrust_Chat::build_system_prompt(). All we + * the role rules — see Ettic_OTC_Chat::build_system_prompt(). All we * add is how this provider should mark citations inline so the server * can convert them into structured citation events. * @@ -508,7 +508,7 @@ private function handle_openai_sse_line(string $line, array &$state): void { /** * Scan the full answer for [[cite:doc-id]] tags and emit citation events * with URLs resolved from the corpus. Unknown IDs are ignored here; - * URL whitelist enforcement happens upstream in OpenTrust_Chat. + * URL whitelist enforcement happens upstream in Ettic_OTC_Chat. */ private function extract_and_emit_citations(string $answer, array $documents, callable $on_chunk): void { if ($answer === '' || empty($documents)) { diff --git a/includes/providers/class-opentrust-chat-provider-openrouter.php b/includes/providers/class-ettic-otc-chat-provider-openrouter.php similarity index 92% rename from includes/providers/class-opentrust-chat-provider-openrouter.php rename to includes/providers/class-ettic-otc-chat-provider-openrouter.php index 472afdd..577a121 100644 --- a/includes/providers/class-opentrust-chat-provider-openrouter.php +++ b/includes/providers/class-ettic-otc-chat-provider-openrouter.php @@ -16,7 +16,7 @@ exit; } -final class OpenTrust_Chat_Provider_OpenRouter extends OpenTrust_Chat_Provider_OpenAI { +final class Ettic_OTC_Chat_Provider_OpenRouter extends Ettic_OTC_Chat_Provider_OpenAI { protected const API_BASE = 'https://openrouter.ai'; protected const MODELS_ENDPOINT = 'https://openrouter.ai/api/v1/models'; @@ -35,7 +35,7 @@ public function slug(): string { } public function label(): string { - return __('OpenRouter', 'opentrust'); + return __('OpenRouter', 'open-trust-center-by-ettic'); } public function allowed_hosts(): array { @@ -106,9 +106,9 @@ protected function is_recommended(string $id): bool { protected function extra_stream_headers(): array { return [ 'HTTP-Referer' => home_url('/'), - 'X-Title' => (string) (OpenTrust::get_settings()['company_name'] ?? 'OpenTrust'), + 'X-Title' => (string) (Ettic_OTC::get_settings()['company_name'] ?? 'Open Trust Center by Ettic'), ]; } - // stream_chat() is inherited from OpenTrust_Chat_Provider_OpenAI. + // stream_chat() is inherited from Ettic_OTC_Chat_Provider_OpenAI. } diff --git a/includes/providers/class-opentrust-chat-provider.php b/includes/providers/class-ettic-otc-chat-provider.php similarity index 91% rename from includes/providers/class-opentrust-chat-provider.php rename to includes/providers/class-ettic-otc-chat-provider.php index 4d4b036..47a499b 100644 --- a/includes/providers/class-opentrust-chat-provider.php +++ b/includes/providers/class-ettic-otc-chat-provider.php @@ -3,9 +3,9 @@ * Abstract base class for chat providers. * * Concrete implementations: - * - OpenTrust_Chat_Provider_Anthropic - * - OpenTrust_Chat_Provider_OpenAI - * - OpenTrust_Chat_Provider_OpenRouter + * - Ettic_OTC_Chat_Provider_Anthropic + * - Ettic_OTC_Chat_Provider_OpenAI + * - Ettic_OTC_Chat_Provider_OpenRouter * * The base class provides the factory, shared HTTP helpers, and a * host allowlist for SSRF prevention. Subclasses implement the @@ -20,16 +20,16 @@ exit; } -abstract class OpenTrust_Chat_Provider { +abstract class Ettic_OTC_Chat_Provider { /** * Factory: return a provider instance for a settings slug. */ public static function for(string $provider_slug): ?self { return match ($provider_slug) { - 'anthropic' => new OpenTrust_Chat_Provider_Anthropic(), - 'openai' => new OpenTrust_Chat_Provider_OpenAI(), - 'openrouter' => new OpenTrust_Chat_Provider_OpenRouter(), + 'anthropic' => new Ettic_OTC_Chat_Provider_Anthropic(), + 'openai' => new Ettic_OTC_Chat_Provider_OpenAI(), + 'openrouter' => new Ettic_OTC_Chat_Provider_OpenRouter(), default => null, }; } @@ -43,19 +43,19 @@ public static function available(): array { return [ [ 'slug' => 'anthropic', - 'label' => __('Anthropic', 'opentrust'), + 'label' => __('Anthropic', 'open-trust-center-by-ettic'), 'key_url' => 'https://console.anthropic.com/settings/keys', 'recommended' => true, ], [ 'slug' => 'openai', - 'label' => __('OpenAI', 'opentrust'), + 'label' => __('OpenAI', 'open-trust-center-by-ettic'), 'key_url' => 'https://platform.openai.com/api-keys', 'recommended' => false, ], [ 'slug' => 'openrouter', - 'label' => __('OpenRouter', 'opentrust'), + 'label' => __('OpenRouter', 'open-trust-center-by-ettic'), 'key_url' => 'https://openrouter.ai/settings/keys', 'recommended' => false, ], @@ -101,7 +101,7 @@ abstract protected function models_endpoint(): string; * Subclasses override when they want a more specific phrasing. */ protected function no_models_message(): string { - return __('No chat models available for this key.', 'opentrust'); + return __('No chat models available for this key.', 'open-trust-center-by-ettic'); } /** @@ -114,13 +114,13 @@ protected function no_models_message(): string { public function validate_and_list_models(string $key): array { $key = trim($key); if ($key === '') { - return ['ok' => false, 'error' => __('API key is empty.', 'opentrust')]; + return ['ok' => false, 'error' => __('API key is empty.', 'open-trust-center-by-ettic')]; } $response = $this->http_get($this->models_endpoint(), $this->auth_headers($key), 15); if (!$response['ok']) { - return ['ok' => false, 'error' => $response['error'] ?? __('Request failed.', 'opentrust')]; + return ['ok' => false, 'error' => $response['error'] ?? __('Request failed.', 'open-trust-center-by-ettic')]; } $models = $this->curate_models($response['body']); @@ -160,7 +160,7 @@ final public function stream_chat(array $args, callable $on_chunk, callable $too // and answer from training data. After any tool call we relax to auto. $has_called_tool = false; - for ($turn = 1; $turn <= OpenTrust_Chat::MAX_TOOL_TURNS; $turn++) { + for ($turn = 1; $turn <= Ettic_OTC_Chat::MAX_TOOL_TURNS; $turn++) { $stream_state = $this->stream_one_turn($turn_loop_state, $has_called_tool, $on_chunk); if ($stream_state === null) { return; // stream_one_turn already emitted an error event @@ -265,7 +265,7 @@ final protected function emit_cap_refusal(callable $on_chunk): void { $on_chunk([ 'type' => 'token', 'data' => [ - 'text' => __("I couldn't find a confident answer in the published trust center documents. Please contact the team for help.", 'opentrust'), + 'text' => __("I couldn't find a confident answer in the published trust center documents. Please contact the team for help.", 'open-trust-center-by-ettic'), ], ]); } @@ -339,19 +339,19 @@ protected static function summarize_tool_call(string $name, array $args, array $ if ($title !== '') { return $is_settled /* translators: %s is the document title. */ - ? sprintf(__('Read "%s"', 'opentrust'), $title) + ? sprintf(__('Read "%s"', 'open-trust-center-by-ettic'), $title) /* translators: %s is the document title. */ - : sprintf(__('Reading "%s"', 'opentrust'), $title); + : sprintf(__('Reading "%s"', 'open-trust-center-by-ettic'), $title); } } } return $is_settled /* translators: %s is the document id. */ - ? sprintf(__('Read %s', 'opentrust'), $id) + ? sprintf(__('Read %s', 'open-trust-center-by-ettic'), $id) /* translators: %s is the document id. */ - : sprintf(__('Reading %s', 'opentrust'), $id); + : sprintf(__('Reading %s', 'open-trust-center-by-ettic'), $id); } - return $is_settled ? __('Read a document', 'opentrust') : __('Reading a document', 'opentrust'); + return $is_settled ? __('Read a document', 'open-trust-center-by-ettic') : __('Reading a document', 'open-trust-center-by-ettic'); } if ($name === 'search_documents') { @@ -364,11 +364,11 @@ protected static function summarize_tool_call(string $name, array $args, array $ } return $is_settled /* translators: %s is the search query. */ - ? sprintf(__('Searched for "%s"', 'opentrust'), $q) + ? sprintf(__('Searched for "%s"', 'open-trust-center-by-ettic'), $q) /* translators: %s is the search query. */ - : sprintf(__('Searching for "%s"', 'opentrust'), $q); + : sprintf(__('Searching for "%s"', 'open-trust-center-by-ettic'), $q); } - return $is_settled ? __('Searched documents', 'opentrust') : __('Searching documents', 'opentrust'); + return $is_settled ? __('Searched documents', 'open-trust-center-by-ettic') : __('Searching documents', 'open-trust-center-by-ettic'); } return $name; @@ -396,22 +396,22 @@ protected static function summarize_turn_batch(array $names, int $count, string if ($all_get) { return $is_settled /* translators: %d is the number of documents that were read in parallel. */ - ? sprintf(__('Read %d documents', 'opentrust'), $count) + ? sprintf(__('Read %d documents', 'open-trust-center-by-ettic'), $count) /* translators: %d is the number of documents being read in parallel. */ - : sprintf(__('Reading %d documents', 'opentrust'), $count); + : sprintf(__('Reading %d documents', 'open-trust-center-by-ettic'), $count); } if ($all_search) { return $is_settled /* translators: %d is the number of search queries that were fired in parallel. */ - ? sprintf(__('Ran %d searches', 'opentrust'), $count) + ? sprintf(__('Ran %d searches', 'open-trust-center-by-ettic'), $count) /* translators: %d is the number of search queries fired in parallel. */ - : sprintf(__('Running %d searches', 'opentrust'), $count); + : sprintf(__('Running %d searches', 'open-trust-center-by-ettic'), $count); } return $is_settled /* translators: %d is the number of parallel retrieval calls (mixed types). */ - ? sprintf(__('Ran %d retrievals', 'opentrust'), $count) + ? sprintf(__('Ran %d retrievals', 'open-trust-center-by-ettic'), $count) /* translators: %d is the number of parallel retrieval calls (mixed types). */ - : sprintf(__('Running %d retrievals', 'opentrust'), $count); + : sprintf(__('Running %d retrievals', 'open-trust-center-by-ettic'), $count); } // ────────────────────────────────────────────── @@ -440,7 +440,7 @@ final protected function host_allowed(string $url): bool { */ final protected function http_get(string $url, array $headers = [], int $timeout = 15): array { if (!$this->host_allowed($url)) { - return ['ok' => false, 'error' => __('Refused outbound request to disallowed host.', 'opentrust')]; + return ['ok' => false, 'error' => __('Refused outbound request to disallowed host.', 'open-trust-center-by-ettic')]; } $response = wp_safe_remote_get($url, [ @@ -459,7 +459,7 @@ final protected function http_get(string $url, array $headers = [], int $timeout */ final protected function http_post(string $url, array $payload, array $headers = [], int $timeout = 60): array { if (!$this->host_allowed($url)) { - return ['ok' => false, 'error' => __('Refused outbound request to disallowed host.', 'opentrust')]; + return ['ok' => false, 'error' => __('Refused outbound request to disallowed host.', 'open-trust-center-by-ettic')]; } $headers = array_merge(['Content-Type' => 'application/json'], $headers); @@ -510,12 +510,12 @@ final protected function normalize_response(mixed $response): array { */ final protected function stream_post(string $url, array $payload, array $headers, callable $on_line, int $timeout = 90): array { if (!$this->host_allowed($url)) { - return ['ok' => false, 'error' => __('Refused outbound request to disallowed host.', 'opentrust')]; + return ['ok' => false, 'error' => __('Refused outbound request to disallowed host.', 'open-trust-center-by-ettic')]; } // SSE chunk callbacks require cURL transport. Streams/fsockopen buffer the full response. if (!function_exists('curl_init')) { - return ['ok' => false, 'error' => __('AI streaming requires the PHP cURL extension.', 'opentrust')]; + return ['ok' => false, 'error' => __('AI streaming requires the PHP cURL extension.', 'open-trust-center-by-ettic')]; } $body = wp_json_encode($payload); @@ -617,7 +617,7 @@ final protected function stream_post(string $url, array $payload, array $headers // If WP picked Streams/fsockopen, http_api_curl never fired and our SSE callbacks // never installed — the response body was buffered, not streamed. Fail loudly. if (!$state->curl_hook_ran) { - return ['ok' => false, 'error' => __('AI streaming requires the WordPress cURL transport.', 'opentrust')]; + return ['ok' => false, 'error' => __('AI streaming requires the WordPress cURL transport.', 'open-trust-center-by-ettic')]; } // Copy parsed response headers (Requests' stream_headers still ran since we @@ -654,7 +654,7 @@ final protected function stream_post(string $url, array $payload, array $headers $state->error_body = $state->buffer; } $detail = $this->describe_streaming_error($state->error_body, $state->response_headers, $http_code); - OpenTrust::debug_log($detail); + Ettic_OTC::debug_log($detail); return ['ok' => false, 'code' => $http_code, 'error' => $detail]; } @@ -670,7 +670,7 @@ final protected function stream_post(string $url, array $payload, array $headers */ final protected function describe_streaming_error(string $body, array $headers, int $code): string { /* translators: %d: HTTP status code returned by the provider */ - $parts = [sprintf(__('Provider returned HTTP %d', 'opentrust'), $code)]; + $parts = [sprintf(__('Provider returned HTTP %d', 'open-trust-center-by-ettic'), $code)]; $body = trim($body); if ($body !== '') { @@ -725,6 +725,6 @@ protected function extract_error_message(mixed $body, int $code): string { } } /* translators: %d: HTTP status code returned by the provider */ - return sprintf(__('Provider returned HTTP %d', 'opentrust'), $code); + return sprintf(__('Provider returned HTTP %d', 'open-trust-center-by-ettic'), $code); } } diff --git a/languages/open-trust-center-by-ettic-nl_NL.mo b/languages/open-trust-center-by-ettic-nl_NL.mo new file mode 100644 index 0000000..f2e8875 Binary files /dev/null and b/languages/open-trust-center-by-ettic-nl_NL.mo differ diff --git a/languages/open-trust-center-by-ettic-nl_NL.po b/languages/open-trust-center-by-ettic-nl_NL.po new file mode 100644 index 0000000..0e6cd1d --- /dev/null +++ b/languages/open-trust-center-by-ettic-nl_NL.po @@ -0,0 +1,2954 @@ +# Copyright (C) 2026 Ettic. Licensed under GPL-2.0-or-later. +msgid "" +msgstr "" +"Project-Id-Version: Open Trust Center by Ettic 1.2.0\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/open-trust-center-" +"by-ettic\n" +"POT-Creation-Date: 2026-05-21T20:40:20+00:00\n" +"PO-Revision-Date: 2026-04-14 21:55+0000\n" +"Last-Translator: Ettic bundled starter \n" +"Language-Team: Dutch (Netherlands)\n" +"Language: nl_NL\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: WP-CLI 2.12.0\n" +"X-Domain: open-trust-center-by-ettic\n" + +#. Plugin Name of the plugin +#: open-trust-center-by-ettic.php includes/class-ettic-otc-admin.php:56 +msgid "Open Trust Center by Ettic" +msgstr "" + +#. Plugin URI of the plugin +#: open-trust-center-by-ettic.php +msgid "https://plugins.ettic.nl/open-trust-center-by-ettic" +msgstr "" + +#. Description of the plugin +#: open-trust-center-by-ettic.php +#, fuzzy +msgid "" +"A self-hosted, open-source trust center for publishing security policies, " +"subprocessors, certifications, and data practices." +msgstr "" +"Een zelf-gehost, open-source vertrouwenscentrum voor het publiceren van " +"beveiligingsbeleid, subverwerkers, certificeringen en gegevenspraktijken." + +#. Author of the plugin +#: open-trust-center-by-ettic.php +msgid "Ettic" +msgstr "" + +#. Author URI of the plugin +#: open-trust-center-by-ettic.php +msgid "https://plugins.ettic.nl" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:78 +msgid "Heads up: citation fidelity is not guaranteed on your active provider." +msgstr "" + +#. translators: %s: provider label, e.g. OpenAI +#: includes/class-ettic-otc-admin-ai.php:83 +#, php-format +msgid "" +"You are currently using %s. Only Anthropic uses a " +"structural Citations API — every other provider relies on prompted citation " +"tags the model can ignore or fabricate. For a published trust center, switch " +"to Anthropic below." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:94 +msgid "" +"Open Trust Center uses Anthropic Claude with the native Citations " +"API to answer visitor questions about your trust center. Every " +"claim the assistant makes is tied to an exact quote from one of your " +"published documents — so no policy text is invented and nothing is " +"paraphrased into something you did not actually publish." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:101 +msgid "Why Anthropic, and not OpenAI or any other provider?" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:106 +msgid "" +"A trust center is a compliance surface. If the assistant " +"invents a security commitment you never made, that is not a UX papercut — it " +"is a misrepresentation of your security posture, and your customers and " +"auditors will hold you to it." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:114 +msgid "" +"Anthropic is the only major provider that exposes a " +"structural Citations API. Documents are sent as typed blocks and the model " +"emits citations as first-class events containing the exact source document " +"and the exact quoted text. The model literally cannot return a citation for " +"text that is not in your source documents." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:120 +msgid "" +"Every other provider (including OpenAI and any model accessed via " +"OpenRouter) relies on prompted citation tags that we parse out of the answer " +"after the fact. That works most of the time, but the model can ignore the " +"instructions, make up document IDs, or attach a citation to a sentence it " +"actually hallucinated. We support these providers as an escape hatch for " +"organizations that genuinely cannot use Anthropic for procurement or data-" +"residency reasons — but we very, very strongly recommend you do not run a " +"public trust center on them." +msgstr "" + +#. translators: %d is the number of policies missing AI summaries. +#: includes/class-ettic-otc-admin-ai.php:159 +#, php-format +msgid "%d policy is missing an AI summary." +msgid_plural "%d policies are missing AI summaries." +msgstr[0] "" +msgstr[1] "" + +#: includes/class-ettic-otc-admin-ai.php:170 +msgid "Generate them now so the assistant can route questions accurately." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:175 +msgid "Generate now" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:199 +msgid "Choose a provider and add your key" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:211 +msgid "Step 1 — Connect Anthropic" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:217 +msgid "Advanced: use a different provider (not recommended)" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:220 +msgid "These providers cannot guarantee citation fidelity." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:222 +msgid "" +"OpenAI and OpenRouter rely on prompted [[cite:document-id]] tags that we " +"parse out of the answer after generation. The model can ignore the " +"instruction, invent document IDs, or attach a citation to a sentence it " +"actually hallucinated. We cannot detect when this happens." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:225 +msgid "Do not use these providers for a published trust center" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:226 +msgid "" +"unless your organization genuinely cannot use Anthropic for procurement, " +"contractual, or data-residency reasons. Inaccurate claims about your " +"security posture are a real compliance risk." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:265 +msgid "Required for citation fidelity" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:271 +msgid "" +"Uses Claude with the native Citations API. Every quote the assistant " +"attributes to one of your documents is structurally guaranteed to come from " +"that document." +msgstr "" + +#. translators: %s: provider name (e.g. Anthropic) +#: includes/class-ettic-otc-admin-ai.php:279 +#, php-format +msgid "Get a %s API key" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:292 +msgid "Remove the saved key for this provider?" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:293 +msgid "Replace key" +msgstr "" + +#. translators: %s: provider name (e.g. Anthropic) +#: includes/class-ettic-otc-admin-ai.php:303 +#, php-format +msgid "Paste your %s API key…" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:307 +msgid "Validate & save" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:346 +msgid "Step 2 — Pick a model and tune defaults" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:359 +msgid "Active model" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:363 +msgid "No cached models found. Use Refresh to re-fetch the model list." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:372 +msgid "(unavailable)" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:376 +msgid "Recommended" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:387 +msgid "Model unavailable" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:392 +msgid "Refresh models" +msgstr "" + +#. translators: %s: human-readable time difference (e.g. "5 minutes") +#: includes/class-ettic-otc-admin-ai.php:402 +#, php-format +msgid "Model list cached %s ago." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:410 +msgid "Daily token budget" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:413 +msgid "" +"Hard cap per site per day. Default 500,000 tokens (~$12/day at Sonnet 4.5 " +"rates)." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:417 +msgid "Monthly token budget" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:420 +msgid "Hard cap per site per month. Default 10,000,000 tokens." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:424 +msgid "Rate limit — per IP" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:426 +msgid "messages per minute" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:430 +msgid "Rate limit — per session" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:432 +msgid "messages per hour" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:436 +msgid "Max message length" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:438 +msgid "characters" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:443 +msgid "Refuse-to-answer contact URL" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:446 +msgid "" +"When the AI cannot confidently answer a question, it links here. Leave blank " +"to use the trust center home." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:451 +msgid "Visitor display" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:455 +msgid "Show the active model name under the chat input" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:461 +msgid "Analytics logging" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:465 +msgid "" +"Log anonymized visitor questions for admin review (90-day auto-purge, no PII)" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:471 +msgid "Improve answer quality" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:475 +msgid "Generate AI summaries of each policy" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:478 +msgid "" +"When on, the AI generates a 2–3 sentence summary of each published policy " +"and stores it for routing decisions. Improves answers on questions like " +"\"What's your data deletion policy?\" that don't match a title literally. " +"Cost is roughly $0.05–$0.10 per 50 policies, lifetime — pennies per edit " +"afterward. Uses your configured AI key." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:487 +msgid "Oversized policies" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:491 +msgid "" +"The following policies are large enough that the AI will receive only a " +"truncated version when retrieving them. Consider splitting them into shorter " +"documents:" +msgstr "" + +#. translators: 1: policy title, 2: token count. +#: includes/class-ettic-otc-admin-ai.php:498 +#, php-format +msgid "%1$s (~%2$s tokens)" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:512 +msgid "Advanced — Turnstile anti-abuse" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:514 +msgid "" +"Cloudflare Turnstile is optional but recommended for public sites. It " +"challenges suspicious visitors on the first message of each session. You " +"need a free Cloudflare account to get site/secret keys." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:518 +msgid "Enable Turnstile for chat" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:522 +msgid "Require Turnstile verification on first chat message" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:527 +msgid "Turnstile Site Key" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:530 +msgid "Public site key from your Cloudflare Turnstile widget." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:534 +msgid "Turnstile Secret Key" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:537 +msgid "Enter secret key…" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:539 +msgid "Key saved" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:541 +msgid "" +"Secret key from Cloudflare Turnstile. Stored server-side — never exposed to " +"the frontend." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:546 +msgid "Save AI settings" +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:657 +#: includes/class-ettic-otc-admin-ai.php:714 +#: includes/class-ettic-otc-admin-ai.php:751 +#: includes/class-ettic-otc-admin-ai.php:796 +#: includes/class-ettic-otc-admin-questions.php:250 +#: includes/class-ettic-otc-admin-questions.php:291 +#: includes/class-ettic-otc-admin-questions.php:308 +#: includes/class-ettic-otc-admin-tools.php:565 +msgid "You do not have permission to perform this action." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:666 +#: includes/class-ettic-otc-admin-ai.php:722 +#: includes/class-ettic-otc-admin-ai.php:758 +msgid "Unknown provider." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:670 +msgid "API key cannot be empty." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:677 +msgid "Validation failed." +msgstr "" + +#. translators: 1: provider label, 2: provider error message +#: includes/class-ettic-otc-admin-ai.php:679 +#, php-format +msgid "%1$s rejected the key: %2$s" +msgstr "" + +#. translators: 1: provider label, 2: number of models +#: includes/class-ettic-otc-admin-ai.php:707 +#, php-format +msgid "%1$s key validated. Found %2$d model(s)." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:745 +msgid "Key removed." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:764 +msgid "No key on file for this provider." +msgstr "" + +#: includes/class-ettic-otc-admin-ai.php:770 +msgid "Refresh failed." +msgstr "" + +#. translators: %s: error message from the provider +#: includes/class-ettic-otc-admin-ai.php:772 +#, php-format +msgid "Refresh failed: %s" +msgstr "" + +#. translators: %d: number of models +#: includes/class-ettic-otc-admin-ai.php:782 +#, php-format +msgid "Model list refreshed. Found %d model(s)." +msgstr "" + +#. translators: %d is the number of policies enqueued for summary generation. +#: includes/class-ettic-otc-admin-ai.php:812 +#, php-format +msgid "" +"Queued %d policy for AI summary generation. Summaries will appear over the " +"next minute." +msgid_plural "" +"Queued %d policies for AI summary generation. Summaries will appear over the " +"next few minutes." +msgstr[0] "" +msgstr[1] "" + +#: includes/class-ettic-otc-admin-ai.php:820 +msgid "All policies already have up-to-date AI summaries." +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:89 +msgid "AI Questions" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:92 +msgid "" +"Questions visitors have asked your trust center chat. Identifiers are hashed " +"and rows auto-purge after 90 days." +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:98 +msgid "Logging is ON" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:100 +msgid "Logging is OFF" +msgstr "" + +#. translators: %d: number of questions +#: includes/class-ettic-otc-admin-questions.php:106 +#, php-format +msgid "%d question logged in the last 90 days" +msgid_plural "%d questions logged in the last 90 days" +msgstr[0] "" +msgstr[1] "" + +#: includes/class-ettic-otc-admin-questions.php:110 +msgid "Toggle visitor question logging?" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:111 +msgid "Disable logging" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:111 +msgid "Enable logging" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:119 +#, fuzzy +msgid "Search" +msgstr "Zoeken" + +#: includes/class-ettic-otc-admin-questions.php:120 +msgid "Search questions…" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:123 +#: includes/class-ettic-otc-admin-questions.php:150 +msgid "Model" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:125 +msgid "Any" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:132 +msgid "From" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:136 +msgid "To" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:139 +msgid "Filter" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:140 +msgid "Reset" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:141 +msgid "Download CSV" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:148 +#: includes/class-ettic-otc-version.php:161 +msgid "Date" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:149 +msgid "Question" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:151 +msgid "Cites" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:152 +msgid "Tokens" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:153 +msgid "Latency" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:158 +msgid "No questions logged yet." +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:167 +msgid "REFUSED" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:216 +msgid "Danger zone" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:217 +msgid "Permanently delete all logged questions? This cannot be undone." +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:218 +msgid "Clear entire question log" +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:299 +msgid "Question log cleared." +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:318 +msgid "Logging enabled." +msgstr "" + +#: includes/class-ettic-otc-admin-questions.php:318 +msgid "Logging disabled." +msgstr "" + +#: includes/class-ettic-otc-admin-review.php:81 +msgid "review on WordPress.org" +msgstr "" + +#. translators: %s: link to the WordPress.org reviews page +#: includes/class-ettic-otc-admin-review.php:89 +#, php-format +msgid "" +"Open Trust Center by Ettic is built and maintained in the open. If it is " +"helping your team, a %s keeps the project moving." +msgstr "" + +#: includes/class-ettic-otc-admin-review.php:115 +msgid "Your trust center is up and running." +msgstr "" + +#: includes/class-ettic-otc-admin-review.php:116 +msgid "" +"Open Trust Center by Ettic is fully open-source with no paid tier — reviews " +"on WordPress.org are how the project gets seen. If it has earned a kind " +"word, we would be grateful." +msgstr "" + +#: includes/class-ettic-otc-admin-review.php:120 +msgid "Leave a review" +msgstr "" + +#: includes/class-ettic-otc-admin-review.php:123 +msgid "Already did, thanks" +msgstr "" + +#: includes/class-ettic-otc-admin-review.php:126 +msgid "Not now" +msgstr "" + +#: includes/class-ettic-otc-admin-review.php:168 +msgid "You do not have permission to dismiss this notice." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:55 +msgid "General Settings" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:60 +msgid "Endpoint Slug" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:61 +msgid "" +"The URL path for your trust center (e.g., \"trust-center\" = yoursite.com/" +"trust-center/)." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:64 +msgid "Page Title" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:66 +msgid "Company Name" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:68 +msgid "Tagline" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:69 +msgid "" +"A short description displayed below the company name in the hero section." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:75 +msgid "Branding" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:80 +msgid "Logo" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:81 +msgid "AI Avatar" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:83 +msgid "Accent Color" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:84 +msgid "" +"Used for buttons, links, and highlights. Choose a color that matches your " +"brand." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:87 +msgid "Credit Link" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:92 +msgid "Visible Sections" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:93 +msgid "Choose which sections to display on the trust center." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:97 +msgid "Sections" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:103 +#: templates/partials/contact.php:111 +msgid "Get in touch" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:104 +msgid "" +"Publish a dark-accent \"Get in touch\" block on the trust center. Every " +"field is optional — the block only appears if at least one is filled in." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:108 +msgid "Company Description" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:109 +msgid "" +"Two or three sentences describing what the company does. Rendered under the " +"\"Get in touch\" section title." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:112 +msgid "DPO Name" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:113 +msgid "" +"Data Protection Officer name. Required under GDPR for many organisations." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:116 +msgid "DPO Email" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:117 +msgid "Dedicated DPO mailbox. Rendered as a mailto link." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:120 +msgid "Security Contact Email" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:121 +msgid "" +"For vulnerability reports and security questions. Often separate from the " +"DPO." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:124 +msgid "Contact Form URL" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:125 +msgid "Optional link to a gated contact form." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:128 +#: templates/partials/contact.php:84 +msgid "Mailing Address" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:129 +msgid "Postal address for formal GDPR / legal notices." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:132 +msgid "PGP Public Key URL" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:133 +msgid "Optional link to your security team's PGP public key." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:136 +msgid "Company Registration Number" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:137 +msgid "" +"KvK (NL), Companies House (UK), Handelsregister (DE), EIN (US), or " +"equivalent business registration." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:140 +#: templates/partials/contact.php:100 +msgid "VAT / Tax ID" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:141 +msgid "VAT number, sales-tax ID, or equivalent international tax identifier." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:222 +msgid "Low contrast on white backgrounds" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:223 +msgid "Using your exact color on white backgrounds" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:226 +msgid "" +"Your chosen color is too light for buttons, links, and borders on white " +"sections. On those surfaces Open Trust Center will use a darker, on-brand " +"variant:" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:229 +msgid "" +"You've chosen to keep your exact color on white backgrounds. Buttons, links, " +"and borders in those sections may be hard to read." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:241 +msgid "The hero and navigation still use your exact color." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:252 +msgid "Use my exact color anyway — skip the contrast adjustment." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:265 +#: includes/class-ettic-otc-cpt.php:510 +msgid "Select Logo" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:266 +msgid "" +"Used in the hero and sticky nav. A white version is recommended — it sits on " +"a dark background." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:273 +msgid "Select Avatar" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:274 +msgid "" +"Square image used as the avatar on AI chat responses. Use a colored " +"background with a light or dark favicon or logo on top." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:289 +#: includes/class-ettic-otc-cpt.php:511 includes/class-ettic-otc-cpt.php:523 +#: includes/class-ettic-otc-cpt.php:654 includes/class-ettic-otc-cpt.php:670 +#: includes/class-ettic-otc-cpt.php:756 includes/class-ettic-otc-cpt.php:794 +msgid "Remove" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:301 +msgid "" +"Show a \"Powered by Open Trust Center by Ettic\" credit in the trust center " +"footer." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:305 +msgid "Off by default. Public credits are opt-in." +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:314 +#: templates/partials/certifications.php:22 +#, fuzzy +msgid "Certifications & Compliance" +msgstr "Certificeringen & compliance" + +#: includes/class-ettic-otc-admin-settings.php:315 +#: includes/class-ettic-otc-admin-tools.php:592 +#: includes/class-ettic-otc-cpt.php:271 includes/class-ettic-otc-cpt.php:281 +#: templates/chat.php:45 templates/partials/hero.php:55 +#: templates/trust-center.php:107 +#, fuzzy +msgid "Policies" +msgstr "Beleid" + +#: includes/class-ettic-otc-admin-settings.php:316 +#: includes/class-ettic-otc-admin-tools.php:594 +#: includes/class-ettic-otc-cpt.php:338 includes/class-ettic-otc-cpt.php:347 +#: templates/chat.php:47 templates/partials/hero.php:61 +#: templates/partials/subprocessors.php:20 templates/trust-center.php:109 +#, fuzzy +msgid "Subprocessors" +msgstr "Subverwerkers" + +#: includes/class-ettic-otc-admin-settings.php:317 +#: includes/class-ettic-otc-admin-tools.php:595 +#: includes/class-ettic-otc-cpt.php:371 includes/class-ettic-otc-cpt.php:380 +#: templates/chat.php:48 templates/partials/data-practices.php:34 +#: templates/trust-center.php:110 +#, fuzzy +msgid "Data Practices" +msgstr "Gegevensverwerking" + +#: includes/class-ettic-otc-admin-settings.php:318 +#: includes/class-ettic-otc-admin-tools.php:596 +#: includes/class-ettic-otc-cpt.php:404 includes/class-ettic-otc-cpt.php:414 +msgid "FAQs" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:319 +msgid "Contact & DPO" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:586 +msgid "View Trust Center" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:593 +#: includes/class-ettic-otc-render.php:515 +msgid "General" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:597 templates/chat.php:60 +#: templates/trust-center.php:111 +msgid "Contact" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:601 +msgid "AI Chat" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:604 +msgid "Live" +msgstr "" + +#: includes/class-ettic-otc-admin-settings.php:610 +msgid "Import & Export" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:155 +msgid "" +"Move trust center content and settings between sites, or seed a fresh " +"install from another. API keys and the Turnstile secret are never included — " +"re-enter them on the destination." +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:163 +msgid "Export" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:167 +msgid "Import" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:183 +msgid "What to export" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:186 +msgid "Content (CPTs + bundled media)" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:190 +msgid "Settings only" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:195 +msgid "Content selection" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:225 +msgid "Bundle attached PDFs and images" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:229 +msgid "Download export" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:243 +msgid "Only upload your own exports." +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:244 +msgid "" +"Export files contain your trust-center content and may include sensitive " +"material. Never import a file you received from someone else." +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:248 +msgid "Upload export file" +msgstr "" + +#. translators: %d: max upload size in MB +#: includes/class-ettic-otc-admin-tools.php:253 +#, php-format +msgid "Max %d MB" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:259 +msgid "On conflict" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:262 +msgid "Skip — keep existing records untouched" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:266 +msgid "Overwrite — replace existing records" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:270 +msgid "Create new — duplicate with a -import suffix" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:275 +msgid "Preview import" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:292 +msgid "Import preview" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:296 +msgid "Import blocked:" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:317 +msgid "" +"Settings export — current values will be merged with imported values. " +"Excluded keys (encrypted secrets, salt, server-controlled flags) are kept as-" +"is." +msgstr "" + +#. translators: %1$d: create count, %2$d: update count, %3$d: skip count +#: includes/class-ettic-otc-admin-tools.php:324 +#, php-format +msgid "Will create %1$d, update %2$d, skip %3$d." +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:338 +msgid "Title" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:339 +msgid "Action" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:340 +msgid "UUID" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:361 +msgid "Confirm and import" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:363 +msgid "Cancel" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:385 +msgid "Pick at least one record to export." +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:415 +msgid "No file uploaded." +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:421 +msgid "Upload exceeds size limit." +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:514 +msgid "Import cancelled." +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:519 +msgid "Import has unresolved errors." +msgstr "" + +#. translators: %1$d: created, %2$d: updated, %3$d: skipped +#: includes/class-ettic-otc-admin-tools.php:539 +#, php-format +msgid "Imported: %1$d created, %2$d updated, %3$d skipped." +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:547 +msgid "Errors:" +msgstr "" + +#: includes/class-ettic-otc-admin-tools.php:593 +#: includes/class-ettic-otc-cpt.php:305 includes/class-ettic-otc-cpt.php:314 +#: templates/chat.php:46 templates/partials/hero.php:49 +#: templates/trust-center.php:108 +#, fuzzy +msgid "Certifications" +msgstr "Certificeringen" + +#: includes/class-ettic-otc-admin.php:57 templates/partials/hero.php:38 +#: templates/trust-center.php:19 +#, fuzzy +msgid "Trust Center" +msgstr "Trust Center" + +#: includes/class-ettic-otc-admin.php:67 includes/class-ettic-otc-admin.php:68 +msgid "Settings" +msgstr "" + +#: includes/class-ettic-otc-admin.php:79 includes/class-ettic-otc-admin.php:80 +msgid "Questions" +msgstr "" + +#: includes/class-ettic-otc-admin.php:158 +msgid "Select Badge Image" +msgstr "" + +#: includes/class-ettic-otc-admin.php:159 +msgid "Use as Badge" +msgstr "" + +#: includes/class-ettic-otc-admin.php:160 +msgid "Select Proof Artifact" +msgstr "" + +#: includes/class-ettic-otc-admin.php:161 +msgid "Use This File" +msgstr "" + +#: includes/class-ettic-otc-admin.php:162 includes/class-ettic-otc-cpt.php:522 +msgid "Upload File" +msgstr "" + +#: includes/class-ettic-otc-admin.php:163 includes/class-ettic-otc-cpt.php:522 +msgid "Replace File" +msgstr "" + +#: includes/class-ettic-otc-admin.php:178 +msgid "No match in catalog, just keep typing to add manually." +msgstr "" + +#: includes/class-ettic-otc-admin.php:179 +msgid "Auto-filled from catalog, you may want to verify this." +msgstr "" + +#: includes/class-ettic-otc-admin.php:180 +msgid "" +"Auto-filled template, please verify this matches how you use this service." +msgstr "" + +#: includes/class-ettic-otc-admin.php:181 +msgid "click to autofill" +msgstr "" + +#: includes/class-ettic-otc-admin.php:182 +msgid "Catalog suggestions" +msgstr "" + +#: includes/class-ettic-otc-admin.php:228 +msgid "Open Trust Center requires pretty permalinks." +msgstr "" + +#. translators: %s: link to Settings → Permalinks +#: includes/class-ettic-otc-admin.php:232 +#, php-format +msgid "" +"Your site is using \"Plain\" permalinks. Please go to %s and choose any " +"other option (Post name is the WordPress default)." +msgstr "" + +#: includes/class-ettic-otc-admin.php:233 +msgid "Settings → Permalinks" +msgstr "" + +#: includes/class-ettic-otc-admin.php:238 +msgid "" +"Without pretty permalinks, every link Open Trust Center generates returns " +"404 — including the trust center page itself. Visitors will not be able to " +"reach your policies, certifications, or chat." +msgstr "" + +#: includes/class-ettic-otc-admin.php:242 +msgid "Read-only fallback if you cannot change permalinks" +msgstr "" + +#: includes/class-ettic-otc-admin.php:246 +msgid "You can preview the trust center via raw query-string URLs:" +msgstr "" + +#: includes/class-ettic-otc-admin.php:254 +msgid "This is for testing only." +msgstr "" + +#: includes/class-ettic-otc-admin.php:255 +msgid "Switching to pretty permalinks is the only supported configuration." +msgstr "" + +#: includes/class-ettic-otc-chat.php:112 +#, fuzzy +msgid "Invalid nonce — refresh the page and try again." +msgstr "Ongeldige nonce — ververs de pagina en probeer opnieuw." + +#: includes/class-ettic-otc-chat.php:140 +#, fuzzy +msgid "Please complete the anti-abuse challenge and try again." +msgstr "Voltooi de anti-misbruikcontrole en probeer opnieuw." + +#: includes/class-ettic-otc-chat.php:152 +msgid "You are sending messages too fast. Please wait a moment and try again." +msgstr "" + +#: includes/class-ettic-otc-chat.php:163 +msgid "" +"You have reached the per-session message limit. Please wait a bit and try " +"again." +msgstr "" + +#: includes/class-ettic-otc-chat.php:183 +msgid "AI chat is not configured on this site." +msgstr "" + +#: includes/class-ettic-otc-chat.php:192 +msgid "Configured provider is unknown." +msgstr "" + +#: includes/class-ettic-otc-chat.php:201 +msgid "No API key stored for the configured provider." +msgstr "" + +#: includes/class-ettic-otc-chat.php:214 +msgid "Your message is empty." +msgstr "" + +#: includes/class-ettic-otc-chat.php:232 +msgid "" +"The daily chat budget for this site has been reached. Please try again later." +msgstr "" + +#: includes/class-ettic-otc-chat.php:394 +msgid "Chat provider failed unexpectedly." +msgstr "" + +#. translators: %s is the tool name (get_document or search_documents). +#: includes/class-ettic-otc-chat.php:722 +#, php-format +msgid "" +"You already called %s with the same arguments earlier in this conversation. " +"Pick a different document id from the index, or rephrase the search query " +"with different keywords." +msgstr "" + +#: includes/class-ettic-otc-chat.php:732 +msgid "Document id is required." +msgstr "" + +#. translators: %s is the requested document id. +#: includes/class-ettic-otc-chat.php:741 +#, php-format +msgid "" +"No document with id \"%s\". Pick one of the ids listed in the corpus index " +"above." +msgstr "" + +#: includes/class-ettic-otc-chat.php:749 +msgid "Search query is empty." +msgstr "" + +#: includes/class-ettic-otc-chat.php:752 +msgid "Search index unavailable." +msgstr "" + +#. translators: %s is the search query. +#: includes/class-ettic-otc-chat.php:758 +#, php-format +msgid "" +"No documents matched \"%s\". Try broader keywords or pick a document id from " +"the corpus index above." +msgstr "" + +#: includes/class-ettic-otc-chat.php:770 +msgid "Search ranking returned no usable results." +msgstr "" + +#. translators: %s is the unknown tool name. +#: includes/class-ettic-otc-chat.php:775 +#, php-format +msgid "Unknown tool: %s" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:252 +msgid "Pick from the catalog or type your own subprocessor name" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:255 +msgid "" +"Pick from the catalog or type your own, e.g. Analytics or Transactional Email" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:258 +msgid "Pick from the catalog or type your own, e.g. SOC 2 Type II or ISO 27001" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:272 templates/partials/policies.php:54 +#, fuzzy +msgid "Policy" +msgstr "Beleid" + +#: includes/class-ettic-otc-cpt.php:273 +msgid "Add Policy" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:274 +msgid "Add New Policy" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:275 +msgid "Edit Policy" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:276 +msgid "New Policy" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:277 +#, fuzzy +msgid "View Policy" +msgstr "Beleid bekijken" + +#: includes/class-ettic-otc-cpt.php:278 +msgid "Search Policies" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:279 +msgid "No policies found." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:280 +msgid "No policies in trash." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:306 +msgid "Certification" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:307 +msgid "Add Certification" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:308 +msgid "Add New Certification" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:309 +msgid "Edit Certification" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:310 +msgid "New Certification" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:311 +msgid "Search Certifications" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:312 +msgid "No certifications found." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:313 +msgid "No certifications in trash." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:339 +msgid "Subprocessor" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:340 +msgid "Add Subprocessor" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:341 +msgid "Add New Subprocessor" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:342 +msgid "Edit Subprocessor" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:343 +msgid "New Subprocessor" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:344 +msgid "Search Subprocessors" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:345 +msgid "No subprocessors found." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:346 +msgid "No subprocessors in trash." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:372 +msgid "Data Practice" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:373 +msgid "Add Data Practice" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:374 +msgid "Add New Data Practice" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:375 +msgid "Edit Data Practice" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:376 +msgid "New Data Practice" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:377 +msgid "Search Data Practices" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:378 +msgid "No data practices found." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:379 +msgid "No data practices in trash." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:405 templates/chat.php:61 +#: templates/trust-center.php:112 +msgid "FAQ" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:406 +msgid "Add FAQ" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:407 +msgid "Add New FAQ" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:408 +msgid "Edit FAQ" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:409 +msgid "New FAQ" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:410 +msgid "View FAQ" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:411 +msgid "Search FAQs" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:412 +msgid "No FAQs found." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:413 +msgid "No FAQs in trash." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:436 +msgid "Certification Details" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:437 +msgid "Policy Details" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:438 +msgid "Subprocessor Details" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:439 +msgid "Data Practice Details" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:440 +msgid "FAQ Details" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:461 +msgid "Audited certification (issued by a third party)" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:462 +msgid "Self-attested alignment (no external audit)" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:466 +msgid "Active / currently met" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:467 +msgid "In progress" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:468 +msgid "Expired / lapsed" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:472 +msgid "Certification Type" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:478 +msgid "" +"Audited means a third-party issued a formal certificate with dates (SOC 2, " +"ISO 27001, PCI DSS). Self-attested means you adhere to the framework without " +"an external audit — the honest framing for GDPR, CCPA, and most HIPAA " +"posture claims." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:482 includes/class-ettic-otc-cpt.php:1057 +#, fuzzy +msgid "Status" +msgstr "Status" + +#: includes/class-ettic-otc-cpt.php:488 +msgid "" +"\"Active\" for audited means you hold a current certificate. \"Active\" for " +"self-attested means you currently meet the framework. Use \"In progress\" " +"while working toward either." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:492 includes/class-ettic-otc-cpt.php:1056 +#, fuzzy +msgid "Issuing Body" +msgstr "Uitgegeven door" + +#: includes/class-ettic-otc-cpt.php:493 +msgid "e.g., AICPA, BSI Group, Schellman" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:497 +msgid "Issue Date" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:502 includes/class-ettic-otc-cpt.php:1058 +#, fuzzy +msgid "Expiry Date" +msgstr "Vervaldatum" + +#: includes/class-ettic-otc-cpt.php:507 +msgid "Framework Logo" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:512 +msgid "" +"Use the official framework mark where licensing allows (SOC 2, ISO, GDPR " +"shield). Square images work best at 44×44." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:516 +msgid "Proof Artifact" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:519 includes/class-ettic-otc-cpt.php:666 +msgid "View file" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:524 +msgid "" +"Optional PDF the trust center can link to — e.g. the audit report, " +"certificate, or policy mapping document. Shown as a download button on the " +"card." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:528 +msgid "Scope & Notes" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:529 +msgid "" +"e.g., We process EU personal data under GDPR. Our DPA covers customer data, " +"and we support DSARs within 30 days." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:530 +msgid "" +"Required for self-attested frameworks so the card has meaningful content. " +"One or two sentences on scope, how you meet the framework, or what prospects " +"should know." +msgstr "" + +#. translators: %s: policy version number +#: includes/class-ettic-otc-cpt.php:585 +#, fuzzy, php-format +msgid "Version %s" +msgstr "Versie %s" + +#: includes/class-ettic-otc-cpt.php:588 +msgid "" +"Regular saves update the current version. Use the checkbox below to formally " +"publish a new version." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:597 +msgid "Publish as new version" +msgstr "" + +#. translators: %1$d: current version number, %2$d: next version number +#: includes/class-ettic-otc-cpt.php:602 +#, php-format +msgid "" +"This will save the current content as v%1$d and create v%2$d. Only check " +"this for formal, published changes — not minor edits." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:611 +msgid "What changed?" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:614 +msgid "e.g., Updated data retention from 90 to 60 days" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:615 +msgid "Shown in the public version history." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:621 +msgid "Policy ID" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:622 +msgid "e.g., POL-012" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:623 +msgid "" +"Optional short reference (e.g., POL-012). Shown on the public listing and in " +"security questionnaires." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:627 includes/class-ettic-otc-cpt.php:1096 +#: templates/partials/policies.php:55 +#, fuzzy +msgid "Category" +msgstr "Categorie" + +#: includes/class-ettic-otc-cpt.php:636 +msgid "Effective Date" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:641 +msgid "Next Review Date" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:646 +msgid "Framework Citations" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:657 +msgid "e.g., SOC 2 CC6.1, ISO 27001 A.9.2…" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:659 +msgid "" +"Framework or control references this policy satisfies. Appears as pill " +"badges on the public page." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:663 +msgid "PDF Attachment" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:669 +msgid "Replace PDF" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:669 +msgid "Upload PDF" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:671 +msgid "" +"Upload the signed PDF. Visitors see a \"Download PDF\" button only when a " +"file is attached." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:675 includes/class-ettic-otc-cpt.php:831 +msgid "Sort Order" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:677 includes/class-ettic-otc-cpt.php:833 +msgid "Lower numbers appear first." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:694 includes/class-ettic-otc-cpt.php:765 +#: includes/class-ettic-otc-cpt.php:1127 +#: templates/partials/data-practices.php:96 +#: templates/partials/subprocessors.php:37 +#, fuzzy +msgid "Purpose" +msgstr "Doel" + +#: includes/class-ettic-otc-cpt.php:696 +msgid "What does this subprocessor do for your company?" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:700 templates/partials/subprocessors.php:38 +#, fuzzy +msgid "Data Processed" +msgstr "Verwerkte gegevens" + +#: includes/class-ettic-otc-cpt.php:702 +msgid "What types of data does this subprocessor handle?" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:706 +msgid "Country / Location" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:707 +msgid "e.g., United States" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:711 templates/partials/subprocessors.php:41 +#, fuzzy +msgid "Website" +msgstr "Website" + +#: includes/class-ettic-otc-cpt.php:718 +#, fuzzy +msgid "DPA Signed" +msgstr "DPA ondertekend" + +#: includes/class-ettic-otc-cpt.php:720 +msgid "" +"A Data Processing Agreement (DPA) is a contract between you and the " +"subprocessor covering how they handle personal data on your behalf. Check " +"this box once your organization has signed one with this vendor." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:750 +msgid "Data Items Collected" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:759 includes/class-ettic-otc-cpt.php:797 +msgid "Type and press Enter..." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:772 +#: templates/partials/data-practices.php:103 +#, fuzzy +msgid "Legal Basis" +msgstr "Wettelijke grondslag" + +#: includes/class-ettic-otc-cpt.php:774 +msgid "— Select —" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:781 +msgid "Retention Period" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:782 +msgid "e.g., 30 days" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:788 +#: templates/partials/data-practices.php:117 +#, fuzzy +msgid "Shared With" +msgstr "Gedeeld met" + +#: includes/class-ettic-otc-cpt.php:803 +msgid "Properties" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:807 +msgid "Collected" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:811 +msgid "Stored" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:815 +msgid "Shared with third parties" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:819 +msgid "Sold to third parties" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:823 +msgid "Encrypted" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:826 +msgid "" +"Unchecked means an explicit \"No\". The AI assistant reports these values " +"verbatim to visitors asking questions like \"Do you sell customer data?\"." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:1095 templates/partials/policies.php:52 +msgid "ID" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:1097 +#: includes/class-ettic-otc-version.php:160 templates/partials/policies.php:56 +#, fuzzy +msgid "Version" +msgstr "Versie" + +#: includes/class-ettic-otc-cpt.php:1098 +msgid "PDF" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:1128 +#: templates/partials/subprocessors.php:39 +#, fuzzy +msgid "Location" +msgstr "Locatie" + +#: includes/class-ettic-otc-cpt.php:1129 +#: templates/partials/subprocessors.php:40 +#, fuzzy +msgid "DPA" +msgstr "DPA" + +#: includes/class-ettic-otc-cpt.php:1148 +#, fuzzy +msgid "Data Items" +msgstr "Gegevensitems" + +#: includes/class-ettic-otc-cpt.php:1149 includes/class-ettic-otc-cpt.php:1218 +msgid "Order" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:1178 +msgid "Related Policy" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:1180 +msgid "— None —" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:1185 +msgid "Optional — link this answer to a published policy for deeper context." +msgstr "" + +#: includes/class-ettic-otc-cpt.php:1190 +msgid "Sort order:" +msgstr "" + +#: includes/class-ettic-otc-cpt.php:1191 +msgid "" +"Use the Page Attributes box below (Order field) to control FAQ order. Lower " +"numbers appear first." +msgstr "" + +#: includes/class-ettic-otc-io.php:206 +msgid "PHP ZipArchive extension is required for export." +msgstr "" + +#: includes/class-ettic-otc-io.php:211 +msgid "Could not create temp file for export." +msgstr "" + +#: includes/class-ettic-otc-io.php:225 +msgid "Could not open ZIP for writing." +msgstr "" + +#: includes/class-ettic-otc-io.php:257 +msgid "Unrecognised export format." +msgstr "" + +#. translators: %1$d: schema version found, %2$d: schema version expected +#: includes/class-ettic-otc-io.php:263 +#, php-format +msgid "Schema version mismatch (found %1$d, expected %2$d)." +msgstr "" + +#. translators: %1$s: their version, %2$s: our version +#: includes/class-ettic-otc-io.php:282 +#, php-format +msgid "Plugin major version mismatch (export: %1$s, this site: %2$s)." +msgstr "" + +#: includes/class-ettic-otc-io.php:422 +msgid "PHP ZipArchive extension is required." +msgstr "" + +#: includes/class-ettic-otc-io.php:426 +msgid "Could not open uploaded archive." +msgstr "" + +#: includes/class-ettic-otc-io.php:431 +msgid "Archive is missing manifest.json." +msgstr "" + +#: includes/class-ettic-otc-io.php:435 +msgid "manifest.json could not be parsed." +msgstr "" + +#: includes/class-ettic-otc-io.php:726 +msgid "Could not reopen archive for media import." +msgstr "" + +#. translators: %s: media path +#: includes/class-ettic-otc-io.php:741 +#, php-format +msgid "Bundled media missing during import: %s" +msgstr "" + +#. translators: %s: filename +#: includes/class-ettic-otc-io.php:761 +#, php-format +msgid "Could not write attachment file: %s" +msgstr "" + +#: includes/class-ettic-otc-io.php:779 +msgid "Could not create attachment." +msgstr "" + +#: includes/class-ettic-otc-render.php:83 +msgid "Session expired. Please reload the page and try again." +msgstr "" + +#: includes/class-ettic-otc-render.php:89 +#, fuzzy +msgid "Please enter a question." +msgstr "Voer een vraag in." + +#: includes/class-ettic-otc-render.php:98 +msgid "AI chat is not configured." +msgstr "" + +#: includes/class-ettic-otc-render.php:270 +msgid "Page not found." +msgstr "" + +#: includes/class-ettic-otc-render.php:271 +#: templates/partials/policy-single.php:54 +#, fuzzy +msgid "Back to Trust Center" +msgstr "Terug naar Trust Center" + +#: includes/class-ettic-otc-render.php:442 +msgid "Updated just now" +msgstr "" + +#. translators: %d: number of minutes since last update +#: includes/class-ettic-otc-render.php:447 +#, fuzzy, php-format +msgid "Updated %d minute ago" +msgid_plural "Updated %d minutes ago" +msgstr[0] "%d minuut geleden bijgewerkt" +msgstr[1] "%d minuten geleden bijgewerkt" + +#. translators: %d: number of hours since last update +#: includes/class-ettic-otc-render.php:454 +#, fuzzy, php-format +msgid "Updated %d hour ago" +msgid_plural "Updated %d hours ago" +msgstr[0] "%d uur geleden bijgewerkt" +msgstr[1] "%d uur geleden bijgewerkt" + +#. translators: %d: number of days since last update +#: includes/class-ettic-otc-render.php:461 +#, fuzzy, php-format +msgid "Updated %d day ago" +msgid_plural "Updated %d days ago" +msgstr[0] "%d dag geleden bijgewerkt" +msgstr[1] "%d dagen geleden bijgewerkt" + +#. translators: %s = formatted date +#. translators: %s: policy last updated date +#: includes/class-ettic-otc-render.php:467 +#: templates/partials/policy-single.php:111 +#, fuzzy, php-format +msgid "Updated %s" +msgstr "Bijgewerkt op %s" + +#: includes/class-ettic-otc-render.php:511 +msgid "Security" +msgstr "" + +#: includes/class-ettic-otc-render.php:512 +msgid "Privacy" +msgstr "" + +#: includes/class-ettic-otc-render.php:513 +msgid "Compliance" +msgstr "" + +#: includes/class-ettic-otc-render.php:514 +msgid "Operational" +msgstr "" + +#: includes/class-ettic-otc-render.php:526 +msgid "Certified" +msgstr "" + +#: includes/class-ettic-otc-render.php:527 +msgid "In audit" +msgstr "" + +#: includes/class-ettic-otc-render.php:528 +#, fuzzy +msgid "Expired" +msgstr "Verlopen" + +#: includes/class-ettic-otc-render.php:537 +#, fuzzy +msgid "Compliant" +msgstr "Compliant" + +#: includes/class-ettic-otc-render.php:538 +msgid "Working toward" +msgstr "" + +#: includes/class-ettic-otc-render.php:539 +msgid "Lapsed" +msgstr "" + +#: includes/class-ettic-otc-render.php:545 +msgid "Consent" +msgstr "" + +#: includes/class-ettic-otc-render.php:546 +msgid "Contractual Necessity" +msgstr "" + +#: includes/class-ettic-otc-render.php:547 +msgid "Legitimate Interest" +msgstr "" + +#: includes/class-ettic-otc-render.php:548 +msgid "Legal Obligation" +msgstr "" + +#: includes/class-ettic-otc-render.php:549 +msgid "Vital Interest" +msgstr "" + +#: includes/class-ettic-otc-render.php:550 +msgid "Public Interest" +msgstr "" + +#: includes/class-ettic-otc-version.php:127 +msgid "Version History" +msgstr "" + +#: includes/class-ettic-otc-version.php:149 +msgid "Current version:" +msgstr "" + +#: includes/class-ettic-otc-version.php:152 +msgid "Version history will appear after the first update." +msgstr "" + +#: includes/class-ettic-otc-version.php:162 templates/partials/policies.php:58 +#, fuzzy +msgid "Actions" +msgstr "Acties" + +#: includes/class-ettic-otc-version.php:169 +#: templates/partials/policy-single.php:174 +msgid "Current" +msgstr "" + +#: includes/class-ettic-otc-version.php:183 +#: includes/class-ettic-otc-version.php:184 +msgid "View" +msgstr "" + +#: includes/class-ettic-otc-version.php:187 +msgid "Compare" +msgstr "" + +#: includes/class-ettic-otc-version.php:188 +msgid "Diff" +msgstr "" + +#: includes/data/faq-catalog.php:29 +msgid "What is a trust center?" +msgstr "" + +#: includes/data/faq-catalog.php:30 +msgid "" +"A trust center is a public page where a company shares information about how " +"it handles security, privacy, and compliance. It usually includes security " +"policies, a list of subprocessors, compliance certifications, and details " +"about how customer data is handled. The goal is to give customers, partners, " +"and prospects one place to answer due diligence questions without having to " +"email anyone." +msgstr "" + +#: includes/data/faq-catalog.php:34 +msgid "What is a Data Processing Agreement (DPA)?" +msgstr "" + +#: includes/data/faq-catalog.php:35 +msgid "" +"A Data Processing Agreement, or DPA, is a contract between a company that " +"collects personal data and a company that processes that data on its behalf. " +"It defines what data can be processed, for what purpose, how long it can be " +"kept, and what security measures must be in place. Under privacy laws like " +"the GDPR, a DPA is required whenever one company processes personal data for " +"another." +msgstr "" + +#: includes/data/faq-catalog.php:39 +msgid "What is a subprocessor?" +msgstr "" + +#: includes/data/faq-catalog.php:40 +msgid "" +"A subprocessor is a third-party service that a company uses to help deliver " +"its product, and that may come into contact with customer data along the " +"way. Common examples include cloud hosting providers, email delivery " +"services, analytics platforms, and customer support tools. Companies publish " +"subprocessor lists so customers can see exactly which vendors may handle " +"their data." +msgstr "" + +#: includes/data/faq-catalog.php:44 +msgid "What is the difference between a data controller and a data processor?" +msgstr "" + +#: includes/data/faq-catalog.php:45 +msgid "" +"The data controller is the party that decides why and how personal data is " +"collected and used. The data processor is the party that handles that data " +"on the controller's behalf, following the controller's instructions. A SaaS " +"customer is usually the controller of their end-user data, while the SaaS " +"vendor acts as the processor. Each role carries different legal " +"responsibilities under privacy laws like the GDPR." +msgstr "" + +#: includes/data/faq-catalog.php:49 +msgid "What is personal data?" +msgstr "" + +#: includes/data/faq-catalog.php:50 +msgid "" +"Personal data is any information that can be used to identify a living " +"person, either on its own or when combined with other information. Obvious " +"examples include names, email addresses, phone numbers, and home addresses. " +"Less obvious examples include IP addresses, device identifiers, cookies, and " +"location data. Privacy laws such as the GDPR and CCPA treat personal data as " +"something that must be collected, stored, and shared with care." +msgstr "" + +#: includes/data/faq-catalog.php:54 +msgid "What is responsible disclosure?" +msgstr "" + +#: includes/data/faq-catalog.php:55 +msgid "" +"Responsible disclosure is the practice of reporting a security vulnerability " +"privately to the company that owns the affected system, giving them a " +"reasonable amount of time to fix it before any details are shared publicly. " +"It protects users from being exposed to a known issue before a patch is " +"available. Most trust centers include a contact address or form for " +"reporting vulnerabilities this way." +msgstr "" + +#: includes/data/faq-catalog.php:59 +msgid "What is a compliance certification?" +msgstr "" + +#: includes/data/faq-catalog.php:60 +msgid "" +"A compliance certification is a formal statement, usually issued by an " +"independent auditor, confirming that a company meets the requirements of a " +"specific security or privacy standard. Certifications give customers a way " +"to trust a company's practices without having to inspect them directly. The " +"scope, issuing body, and validity period are typically listed alongside each " +"certification in a trust center." +msgstr "" + +#: includes/data/faq-catalog.php:64 +msgid "What is a security policy?" +msgstr "" + +#: includes/data/faq-catalog.php:65 +msgid "" +"A security policy is a written document that describes how a company " +"protects its systems, data, and people. Policies commonly cover topics like " +"access control, incident response, acceptable use, vendor management, and " +"business continuity. Publishing policies in a trust center lets customers " +"see how security is handled without needing to sign an NDA first." +msgstr "" + +#: includes/data/faq-catalog.php:69 +msgid "Why do companies publish a list of subprocessors?" +msgstr "" + +#: includes/data/faq-catalog.php:70 +msgid "" +"Publishing a subprocessor list is a transparency practice, and in many cases " +"a legal requirement, that lets customers see every third party that may " +"handle their data. It gives customers the chance to review new vendors " +"before they start processing data, and it makes it easier to meet their own " +"compliance obligations. Most trust centers also offer a way to be notified " +"when the list changes." +msgstr "" + +#: includes/data/faq-catalog.php:74 +msgid "What is a data practice?" +msgstr "" + +#: includes/data/faq-catalog.php:75 +msgid "" +"A data practice describes a specific way a company collects, uses, stores, " +"or shares information. Each practice usually spells out what data is " +"involved, why it is collected, how long it is kept, and who it is shared " +"with. Grouping data practices by category, such as account data, usage data, " +"or support data, helps customers understand exactly what happens to their " +"information." +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider-anthropic.php:44 +#: includes/providers/class-ettic-otc-chat-provider.php:46 +msgid "Anthropic" +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider-anthropic.php:63 +msgid "No models available — your account may not be authorized." +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider-anthropic.php:127 +msgid "Anthropic adapter missing required args." +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider-anthropic.php:219 +msgid "Anthropic request failed." +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider-anthropic.php:414 +msgid "Searching documents…" +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider-anthropic.php:415 +msgid "Reading documents…" +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider-openai.php:66 +#: includes/providers/class-ettic-otc-chat-provider.php:52 +msgid "OpenAI" +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider-openai.php:166 +msgid "OpenAI adapter missing required args." +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider-openai.php:253 +msgid "OpenAI request failed." +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider-openrouter.php:38 +#: includes/providers/class-ettic-otc-chat-provider.php:58 +msgid "OpenRouter" +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider.php:104 +msgid "No chat models available for this key." +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider.php:117 +msgid "API key is empty." +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider.php:123 +msgid "Request failed." +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider.php:268 +msgid "" +"I couldn't find a confident answer in the published trust center documents. " +"Please contact the team for help." +msgstr "" + +#. translators: %s is the document title. +#: includes/providers/class-ettic-otc-chat-provider.php:342 +#, php-format +msgid "Read \"%s\"" +msgstr "" + +#. translators: %s is the document title. +#: includes/providers/class-ettic-otc-chat-provider.php:344 +#, php-format +msgid "Reading \"%s\"" +msgstr "" + +#. translators: %s is the document id. +#: includes/providers/class-ettic-otc-chat-provider.php:350 +#, php-format +msgid "Read %s" +msgstr "" + +#. translators: %s is the document id. +#: includes/providers/class-ettic-otc-chat-provider.php:352 +#, php-format +msgid "Reading %s" +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider.php:354 +msgid "Read a document" +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider.php:354 +msgid "Reading a document" +msgstr "" + +#. translators: %s is the search query. +#: includes/providers/class-ettic-otc-chat-provider.php:367 +#, php-format +msgid "Searched for \"%s\"" +msgstr "" + +#. translators: %s is the search query. +#: includes/providers/class-ettic-otc-chat-provider.php:369 +#, php-format +msgid "Searching for \"%s\"" +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider.php:371 +msgid "Searched documents" +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider.php:371 +msgid "Searching documents" +msgstr "" + +#. translators: %d is the number of documents that were read in parallel. +#: includes/providers/class-ettic-otc-chat-provider.php:399 +#, php-format +msgid "Read %d documents" +msgstr "" + +#. translators: %d is the number of documents being read in parallel. +#: includes/providers/class-ettic-otc-chat-provider.php:401 +#, php-format +msgid "Reading %d documents" +msgstr "" + +#. translators: %d is the number of search queries that were fired in parallel. +#: includes/providers/class-ettic-otc-chat-provider.php:406 +#, php-format +msgid "Ran %d searches" +msgstr "" + +#. translators: %d is the number of search queries fired in parallel. +#: includes/providers/class-ettic-otc-chat-provider.php:408 +#, php-format +msgid "Running %d searches" +msgstr "" + +#. translators: %d is the number of parallel retrieval calls (mixed types). +#: includes/providers/class-ettic-otc-chat-provider.php:412 +#, php-format +msgid "Ran %d retrievals" +msgstr "" + +#. translators: %d is the number of parallel retrieval calls (mixed types). +#: includes/providers/class-ettic-otc-chat-provider.php:414 +#, php-format +msgid "Running %d retrievals" +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider.php:443 +#: includes/providers/class-ettic-otc-chat-provider.php:462 +#: includes/providers/class-ettic-otc-chat-provider.php:513 +msgid "Refused outbound request to disallowed host." +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider.php:518 +msgid "AI streaming requires the PHP cURL extension." +msgstr "" + +#: includes/providers/class-ettic-otc-chat-provider.php:620 +msgid "AI streaming requires the WordPress cURL transport." +msgstr "" + +#. translators: %d: HTTP status code returned by the provider +#: includes/providers/class-ettic-otc-chat-provider.php:673 +#: includes/providers/class-ettic-otc-chat-provider.php:728 +#, php-format +msgid "Provider returned HTTP %d" +msgstr "" + +#. translators: %s: company name +#: templates/chat.php:70 +#, fuzzy, php-format +msgid "Ask %s — Trust Center" +msgstr "Vraag het aan %s — Trust Center" + +#: templates/chat.php:100 templates/trust-center.php:101 +msgid "Skip to content" +msgstr "" + +#: templates/chat.php:102 templates/partials/policy-single.php:36 +#: templates/trust-center.php:114 +msgid "Trust center navigation" +msgstr "" + +#: templates/chat.php:131 templates/trust-center.php:144 +#, fuzzy +msgid "Ask AI" +msgstr "Vraag de AI" + +#: templates/chat.php:145 +msgid "Ask AI is not configured" +msgstr "" + +#: templates/chat.php:146 +msgid "The site administrator has not enabled the AI chat feature yet." +msgstr "" + +#: templates/chat.php:148 +msgid "Browse trust center" +msgstr "" + +#: templates/chat.php:176 templates/chat.php:353 +#, fuzzy +msgid "You" +msgstr "Jij" + +#: templates/chat.php:178 templates/chat.php:195 templates/chat.php:354 +#, fuzzy +msgid "just now" +msgstr "zojuist" + +#: templates/chat.php:202 templates/chat.php:346 +#, fuzzy +msgid "Sources" +msgstr "Bronnen" + +#: templates/chat.php:212 templates/chat.php:356 +#, fuzzy +msgid "" +"AI-generated answer. Not legal, security, or compliance advice. Verify " +"against the sources above." +msgstr "" +"Dit antwoord is door AI gegenereerd. Geen juridisch, beveiligings- of " +"compliance-advies. Controleer het altijd aan de hand van de bronnen " +"hierboven." + +#: templates/chat.php:222 +msgid "" +"JavaScript is disabled — you can still ask one question below. The answer " +"will load as a regular page." +msgstr "" + +#: templates/chat.php:225 +msgid "Your question" +msgstr "" + +#: templates/chat.php:232 +msgid "Ask" +msgstr "" + +#: templates/chat.php:243 +#, fuzzy +msgid "Are you SOC 2 compliant?" +msgstr "Zijn jullie SOC 2-compliant?" + +#: templates/chat.php:244 +#, fuzzy +msgid "Where is customer data stored?" +msgstr "Waar worden klantgegevens opgeslagen?" + +#: templates/chat.php:245 +msgid "What's your incident response process?" +msgstr "" + +#: templates/chat.php:246 +#, fuzzy +msgid "Which subprocessors do you use?" +msgstr "Welke subverwerkers gebruiken jullie?" + +#: templates/chat.php:271 +msgid "Suggested questions" +msgstr "" + +#: templates/chat.php:286 +msgid "Ask a question" +msgstr "" + +#: templates/chat.php:293 templates/chat.php:335 +#, fuzzy +msgid "Ask anything about our security and compliance…" +msgstr "Stel een vraag over onze beveiliging en compliance…" + +#: templates/chat.php:295 templates/chat.php:344 +#, fuzzy +msgid "Start a new conversation" +msgstr "Start een nieuw gesprek" + +#: templates/chat.php:298 templates/chat.php:336 +#, fuzzy +msgid "Send" +msgstr "Versturen" + +#. translators: 1: link to trust center, 2: company name +#: templates/chat.php:309 +#, php-format +msgid "" +"Grounded in the published %1$s for %2$s. AI-generated, not legal, security, " +"or compliance advice. Always check the sources." +msgstr "" + +#: templates/chat.php:310 +msgid "trust center" +msgstr "" + +#: templates/chat.php:337 +#, fuzzy +msgid "Stop" +msgstr "Stoppen" + +#: templates/chat.php:338 +#, fuzzy +msgid "Thinking…" +msgstr "Denkt na…" + +#: templates/chat.php:339 +#, fuzzy +msgid "Connection lost. Retry?" +msgstr "Verbinding verbroken. Opnieuw proberen?" + +#: templates/chat.php:340 +#, fuzzy +msgid "Contact security team →" +msgstr "Neem contact op met het beveiligingsteam →" + +#: templates/chat.php:341 +#, fuzzy +msgid "Copy" +msgstr "Kopiëren" + +#: templates/chat.php:342 +#, fuzzy +msgid "Copied" +msgstr "Gekopieerd" + +#: templates/chat.php:343 templates/partials/policy-single.php:139 +#, fuzzy +msgid "Print" +msgstr "Afdrukken" + +#: templates/chat.php:345 +#, fuzzy +msgid "This conversation is getting long. Start fresh for better answers." +msgstr "Dit gesprek wordt lang — begin opnieuw voor de beste antwoorden." + +#: templates/chat.php:347 +#, fuzzy +msgid "" +"I don't see enough information in our trust center to answer that " +"confidently." +msgstr "" +"In ons Trust Center staat niet genoeg informatie om die vraag met zekerheid " +"te beantwoorden." + +#: templates/chat.php:348 +#, fuzzy +msgid "The AI provider returned an error. Please try again." +msgstr "De AI-provider gaf een fout. Probeer het opnieuw." + +#: templates/chat.php:349 +#, fuzzy +msgid "" +"AI is temporarily unavailable. Please try again in a few minutes or browse " +"our published content." +msgstr "" +"De AI is tijdelijk niet beschikbaar. Probeer het over enkele minuten opnieuw " +"of bekijk onze gepubliceerde content." + +#: templates/chat.php:350 +#, fuzzy +msgid "Message is too long." +msgstr "Het bericht is te lang." + +#: templates/chat.php:351 +#, fuzzy +msgid "Please wait a moment before asking again." +msgstr "Wacht even voordat je opnieuw een vraag stelt." + +#: templates/chat.php:352 +#, fuzzy +msgid "Cite source" +msgstr "Bron citeren" + +#: templates/chat.php:355 +#, fuzzy +msgid "No content returned by the model." +msgstr "Het model gaf geen antwoord terug." + +#: templates/partials/certifications.php:23 +#, fuzzy +msgid "" +"Our active certifications and compliance frameworks demonstrate our " +"commitment to protecting your data." +msgstr "" +"Onze actieve certificeringen en compliance-frameworks laten zien hoe serieus " +"we jouw gegevens beschermen." + +#: templates/partials/certifications.php:59 +msgid "Self-attested framework" +msgstr "" + +#. translators: %s: certification issue date +#: templates/partials/certifications.php:98 +#, fuzzy, php-format +msgid "Issued %s" +msgstr "Uitgegeven op %s" + +#. translators: %s: certification expiry date +#: templates/partials/certifications.php:102 +#, fuzzy, php-format +msgid "Expires %s" +msgstr "Verloopt op %s" + +#: templates/partials/certifications.php:114 +msgid "Download report" +msgstr "" + +#: templates/partials/certifications.php:115 +msgid "View documentation" +msgstr "" + +#: templates/partials/chat-budget-exhausted.php:18 +msgid "Ask AI is taking a breather" +msgstr "" + +#: templates/partials/chat-budget-exhausted.php:19 +msgid "" +"We've hit the daily question limit. Chat will be back soon — in the " +"meantime, you can still browse the full trust center." +msgstr "" + +#: templates/partials/chat-budget-exhausted.php:22 +msgid "Browse policies" +msgstr "" + +#: templates/partials/chat-budget-exhausted.php:29 +msgid "Contact us" +msgstr "" + +#. translators: %s: company name +#: templates/partials/chat-empty-state.php:29 +#, fuzzy, php-format +msgid "Ask about %s's security and compliance" +msgstr "Vraag ons over de beveiliging en compliance van %s" + +#. translators: 1: model identifier, 2: sources summary +#: templates/partials/chat-empty-state.php:37 +#, php-format +msgid "Using model %1$s. Grounded in %2$s." +msgstr "" + +#. translators: %s: sources summary +#: templates/partials/chat-empty-state.php:49 +#, fuzzy, php-format +msgid "Grounded in %s." +msgstr "Gebaseerd op %s." + +#: templates/partials/chat-empty-state.php:60 +msgid "Conversation" +msgstr "" + +#: templates/partials/contact.php:41 +msgid "Data Protection Officer" +msgstr "" + +#: templates/partials/contact.php:48 +msgid "Security Team" +msgstr "" + +#: templates/partials/contact.php:57 +msgid "Contact Form" +msgstr "" + +#: templates/partials/contact.php:59 +msgid "Open the contact form" +msgstr "" + +#: templates/partials/contact.php:66 +msgid "PGP Public Key" +msgstr "" + +#: templates/partials/contact.php:68 +msgid "Download public key" +msgstr "" + +#: templates/partials/contact.php:91 +msgid "Company Registration" +msgstr "" + +#: templates/partials/data-practices.php:35 +#, fuzzy +msgid "What we collect and how we handle your data." +msgstr "Welke gegevens we verzamelen en hoe we ze gebruiken." + +#. translators: %1$d = number, %2$s = category name +#: templates/partials/data-practices.php:73 +#, fuzzy, php-format +msgid "View %1$d more %2$s items" +msgstr "Bekijk nog %1$d %2$s-items" + +#: templates/partials/data-practices.php:110 +#, fuzzy +msgid "Retention" +msgstr "Bewaartermijn" + +#: templates/partials/faq.php:42 +msgid "Frequently Asked Questions" +msgstr "" + +#: templates/partials/faq.php:43 +msgid "Quick answers to the questions we hear most." +msgstr "" + +#: templates/partials/faq.php:57 +msgid "Related:" +msgstr "" + +#. translators: %d: number of active certifications +#: templates/partials/hero.php:31 +#, fuzzy, php-format +msgid "%d Active Certification" +msgid_plural "%d Active Certifications" +msgstr[0] "%d Actieve certificering" +msgstr[1] "%d Actieve certificeringen" + +#: templates/partials/policies.php:27 +#, fuzzy +msgid "Security Policies" +msgstr "Beveiligingsbeleid" + +#: templates/partials/policies.php:28 +#, fuzzy +msgid "" +"Our published security and compliance policies are regularly reviewed and " +"updated." +msgstr "" +"Ons gepubliceerde beveiligings- en compliancebeleid wordt regelmatig herzien " +"en bijgewerkt." + +#: templates/partials/policies.php:57 +#, fuzzy +msgid "Last Updated" +msgstr "Laatst bijgewerkt" + +#: templates/partials/policies.php:89 templates/partials/policy-single.php:116 +msgid "Framework citations" +msgstr "" + +#. translators: %s: policy version number +#: templates/partials/policies.php:99 templates/partials/policy-single.php:94 +#, fuzzy, php-format +msgid "v%s" +msgstr "v%s" + +#. translators: %s: human-readable file size +#: templates/partials/policies.php:106 templates/partials/policy-single.php:130 +#, php-format +msgid "Download PDF (%s)" +msgstr "" + +#: templates/partials/policies.php:108 templates/partials/policy-single.php:132 +#, fuzzy +msgid "Download PDF" +msgstr "Download PDF" + +#. translators: %s: policy title +#: templates/partials/policies.php:116 +#, fuzzy, php-format +msgid "View %s" +msgstr "%s bekijken" + +#. translators: %1$s: version number, %2$s: link to current version +#: templates/partials/policy-single.php:63 +#, fuzzy, php-format +msgid "You are viewing version %1$s. %2$s" +msgstr "Je bekijkt versie %1$s. %2$s" + +#: templates/partials/policy-single.php:65 +#: templates/partials/policy-single.php:78 +#, fuzzy +msgid "View current version" +msgstr "Huidige versie bekijken" + +#. translators: %1$s: effective date, %2$s: link to previous version +#: templates/partials/policy-single.php:75 +#, fuzzy, php-format +msgid "This version takes effect on %1$s. %2$s" +msgstr "Deze versie gaat in op %1$s. %2$s" + +#. translators: %s: policy effective date +#: templates/partials/policy-single.php:105 +#, php-format +msgid "Effective %s" +msgstr "" + +#. translators: %d: number of policy versions +#: templates/partials/policy-single.php:150 +#, fuzzy, php-format +msgid "Version history (%d)" +msgstr "Versiegeschiedenis (%d)" + +#. translators: %d: version number +#: templates/partials/policy-single.php:166 +#, fuzzy, php-format +msgid "v%d" +msgstr "v%d" + +#: templates/partials/subprocessors.php:21 +#, fuzzy +msgid "" +"Third-party services that process data on our behalf, along with their " +"purposes and data handling agreements." +msgstr "" +"Externe diensten die namens ons gegevens verwerken, met hun doel en " +"verwerkersovereenkomsten." + +#: templates/partials/subprocessors.php:36 +#, fuzzy +msgid "Name" +msgstr "Naam" + +#: templates/partials/subprocessors.php:53 +#: templates/partials/subprocessors.php:57 +#, fuzzy +msgid "more" +msgstr "meer" + +#: templates/partials/subprocessors.php:64 +#, fuzzy +msgid "Signed" +msgstr "Ondertekend" + +#: templates/trust-center.php:21 +#, fuzzy +msgid "Transparency and security you can trust." +msgstr "Transparante beveiliging om op te vertrouwen." + +#: templates/trust-center.php:196 +msgid "Trust center content is being prepared. Check back soon." +msgstr "" + +#. translators: %s: company name +#: templates/trust-center.php:212 +#, fuzzy, php-format +msgid "© %1$s %2$s. All rights reserved." +msgstr "© %1$s %2$s. Alle rechten voorbehouden." + +#: templates/trust-center.php:220 +msgid "Powered by Open Trust Center" +msgstr "" + +#, fuzzy +#~ msgid "OpenTrust" +#~ msgstr "OpenTrust" + +#, fuzzy +#~ msgid "Email" +#~ msgstr "E-mail" + +#, fuzzy +#~ msgid "Company" +#~ msgstr "Bedrijf" + +#, fuzzy +#~ msgid "Active" +#~ msgstr "Actief" + +#, fuzzy +#~ msgid "In Progress" +#~ msgstr "In aanvraag" + +#, fuzzy +#~ msgid "Please enter a valid email address." +#~ msgstr "Voer een geldig e-mailadres in." + +#, fuzzy +#~ msgid "Please check your inbox and click the confirmation link." +#~ msgstr "Controleer je inbox en klik op de bevestigingslink." + +#, fuzzy, php-format +#~ msgid "Hi %s," +#~ msgstr "Hoi %s," + +#, fuzzy, php-format +#~ msgid "[%1$s] Policy updated: %2$s" +#~ msgstr "[%1$s] Beleid bijgewerkt: %2$s" + +#, fuzzy, php-format +#~ msgid "Confirm your subscription to %s Trust Center updates" +#~ msgstr "Bevestig je aanmelding voor Trust Center-updates van %s" + +#, fuzzy +#~ msgid "Invalid request." +#~ msgstr "Ongeldig verzoek." + +#, fuzzy +#~ msgid "Please complete the security check." +#~ msgstr "Voltooi de beveiligingscontrole." + +#, fuzzy +#~ msgid "Please select at least one category." +#~ msgstr "Selecteer ten minste één categorie." + +#, fuzzy +#~ msgid "Share" +#~ msgstr "Delen" + +#, fuzzy +#~ msgid "Link copied" +#~ msgstr "Link gekopieerd" + +#, fuzzy, php-format +#~ msgid "" +#~ "Thank you for subscribing to trust center updates from %s. Please confirm " +#~ "your subscription by clicking the button below." +#~ msgstr "" +#~ "Bedankt dat je je hebt aangemeld voor Trust Center-updates van %s. " +#~ "Bevestig je aanmelding door op de knop hieronder te klikken." + +#, fuzzy, php-format +#~ msgid "%1$s has updated the following policy on our trust center:" +#~ msgstr "%1$s heeft het volgende beleid bijgewerkt op het Trust Center:" + +#, fuzzy +#~ msgid "Unsubscribe" +#~ msgstr "Uitschrijven" + +#, fuzzy, php-format +#~ msgid "Powered by %1$s. Grounded in %2$s." +#~ msgstr "Mogelijk gemaakt door %1$s. Gebaseerd op %2$s." + +#, fuzzy, php-format +#~ msgid "Effective: %s" +#~ msgstr "Ingangsdatum: %s" + +#, fuzzy, php-format +#~ msgid "Updated: %s" +#~ msgstr "Bijgewerkt: %s" + +#, fuzzy +#~ msgid "Subscribe to updates" +#~ msgstr "Abonneer op updates" + +#, fuzzy, php-format +#~ msgid "Get email notifications when %s updates their trust center." +#~ msgstr "Ontvang e-mailmeldingen zodra %s hun Trust Center bijwerkt." + +#, fuzzy +#~ msgid "Email address" +#~ msgstr "E-mailadres" + +#, fuzzy +#~ msgid "Subscribe" +#~ msgstr "Abonneren" + +#, fuzzy, php-format +#~ msgid "Manage notifications for %s." +#~ msgstr "Beheer meldingen voor %s." diff --git a/languages/opentrust.pot b/languages/open-trust-center-by-ettic.pot similarity index 64% rename from languages/opentrust.pot rename to languages/open-trust-center-by-ettic.pot index fc091a9..e6e399b 100644 --- a/languages/opentrust.pot +++ b/languages/open-trust-center-by-ettic.pot @@ -2,777 +2,776 @@ # This file is distributed under the GPL-2.0-or-later. msgid "" msgstr "" -"Project-Id-Version: OpenTrust 1.1.1\n" -"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/opentrust\n" +"Project-Id-Version: Open Trust Center by Ettic 1.2.0\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/open-trust-center-by-ettic\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2026-05-14T14:04:32+00:00\n" +"POT-Creation-Date: 2026-05-21T20:40:20+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" -"X-Domain: opentrust\n" +"X-Domain: open-trust-center-by-ettic\n" #. Plugin Name of the plugin -#: opentrust.php -#: includes/class-opentrust-admin.php:56 -#: includes/class-opentrust-admin.php:57 -msgid "OpenTrust" +#: open-trust-center-by-ettic.php +#: includes/class-ettic-otc-admin.php:56 +msgid "Open Trust Center by Ettic" msgstr "" #. Plugin URI of the plugin -#: opentrust.php -msgid "https://plugins.ettic.nl/opentrust" +#: open-trust-center-by-ettic.php +msgid "https://plugins.ettic.nl/open-trust-center-by-ettic" msgstr "" #. Description of the plugin -#: opentrust.php +#: open-trust-center-by-ettic.php msgid "A self-hosted, open-source trust center for publishing security policies, subprocessors, certifications, and data practices." msgstr "" #. Author of the plugin -#: opentrust.php +#: open-trust-center-by-ettic.php msgid "Ettic" msgstr "" #. Author URI of the plugin -#: opentrust.php +#: open-trust-center-by-ettic.php msgid "https://plugins.ettic.nl" msgstr "" -#: includes/class-opentrust-admin-ai.php:78 +#: includes/class-ettic-otc-admin-ai.php:78 msgid "Heads up: citation fidelity is not guaranteed on your active provider." msgstr "" #. translators: %s: provider label, e.g. OpenAI -#: includes/class-opentrust-admin-ai.php:83 +#: includes/class-ettic-otc-admin-ai.php:83 #, php-format msgid "You are currently using %s. Only Anthropic uses a structural Citations API — every other provider relies on prompted citation tags the model can ignore or fabricate. For a published trust center, switch to Anthropic below." msgstr "" -#: includes/class-opentrust-admin-ai.php:94 -msgid "OpenTrust uses Anthropic Claude with the native Citations API to answer visitor questions about your trust center. Every claim the assistant makes is tied to an exact quote from one of your published documents — so no policy text is invented and nothing is paraphrased into something you did not actually publish." +#: includes/class-ettic-otc-admin-ai.php:94 +msgid "Open Trust Center uses Anthropic Claude with the native Citations API to answer visitor questions about your trust center. Every claim the assistant makes is tied to an exact quote from one of your published documents — so no policy text is invented and nothing is paraphrased into something you did not actually publish." msgstr "" -#: includes/class-opentrust-admin-ai.php:101 +#: includes/class-ettic-otc-admin-ai.php:101 msgid "Why Anthropic, and not OpenAI or any other provider?" msgstr "" -#: includes/class-opentrust-admin-ai.php:106 +#: includes/class-ettic-otc-admin-ai.php:106 msgid "A trust center is a compliance surface. If the assistant invents a security commitment you never made, that is not a UX papercut — it is a misrepresentation of your security posture, and your customers and auditors will hold you to it." msgstr "" -#: includes/class-opentrust-admin-ai.php:114 +#: includes/class-ettic-otc-admin-ai.php:114 msgid "Anthropic is the only major provider that exposes a structural Citations API. Documents are sent as typed blocks and the model emits citations as first-class events containing the exact source document and the exact quoted text. The model literally cannot return a citation for text that is not in your source documents." msgstr "" -#: includes/class-opentrust-admin-ai.php:120 +#: includes/class-ettic-otc-admin-ai.php:120 msgid "Every other provider (including OpenAI and any model accessed via OpenRouter) relies on prompted citation tags that we parse out of the answer after the fact. That works most of the time, but the model can ignore the instructions, make up document IDs, or attach a citation to a sentence it actually hallucinated. We support these providers as an escape hatch for organizations that genuinely cannot use Anthropic for procurement or data-residency reasons — but we very, very strongly recommend you do not run a public trust center on them." msgstr "" #. translators: %d is the number of policies missing AI summaries. -#: includes/class-opentrust-admin-ai.php:159 +#: includes/class-ettic-otc-admin-ai.php:159 #, php-format msgid "%d policy is missing an AI summary." msgid_plural "%d policies are missing AI summaries." msgstr[0] "" msgstr[1] "" -#: includes/class-opentrust-admin-ai.php:170 +#: includes/class-ettic-otc-admin-ai.php:170 msgid "Generate them now so the assistant can route questions accurately." msgstr "" -#: includes/class-opentrust-admin-ai.php:175 +#: includes/class-ettic-otc-admin-ai.php:175 msgid "Generate now" msgstr "" -#: includes/class-opentrust-admin-ai.php:199 +#: includes/class-ettic-otc-admin-ai.php:199 msgid "Choose a provider and add your key" msgstr "" -#: includes/class-opentrust-admin-ai.php:211 +#: includes/class-ettic-otc-admin-ai.php:211 msgid "Step 1 — Connect Anthropic" msgstr "" -#: includes/class-opentrust-admin-ai.php:217 +#: includes/class-ettic-otc-admin-ai.php:217 msgid "Advanced: use a different provider (not recommended)" msgstr "" -#: includes/class-opentrust-admin-ai.php:220 +#: includes/class-ettic-otc-admin-ai.php:220 msgid "These providers cannot guarantee citation fidelity." msgstr "" -#: includes/class-opentrust-admin-ai.php:222 +#: includes/class-ettic-otc-admin-ai.php:222 msgid "OpenAI and OpenRouter rely on prompted [[cite:document-id]] tags that we parse out of the answer after generation. The model can ignore the instruction, invent document IDs, or attach a citation to a sentence it actually hallucinated. We cannot detect when this happens." msgstr "" -#: includes/class-opentrust-admin-ai.php:225 +#: includes/class-ettic-otc-admin-ai.php:225 msgid "Do not use these providers for a published trust center" msgstr "" -#: includes/class-opentrust-admin-ai.php:226 +#: includes/class-ettic-otc-admin-ai.php:226 msgid "unless your organization genuinely cannot use Anthropic for procurement, contractual, or data-residency reasons. Inaccurate claims about your security posture are a real compliance risk." msgstr "" -#: includes/class-opentrust-admin-ai.php:265 +#: includes/class-ettic-otc-admin-ai.php:265 msgid "Required for citation fidelity" msgstr "" -#: includes/class-opentrust-admin-ai.php:271 +#: includes/class-ettic-otc-admin-ai.php:271 msgid "Uses Claude with the native Citations API. Every quote the assistant attributes to one of your documents is structurally guaranteed to come from that document." msgstr "" #. translators: %s: provider name (e.g. Anthropic) -#: includes/class-opentrust-admin-ai.php:279 +#: includes/class-ettic-otc-admin-ai.php:279 #, php-format msgid "Get a %s API key" msgstr "" -#: includes/class-opentrust-admin-ai.php:292 +#: includes/class-ettic-otc-admin-ai.php:292 msgid "Remove the saved key for this provider?" msgstr "" -#: includes/class-opentrust-admin-ai.php:293 +#: includes/class-ettic-otc-admin-ai.php:293 msgid "Replace key" msgstr "" #. translators: %s: provider name (e.g. Anthropic) -#: includes/class-opentrust-admin-ai.php:303 +#: includes/class-ettic-otc-admin-ai.php:303 #, php-format msgid "Paste your %s API key…" msgstr "" -#: includes/class-opentrust-admin-ai.php:307 +#: includes/class-ettic-otc-admin-ai.php:307 msgid "Validate & save" msgstr "" -#: includes/class-opentrust-admin-ai.php:346 +#: includes/class-ettic-otc-admin-ai.php:346 msgid "Step 2 — Pick a model and tune defaults" msgstr "" -#: includes/class-opentrust-admin-ai.php:359 +#: includes/class-ettic-otc-admin-ai.php:359 msgid "Active model" msgstr "" -#: includes/class-opentrust-admin-ai.php:363 +#: includes/class-ettic-otc-admin-ai.php:363 msgid "No cached models found. Use Refresh to re-fetch the model list." msgstr "" -#: includes/class-opentrust-admin-ai.php:372 +#: includes/class-ettic-otc-admin-ai.php:372 msgid "(unavailable)" msgstr "" -#: includes/class-opentrust-admin-ai.php:376 +#: includes/class-ettic-otc-admin-ai.php:376 msgid "Recommended" msgstr "" -#: includes/class-opentrust-admin-ai.php:387 +#: includes/class-ettic-otc-admin-ai.php:387 msgid "Model unavailable" msgstr "" -#: includes/class-opentrust-admin-ai.php:392 +#: includes/class-ettic-otc-admin-ai.php:392 msgid "Refresh models" msgstr "" #. translators: %s: human-readable time difference (e.g. "5 minutes") -#: includes/class-opentrust-admin-ai.php:402 +#: includes/class-ettic-otc-admin-ai.php:402 #, php-format msgid "Model list cached %s ago." msgstr "" -#: includes/class-opentrust-admin-ai.php:410 +#: includes/class-ettic-otc-admin-ai.php:410 msgid "Daily token budget" msgstr "" -#: includes/class-opentrust-admin-ai.php:413 +#: includes/class-ettic-otc-admin-ai.php:413 msgid "Hard cap per site per day. Default 500,000 tokens (~$12/day at Sonnet 4.5 rates)." msgstr "" -#: includes/class-opentrust-admin-ai.php:417 +#: includes/class-ettic-otc-admin-ai.php:417 msgid "Monthly token budget" msgstr "" -#: includes/class-opentrust-admin-ai.php:420 +#: includes/class-ettic-otc-admin-ai.php:420 msgid "Hard cap per site per month. Default 10,000,000 tokens." msgstr "" -#: includes/class-opentrust-admin-ai.php:424 +#: includes/class-ettic-otc-admin-ai.php:424 msgid "Rate limit — per IP" msgstr "" -#: includes/class-opentrust-admin-ai.php:426 +#: includes/class-ettic-otc-admin-ai.php:426 msgid "messages per minute" msgstr "" -#: includes/class-opentrust-admin-ai.php:430 +#: includes/class-ettic-otc-admin-ai.php:430 msgid "Rate limit — per session" msgstr "" -#: includes/class-opentrust-admin-ai.php:432 +#: includes/class-ettic-otc-admin-ai.php:432 msgid "messages per hour" msgstr "" -#: includes/class-opentrust-admin-ai.php:436 +#: includes/class-ettic-otc-admin-ai.php:436 msgid "Max message length" msgstr "" -#: includes/class-opentrust-admin-ai.php:438 +#: includes/class-ettic-otc-admin-ai.php:438 msgid "characters" msgstr "" -#: includes/class-opentrust-admin-ai.php:443 +#: includes/class-ettic-otc-admin-ai.php:443 msgid "Refuse-to-answer contact URL" msgstr "" -#: includes/class-opentrust-admin-ai.php:446 +#: includes/class-ettic-otc-admin-ai.php:446 msgid "When the AI cannot confidently answer a question, it links here. Leave blank to use the trust center home." msgstr "" -#: includes/class-opentrust-admin-ai.php:451 +#: includes/class-ettic-otc-admin-ai.php:451 msgid "Visitor display" msgstr "" -#: includes/class-opentrust-admin-ai.php:455 +#: includes/class-ettic-otc-admin-ai.php:455 msgid "Show the active model name under the chat input" msgstr "" -#: includes/class-opentrust-admin-ai.php:461 +#: includes/class-ettic-otc-admin-ai.php:461 msgid "Analytics logging" msgstr "" -#: includes/class-opentrust-admin-ai.php:465 +#: includes/class-ettic-otc-admin-ai.php:465 msgid "Log anonymized visitor questions for admin review (90-day auto-purge, no PII)" msgstr "" -#: includes/class-opentrust-admin-ai.php:471 +#: includes/class-ettic-otc-admin-ai.php:471 msgid "Improve answer quality" msgstr "" -#: includes/class-opentrust-admin-ai.php:475 +#: includes/class-ettic-otc-admin-ai.php:475 msgid "Generate AI summaries of each policy" msgstr "" -#: includes/class-opentrust-admin-ai.php:478 +#: includes/class-ettic-otc-admin-ai.php:478 msgid "When on, the AI generates a 2–3 sentence summary of each published policy and stores it for routing decisions. Improves answers on questions like \"What's your data deletion policy?\" that don't match a title literally. Cost is roughly $0.05–$0.10 per 50 policies, lifetime — pennies per edit afterward. Uses your configured AI key." msgstr "" -#: includes/class-opentrust-admin-ai.php:487 +#: includes/class-ettic-otc-admin-ai.php:487 msgid "Oversized policies" msgstr "" -#: includes/class-opentrust-admin-ai.php:491 +#: includes/class-ettic-otc-admin-ai.php:491 msgid "The following policies are large enough that the AI will receive only a truncated version when retrieving them. Consider splitting them into shorter documents:" msgstr "" #. translators: 1: policy title, 2: token count. -#: includes/class-opentrust-admin-ai.php:498 +#: includes/class-ettic-otc-admin-ai.php:498 #, php-format msgid "%1$s (~%2$s tokens)" msgstr "" -#: includes/class-opentrust-admin-ai.php:512 +#: includes/class-ettic-otc-admin-ai.php:512 msgid "Advanced — Turnstile anti-abuse" msgstr "" -#: includes/class-opentrust-admin-ai.php:514 +#: includes/class-ettic-otc-admin-ai.php:514 msgid "Cloudflare Turnstile is optional but recommended for public sites. It challenges suspicious visitors on the first message of each session. You need a free Cloudflare account to get site/secret keys." msgstr "" -#: includes/class-opentrust-admin-ai.php:518 +#: includes/class-ettic-otc-admin-ai.php:518 msgid "Enable Turnstile for chat" msgstr "" -#: includes/class-opentrust-admin-ai.php:522 +#: includes/class-ettic-otc-admin-ai.php:522 msgid "Require Turnstile verification on first chat message" msgstr "" -#: includes/class-opentrust-admin-ai.php:527 +#: includes/class-ettic-otc-admin-ai.php:527 msgid "Turnstile Site Key" msgstr "" -#: includes/class-opentrust-admin-ai.php:530 +#: includes/class-ettic-otc-admin-ai.php:530 msgid "Public site key from your Cloudflare Turnstile widget." msgstr "" -#: includes/class-opentrust-admin-ai.php:534 +#: includes/class-ettic-otc-admin-ai.php:534 msgid "Turnstile Secret Key" msgstr "" -#: includes/class-opentrust-admin-ai.php:537 +#: includes/class-ettic-otc-admin-ai.php:537 msgid "Enter secret key…" msgstr "" -#: includes/class-opentrust-admin-ai.php:539 +#: includes/class-ettic-otc-admin-ai.php:539 msgid "Key saved" msgstr "" -#: includes/class-opentrust-admin-ai.php:541 +#: includes/class-ettic-otc-admin-ai.php:541 msgid "Secret key from Cloudflare Turnstile. Stored server-side — never exposed to the frontend." msgstr "" -#: includes/class-opentrust-admin-ai.php:546 +#: includes/class-ettic-otc-admin-ai.php:546 msgid "Save AI settings" msgstr "" -#: includes/class-opentrust-admin-ai.php:657 -#: includes/class-opentrust-admin-ai.php:714 -#: includes/class-opentrust-admin-ai.php:751 -#: includes/class-opentrust-admin-ai.php:796 -#: includes/class-opentrust-admin-questions.php:223 -#: includes/class-opentrust-admin-questions.php:264 -#: includes/class-opentrust-admin-questions.php:281 -#: includes/class-opentrust-admin-tools.php:565 +#: includes/class-ettic-otc-admin-ai.php:657 +#: includes/class-ettic-otc-admin-ai.php:714 +#: includes/class-ettic-otc-admin-ai.php:751 +#: includes/class-ettic-otc-admin-ai.php:796 +#: includes/class-ettic-otc-admin-questions.php:250 +#: includes/class-ettic-otc-admin-questions.php:291 +#: includes/class-ettic-otc-admin-questions.php:308 +#: includes/class-ettic-otc-admin-tools.php:565 msgid "You do not have permission to perform this action." msgstr "" -#: includes/class-opentrust-admin-ai.php:666 -#: includes/class-opentrust-admin-ai.php:722 -#: includes/class-opentrust-admin-ai.php:758 +#: includes/class-ettic-otc-admin-ai.php:666 +#: includes/class-ettic-otc-admin-ai.php:722 +#: includes/class-ettic-otc-admin-ai.php:758 msgid "Unknown provider." msgstr "" -#: includes/class-opentrust-admin-ai.php:670 +#: includes/class-ettic-otc-admin-ai.php:670 msgid "API key cannot be empty." msgstr "" -#: includes/class-opentrust-admin-ai.php:677 +#: includes/class-ettic-otc-admin-ai.php:677 msgid "Validation failed." msgstr "" #. translators: 1: provider label, 2: provider error message -#: includes/class-opentrust-admin-ai.php:679 +#: includes/class-ettic-otc-admin-ai.php:679 #, php-format msgid "%1$s rejected the key: %2$s" msgstr "" #. translators: 1: provider label, 2: number of models -#: includes/class-opentrust-admin-ai.php:707 +#: includes/class-ettic-otc-admin-ai.php:707 #, php-format msgid "%1$s key validated. Found %2$d model(s)." msgstr "" -#: includes/class-opentrust-admin-ai.php:745 +#: includes/class-ettic-otc-admin-ai.php:745 msgid "Key removed." msgstr "" -#: includes/class-opentrust-admin-ai.php:764 +#: includes/class-ettic-otc-admin-ai.php:764 msgid "No key on file for this provider." msgstr "" -#: includes/class-opentrust-admin-ai.php:770 +#: includes/class-ettic-otc-admin-ai.php:770 msgid "Refresh failed." msgstr "" #. translators: %s: error message from the provider -#: includes/class-opentrust-admin-ai.php:772 +#: includes/class-ettic-otc-admin-ai.php:772 #, php-format msgid "Refresh failed: %s" msgstr "" #. translators: %d: number of models -#: includes/class-opentrust-admin-ai.php:782 +#: includes/class-ettic-otc-admin-ai.php:782 #, php-format msgid "Model list refreshed. Found %d model(s)." msgstr "" #. translators: %d is the number of policies enqueued for summary generation. -#: includes/class-opentrust-admin-ai.php:812 +#: includes/class-ettic-otc-admin-ai.php:812 #, php-format msgid "Queued %d policy for AI summary generation. Summaries will appear over the next minute." msgid_plural "Queued %d policies for AI summary generation. Summaries will appear over the next few minutes." msgstr[0] "" msgstr[1] "" -#: includes/class-opentrust-admin-ai.php:820 +#: includes/class-ettic-otc-admin-ai.php:820 msgid "All policies already have up-to-date AI summaries." msgstr "" -#: includes/class-opentrust-admin-questions.php:87 +#: includes/class-ettic-otc-admin-questions.php:89 msgid "AI Questions" msgstr "" -#: includes/class-opentrust-admin-questions.php:90 +#: includes/class-ettic-otc-admin-questions.php:92 msgid "Questions visitors have asked your trust center chat. Identifiers are hashed and rows auto-purge after 90 days." msgstr "" -#: includes/class-opentrust-admin-questions.php:96 +#: includes/class-ettic-otc-admin-questions.php:98 msgid "Logging is ON" msgstr "" -#: includes/class-opentrust-admin-questions.php:98 +#: includes/class-ettic-otc-admin-questions.php:100 msgid "Logging is OFF" msgstr "" #. translators: %d: number of questions -#: includes/class-opentrust-admin-questions.php:104 +#: includes/class-ettic-otc-admin-questions.php:106 #, php-format msgid "%d question logged in the last 90 days" msgid_plural "%d questions logged in the last 90 days" msgstr[0] "" msgstr[1] "" -#: includes/class-opentrust-admin-questions.php:108 +#: includes/class-ettic-otc-admin-questions.php:110 msgid "Toggle visitor question logging?" msgstr "" -#: includes/class-opentrust-admin-questions.php:109 +#: includes/class-ettic-otc-admin-questions.php:111 msgid "Disable logging" msgstr "" -#: includes/class-opentrust-admin-questions.php:109 +#: includes/class-ettic-otc-admin-questions.php:111 msgid "Enable logging" msgstr "" -#: includes/class-opentrust-admin-questions.php:117 +#: includes/class-ettic-otc-admin-questions.php:119 msgid "Search" msgstr "" -#: includes/class-opentrust-admin-questions.php:118 +#: includes/class-ettic-otc-admin-questions.php:120 msgid "Search questions…" msgstr "" -#: includes/class-opentrust-admin-questions.php:121 -#: includes/class-opentrust-admin-questions.php:148 +#: includes/class-ettic-otc-admin-questions.php:123 +#: includes/class-ettic-otc-admin-questions.php:150 msgid "Model" msgstr "" -#: includes/class-opentrust-admin-questions.php:123 +#: includes/class-ettic-otc-admin-questions.php:125 msgid "Any" msgstr "" -#: includes/class-opentrust-admin-questions.php:130 +#: includes/class-ettic-otc-admin-questions.php:132 msgid "From" msgstr "" -#: includes/class-opentrust-admin-questions.php:134 +#: includes/class-ettic-otc-admin-questions.php:136 msgid "To" msgstr "" -#: includes/class-opentrust-admin-questions.php:137 +#: includes/class-ettic-otc-admin-questions.php:139 msgid "Filter" msgstr "" -#: includes/class-opentrust-admin-questions.php:138 +#: includes/class-ettic-otc-admin-questions.php:140 msgid "Reset" msgstr "" -#: includes/class-opentrust-admin-questions.php:139 +#: includes/class-ettic-otc-admin-questions.php:141 msgid "Download CSV" msgstr "" -#: includes/class-opentrust-admin-questions.php:146 -#: includes/class-opentrust-version.php:161 +#: includes/class-ettic-otc-admin-questions.php:148 +#: includes/class-ettic-otc-version.php:161 msgid "Date" msgstr "" -#: includes/class-opentrust-admin-questions.php:147 +#: includes/class-ettic-otc-admin-questions.php:149 msgid "Question" msgstr "" -#: includes/class-opentrust-admin-questions.php:149 +#: includes/class-ettic-otc-admin-questions.php:151 msgid "Cites" msgstr "" -#: includes/class-opentrust-admin-questions.php:150 +#: includes/class-ettic-otc-admin-questions.php:152 msgid "Tokens" msgstr "" -#: includes/class-opentrust-admin-questions.php:151 +#: includes/class-ettic-otc-admin-questions.php:153 msgid "Latency" msgstr "" -#: includes/class-opentrust-admin-questions.php:156 +#: includes/class-ettic-otc-admin-questions.php:158 msgid "No questions logged yet." msgstr "" -#: includes/class-opentrust-admin-questions.php:165 +#: includes/class-ettic-otc-admin-questions.php:167 msgid "REFUSED" msgstr "" -#: includes/class-opentrust-admin-questions.php:209 +#: includes/class-ettic-otc-admin-questions.php:216 msgid "Danger zone" msgstr "" -#: includes/class-opentrust-admin-questions.php:210 +#: includes/class-ettic-otc-admin-questions.php:217 msgid "Permanently delete all logged questions? This cannot be undone." msgstr "" -#: includes/class-opentrust-admin-questions.php:211 +#: includes/class-ettic-otc-admin-questions.php:218 msgid "Clear entire question log" msgstr "" -#: includes/class-opentrust-admin-questions.php:272 +#: includes/class-ettic-otc-admin-questions.php:299 msgid "Question log cleared." msgstr "" -#: includes/class-opentrust-admin-questions.php:291 +#: includes/class-ettic-otc-admin-questions.php:318 msgid "Logging enabled." msgstr "" -#: includes/class-opentrust-admin-questions.php:291 +#: includes/class-ettic-otc-admin-questions.php:318 msgid "Logging disabled." msgstr "" -#: includes/class-opentrust-admin-review.php:81 +#: includes/class-ettic-otc-admin-review.php:81 msgid "review on WordPress.org" msgstr "" #. translators: %s: link to the WordPress.org reviews page -#: includes/class-opentrust-admin-review.php:89 +#: includes/class-ettic-otc-admin-review.php:89 #, php-format -msgid "OpenTrust is built and maintained in the open. If it is helping your team, a %s keeps the project moving." +msgid "Open Trust Center by Ettic is built and maintained in the open. If it is helping your team, a %s keeps the project moving." msgstr "" -#: includes/class-opentrust-admin-review.php:115 +#: includes/class-ettic-otc-admin-review.php:115 msgid "Your trust center is up and running." msgstr "" -#: includes/class-opentrust-admin-review.php:116 -msgid "OpenTrust is fully open-source with no paid tier — reviews on WordPress.org are how the project gets seen. If it has earned a kind word, we would be grateful." +#: includes/class-ettic-otc-admin-review.php:116 +msgid "Open Trust Center by Ettic is fully open-source with no paid tier — reviews on WordPress.org are how the project gets seen. If it has earned a kind word, we would be grateful." msgstr "" -#: includes/class-opentrust-admin-review.php:120 +#: includes/class-ettic-otc-admin-review.php:120 msgid "Leave a review" msgstr "" -#: includes/class-opentrust-admin-review.php:123 +#: includes/class-ettic-otc-admin-review.php:123 msgid "Already did, thanks" msgstr "" -#: includes/class-opentrust-admin-review.php:126 +#: includes/class-ettic-otc-admin-review.php:126 msgid "Not now" msgstr "" -#: includes/class-opentrust-admin-review.php:168 +#: includes/class-ettic-otc-admin-review.php:168 msgid "You do not have permission to dismiss this notice." msgstr "" -#: includes/class-opentrust-admin-settings.php:55 +#: includes/class-ettic-otc-admin-settings.php:55 msgid "General Settings" msgstr "" -#: includes/class-opentrust-admin-settings.php:60 +#: includes/class-ettic-otc-admin-settings.php:60 msgid "Endpoint Slug" msgstr "" -#: includes/class-opentrust-admin-settings.php:61 +#: includes/class-ettic-otc-admin-settings.php:61 msgid "The URL path for your trust center (e.g., \"trust-center\" = yoursite.com/trust-center/)." msgstr "" -#: includes/class-opentrust-admin-settings.php:64 +#: includes/class-ettic-otc-admin-settings.php:64 msgid "Page Title" msgstr "" -#: includes/class-opentrust-admin-settings.php:66 +#: includes/class-ettic-otc-admin-settings.php:66 msgid "Company Name" msgstr "" -#: includes/class-opentrust-admin-settings.php:68 +#: includes/class-ettic-otc-admin-settings.php:68 msgid "Tagline" msgstr "" -#: includes/class-opentrust-admin-settings.php:69 +#: includes/class-ettic-otc-admin-settings.php:69 msgid "A short description displayed below the company name in the hero section." msgstr "" -#: includes/class-opentrust-admin-settings.php:75 +#: includes/class-ettic-otc-admin-settings.php:75 msgid "Branding" msgstr "" -#: includes/class-opentrust-admin-settings.php:80 +#: includes/class-ettic-otc-admin-settings.php:80 msgid "Logo" msgstr "" -#: includes/class-opentrust-admin-settings.php:81 +#: includes/class-ettic-otc-admin-settings.php:81 msgid "AI Avatar" msgstr "" -#: includes/class-opentrust-admin-settings.php:83 +#: includes/class-ettic-otc-admin-settings.php:83 msgid "Accent Color" msgstr "" -#: includes/class-opentrust-admin-settings.php:84 +#: includes/class-ettic-otc-admin-settings.php:84 msgid "Used for buttons, links, and highlights. Choose a color that matches your brand." msgstr "" -#: includes/class-opentrust-admin-settings.php:87 +#: includes/class-ettic-otc-admin-settings.php:87 msgid "Credit Link" msgstr "" -#: includes/class-opentrust-admin-settings.php:92 +#: includes/class-ettic-otc-admin-settings.php:92 msgid "Visible Sections" msgstr "" -#: includes/class-opentrust-admin-settings.php:93 +#: includes/class-ettic-otc-admin-settings.php:93 msgid "Choose which sections to display on the trust center." msgstr "" -#: includes/class-opentrust-admin-settings.php:97 +#: includes/class-ettic-otc-admin-settings.php:97 msgid "Sections" msgstr "" -#: includes/class-opentrust-admin-settings.php:103 +#: includes/class-ettic-otc-admin-settings.php:103 #: templates/partials/contact.php:111 msgid "Get in touch" msgstr "" -#: includes/class-opentrust-admin-settings.php:104 +#: includes/class-ettic-otc-admin-settings.php:104 msgid "Publish a dark-accent \"Get in touch\" block on the trust center. Every field is optional — the block only appears if at least one is filled in." msgstr "" -#: includes/class-opentrust-admin-settings.php:108 +#: includes/class-ettic-otc-admin-settings.php:108 msgid "Company Description" msgstr "" -#: includes/class-opentrust-admin-settings.php:109 +#: includes/class-ettic-otc-admin-settings.php:109 msgid "Two or three sentences describing what the company does. Rendered under the \"Get in touch\" section title." msgstr "" -#: includes/class-opentrust-admin-settings.php:112 +#: includes/class-ettic-otc-admin-settings.php:112 msgid "DPO Name" msgstr "" -#: includes/class-opentrust-admin-settings.php:113 +#: includes/class-ettic-otc-admin-settings.php:113 msgid "Data Protection Officer name. Required under GDPR for many organisations." msgstr "" -#: includes/class-opentrust-admin-settings.php:116 +#: includes/class-ettic-otc-admin-settings.php:116 msgid "DPO Email" msgstr "" -#: includes/class-opentrust-admin-settings.php:117 +#: includes/class-ettic-otc-admin-settings.php:117 msgid "Dedicated DPO mailbox. Rendered as a mailto link." msgstr "" -#: includes/class-opentrust-admin-settings.php:120 +#: includes/class-ettic-otc-admin-settings.php:120 msgid "Security Contact Email" msgstr "" -#: includes/class-opentrust-admin-settings.php:121 +#: includes/class-ettic-otc-admin-settings.php:121 msgid "For vulnerability reports and security questions. Often separate from the DPO." msgstr "" -#: includes/class-opentrust-admin-settings.php:124 +#: includes/class-ettic-otc-admin-settings.php:124 msgid "Contact Form URL" msgstr "" -#: includes/class-opentrust-admin-settings.php:125 +#: includes/class-ettic-otc-admin-settings.php:125 msgid "Optional link to a gated contact form." msgstr "" -#: includes/class-opentrust-admin-settings.php:128 +#: includes/class-ettic-otc-admin-settings.php:128 #: templates/partials/contact.php:84 msgid "Mailing Address" msgstr "" -#: includes/class-opentrust-admin-settings.php:129 +#: includes/class-ettic-otc-admin-settings.php:129 msgid "Postal address for formal GDPR / legal notices." msgstr "" -#: includes/class-opentrust-admin-settings.php:132 +#: includes/class-ettic-otc-admin-settings.php:132 msgid "PGP Public Key URL" msgstr "" -#: includes/class-opentrust-admin-settings.php:133 +#: includes/class-ettic-otc-admin-settings.php:133 msgid "Optional link to your security team's PGP public key." msgstr "" -#: includes/class-opentrust-admin-settings.php:136 +#: includes/class-ettic-otc-admin-settings.php:136 msgid "Company Registration Number" msgstr "" -#: includes/class-opentrust-admin-settings.php:137 +#: includes/class-ettic-otc-admin-settings.php:137 msgid "KvK (NL), Companies House (UK), Handelsregister (DE), EIN (US), or equivalent business registration." msgstr "" -#: includes/class-opentrust-admin-settings.php:140 +#: includes/class-ettic-otc-admin-settings.php:140 #: templates/partials/contact.php:100 msgid "VAT / Tax ID" msgstr "" -#: includes/class-opentrust-admin-settings.php:141 +#: includes/class-ettic-otc-admin-settings.php:141 msgid "VAT number, sales-tax ID, or equivalent international tax identifier." msgstr "" -#: includes/class-opentrust-admin-settings.php:222 +#: includes/class-ettic-otc-admin-settings.php:222 msgid "Low contrast on white backgrounds" msgstr "" -#: includes/class-opentrust-admin-settings.php:223 +#: includes/class-ettic-otc-admin-settings.php:223 msgid "Using your exact color on white backgrounds" msgstr "" -#: includes/class-opentrust-admin-settings.php:226 -msgid "Your chosen color is too light for buttons, links, and borders on white sections. On those surfaces OpenTrust will use a darker, on-brand variant:" +#: includes/class-ettic-otc-admin-settings.php:226 +msgid "Your chosen color is too light for buttons, links, and borders on white sections. On those surfaces Open Trust Center will use a darker, on-brand variant:" msgstr "" -#: includes/class-opentrust-admin-settings.php:229 +#: includes/class-ettic-otc-admin-settings.php:229 msgid "You've chosen to keep your exact color on white backgrounds. Buttons, links, and borders in those sections may be hard to read." msgstr "" -#: includes/class-opentrust-admin-settings.php:241 +#: includes/class-ettic-otc-admin-settings.php:241 msgid "The hero and navigation still use your exact color." msgstr "" -#: includes/class-opentrust-admin-settings.php:252 +#: includes/class-ettic-otc-admin-settings.php:252 msgid "Use my exact color anyway — skip the contrast adjustment." msgstr "" -#: includes/class-opentrust-admin-settings.php:265 -#: includes/class-opentrust-cpt.php:479 +#: includes/class-ettic-otc-admin-settings.php:265 +#: includes/class-ettic-otc-cpt.php:510 msgid "Select Logo" msgstr "" -#: includes/class-opentrust-admin-settings.php:266 +#: includes/class-ettic-otc-admin-settings.php:266 msgid "Used in the hero and sticky nav. A white version is recommended — it sits on a dark background." msgstr "" -#: includes/class-opentrust-admin-settings.php:273 +#: includes/class-ettic-otc-admin-settings.php:273 msgid "Select Avatar" msgstr "" -#: includes/class-opentrust-admin-settings.php:274 +#: includes/class-ettic-otc-admin-settings.php:274 msgid "Square image used as the avatar on AI chat responses. Use a colored background with a light or dark favicon or logo on top." msgstr "" -#: includes/class-opentrust-admin-settings.php:289 -#: includes/class-opentrust-cpt.php:480 -#: includes/class-opentrust-cpt.php:492 -#: includes/class-opentrust-cpt.php:623 -#: includes/class-opentrust-cpt.php:639 -#: includes/class-opentrust-cpt.php:725 -#: includes/class-opentrust-cpt.php:763 +#: includes/class-ettic-otc-admin-settings.php:289 +#: includes/class-ettic-otc-cpt.php:511 +#: includes/class-ettic-otc-cpt.php:523 +#: includes/class-ettic-otc-cpt.php:654 +#: includes/class-ettic-otc-cpt.php:670 +#: includes/class-ettic-otc-cpt.php:756 +#: includes/class-ettic-otc-cpt.php:794 msgid "Remove" msgstr "" -#: includes/class-opentrust-admin-settings.php:301 -msgid "Show a \"Powered by OpenTrust\" credit in the trust center footer." +#: includes/class-ettic-otc-admin-settings.php:301 +msgid "Show a \"Powered by Open Trust Center by Ettic\" credit in the trust center footer." msgstr "" -#: includes/class-opentrust-admin-settings.php:305 +#: includes/class-ettic-otc-admin-settings.php:305 msgid "Off by default. Public credits are opt-in." msgstr "" -#: includes/class-opentrust-admin-settings.php:314 +#: includes/class-ettic-otc-admin-settings.php:314 #: templates/partials/certifications.php:22 msgid "Certifications & Compliance" msgstr "" -#: includes/class-opentrust-admin-settings.php:315 -#: includes/class-opentrust-admin-tools.php:592 -#: includes/class-opentrust-cpt.php:240 -#: includes/class-opentrust-cpt.php:250 +#: includes/class-ettic-otc-admin-settings.php:315 +#: includes/class-ettic-otc-admin-tools.php:592 +#: includes/class-ettic-otc-cpt.php:271 +#: includes/class-ettic-otc-cpt.php:281 #: templates/chat.php:45 #: templates/partials/hero.php:55 #: templates/trust-center.php:107 msgid "Policies" msgstr "" -#: includes/class-opentrust-admin-settings.php:316 -#: includes/class-opentrust-admin-tools.php:594 -#: includes/class-opentrust-cpt.php:307 -#: includes/class-opentrust-cpt.php:316 +#: includes/class-ettic-otc-admin-settings.php:316 +#: includes/class-ettic-otc-admin-tools.php:594 +#: includes/class-ettic-otc-cpt.php:338 +#: includes/class-ettic-otc-cpt.php:347 #: templates/chat.php:47 #: templates/partials/hero.php:61 #: templates/partials/subprocessors.php:20 @@ -780,1008 +779,1014 @@ msgstr "" msgid "Subprocessors" msgstr "" -#: includes/class-opentrust-admin-settings.php:317 -#: includes/class-opentrust-admin-tools.php:595 -#: includes/class-opentrust-cpt.php:340 -#: includes/class-opentrust-cpt.php:349 +#: includes/class-ettic-otc-admin-settings.php:317 +#: includes/class-ettic-otc-admin-tools.php:595 +#: includes/class-ettic-otc-cpt.php:371 +#: includes/class-ettic-otc-cpt.php:380 #: templates/chat.php:48 #: templates/partials/data-practices.php:34 #: templates/trust-center.php:110 msgid "Data Practices" msgstr "" -#: includes/class-opentrust-admin-settings.php:318 -#: includes/class-opentrust-admin-tools.php:596 -#: includes/class-opentrust-cpt.php:373 -#: includes/class-opentrust-cpt.php:383 +#: includes/class-ettic-otc-admin-settings.php:318 +#: includes/class-ettic-otc-admin-tools.php:596 +#: includes/class-ettic-otc-cpt.php:404 +#: includes/class-ettic-otc-cpt.php:414 msgid "FAQs" msgstr "" -#: includes/class-opentrust-admin-settings.php:319 +#: includes/class-ettic-otc-admin-settings.php:319 msgid "Contact & DPO" msgstr "" -#: includes/class-opentrust-admin-settings.php:586 +#: includes/class-ettic-otc-admin-settings.php:586 msgid "View Trust Center" msgstr "" -#: includes/class-opentrust-admin-settings.php:593 -#: includes/class-opentrust-render.php:515 +#: includes/class-ettic-otc-admin-settings.php:593 +#: includes/class-ettic-otc-render.php:515 msgid "General" msgstr "" -#: includes/class-opentrust-admin-settings.php:597 +#: includes/class-ettic-otc-admin-settings.php:597 #: templates/chat.php:60 #: templates/trust-center.php:111 msgid "Contact" msgstr "" -#: includes/class-opentrust-admin-settings.php:601 +#: includes/class-ettic-otc-admin-settings.php:601 msgid "AI Chat" msgstr "" -#: includes/class-opentrust-admin-settings.php:604 +#: includes/class-ettic-otc-admin-settings.php:604 msgid "Live" msgstr "" -#: includes/class-opentrust-admin-settings.php:610 +#: includes/class-ettic-otc-admin-settings.php:610 msgid "Import & Export" msgstr "" -#: includes/class-opentrust-admin-tools.php:155 +#: includes/class-ettic-otc-admin-tools.php:155 msgid "Move trust center content and settings between sites, or seed a fresh install from another. API keys and the Turnstile secret are never included — re-enter them on the destination." msgstr "" -#: includes/class-opentrust-admin-tools.php:163 +#: includes/class-ettic-otc-admin-tools.php:163 msgid "Export" msgstr "" -#: includes/class-opentrust-admin-tools.php:167 +#: includes/class-ettic-otc-admin-tools.php:167 msgid "Import" msgstr "" -#: includes/class-opentrust-admin-tools.php:183 +#: includes/class-ettic-otc-admin-tools.php:183 msgid "What to export" msgstr "" -#: includes/class-opentrust-admin-tools.php:186 +#: includes/class-ettic-otc-admin-tools.php:186 msgid "Content (CPTs + bundled media)" msgstr "" -#: includes/class-opentrust-admin-tools.php:190 +#: includes/class-ettic-otc-admin-tools.php:190 msgid "Settings only" msgstr "" -#: includes/class-opentrust-admin-tools.php:195 +#: includes/class-ettic-otc-admin-tools.php:195 msgid "Content selection" msgstr "" -#: includes/class-opentrust-admin-tools.php:225 +#: includes/class-ettic-otc-admin-tools.php:225 msgid "Bundle attached PDFs and images" msgstr "" -#: includes/class-opentrust-admin-tools.php:229 +#: includes/class-ettic-otc-admin-tools.php:229 msgid "Download export" msgstr "" -#: includes/class-opentrust-admin-tools.php:243 +#: includes/class-ettic-otc-admin-tools.php:243 msgid "Only upload your own exports." msgstr "" -#: includes/class-opentrust-admin-tools.php:244 +#: includes/class-ettic-otc-admin-tools.php:244 msgid "Export files contain your trust-center content and may include sensitive material. Never import a file you received from someone else." msgstr "" -#: includes/class-opentrust-admin-tools.php:248 +#: includes/class-ettic-otc-admin-tools.php:248 msgid "Upload export file" msgstr "" #. translators: %d: max upload size in MB -#: includes/class-opentrust-admin-tools.php:253 +#: includes/class-ettic-otc-admin-tools.php:253 #, php-format msgid "Max %d MB" msgstr "" -#: includes/class-opentrust-admin-tools.php:259 +#: includes/class-ettic-otc-admin-tools.php:259 msgid "On conflict" msgstr "" -#: includes/class-opentrust-admin-tools.php:262 +#: includes/class-ettic-otc-admin-tools.php:262 msgid "Skip — keep existing records untouched" msgstr "" -#: includes/class-opentrust-admin-tools.php:266 +#: includes/class-ettic-otc-admin-tools.php:266 msgid "Overwrite — replace existing records" msgstr "" -#: includes/class-opentrust-admin-tools.php:270 +#: includes/class-ettic-otc-admin-tools.php:270 msgid "Create new — duplicate with a -import suffix" msgstr "" -#: includes/class-opentrust-admin-tools.php:275 +#: includes/class-ettic-otc-admin-tools.php:275 msgid "Preview import" msgstr "" -#: includes/class-opentrust-admin-tools.php:292 +#: includes/class-ettic-otc-admin-tools.php:292 msgid "Import preview" msgstr "" -#: includes/class-opentrust-admin-tools.php:296 +#: includes/class-ettic-otc-admin-tools.php:296 msgid "Import blocked:" msgstr "" -#: includes/class-opentrust-admin-tools.php:317 +#: includes/class-ettic-otc-admin-tools.php:317 msgid "Settings export — current values will be merged with imported values. Excluded keys (encrypted secrets, salt, server-controlled flags) are kept as-is." msgstr "" #. translators: %1$d: create count, %2$d: update count, %3$d: skip count -#: includes/class-opentrust-admin-tools.php:324 +#: includes/class-ettic-otc-admin-tools.php:324 #, php-format msgid "Will create %1$d, update %2$d, skip %3$d." msgstr "" -#: includes/class-opentrust-admin-tools.php:338 +#: includes/class-ettic-otc-admin-tools.php:338 msgid "Title" msgstr "" -#: includes/class-opentrust-admin-tools.php:339 +#: includes/class-ettic-otc-admin-tools.php:339 msgid "Action" msgstr "" -#: includes/class-opentrust-admin-tools.php:340 +#: includes/class-ettic-otc-admin-tools.php:340 msgid "UUID" msgstr "" -#: includes/class-opentrust-admin-tools.php:361 +#: includes/class-ettic-otc-admin-tools.php:361 msgid "Confirm and import" msgstr "" -#: includes/class-opentrust-admin-tools.php:363 +#: includes/class-ettic-otc-admin-tools.php:363 msgid "Cancel" msgstr "" -#: includes/class-opentrust-admin-tools.php:385 +#: includes/class-ettic-otc-admin-tools.php:385 msgid "Pick at least one record to export." msgstr "" -#: includes/class-opentrust-admin-tools.php:415 +#: includes/class-ettic-otc-admin-tools.php:415 msgid "No file uploaded." msgstr "" -#: includes/class-opentrust-admin-tools.php:421 +#: includes/class-ettic-otc-admin-tools.php:421 msgid "Upload exceeds size limit." msgstr "" -#: includes/class-opentrust-admin-tools.php:514 +#: includes/class-ettic-otc-admin-tools.php:514 msgid "Import cancelled." msgstr "" -#: includes/class-opentrust-admin-tools.php:519 +#: includes/class-ettic-otc-admin-tools.php:519 msgid "Import has unresolved errors." msgstr "" #. translators: %1$d: created, %2$d: updated, %3$d: skipped -#: includes/class-opentrust-admin-tools.php:539 +#: includes/class-ettic-otc-admin-tools.php:539 #, php-format msgid "Imported: %1$d created, %2$d updated, %3$d skipped." msgstr "" -#: includes/class-opentrust-admin-tools.php:547 +#: includes/class-ettic-otc-admin-tools.php:547 msgid "Errors:" msgstr "" -#: includes/class-opentrust-admin-tools.php:593 -#: includes/class-opentrust-cpt.php:274 -#: includes/class-opentrust-cpt.php:283 +#: includes/class-ettic-otc-admin-tools.php:593 +#: includes/class-ettic-otc-cpt.php:305 +#: includes/class-ettic-otc-cpt.php:314 #: templates/chat.php:46 #: templates/partials/hero.php:49 #: templates/trust-center.php:108 msgid "Certifications" msgstr "" -#: includes/class-opentrust-admin.php:67 -#: includes/class-opentrust-admin.php:68 +#: includes/class-ettic-otc-admin.php:57 +#: templates/partials/hero.php:38 +#: templates/trust-center.php:19 +msgid "Trust Center" +msgstr "" + +#: includes/class-ettic-otc-admin.php:67 +#: includes/class-ettic-otc-admin.php:68 msgid "Settings" msgstr "" -#: includes/class-opentrust-admin.php:79 -#: includes/class-opentrust-admin.php:80 +#: includes/class-ettic-otc-admin.php:79 +#: includes/class-ettic-otc-admin.php:80 msgid "Questions" msgstr "" -#: includes/class-opentrust-admin.php:158 +#: includes/class-ettic-otc-admin.php:158 msgid "Select Badge Image" msgstr "" -#: includes/class-opentrust-admin.php:159 +#: includes/class-ettic-otc-admin.php:159 msgid "Use as Badge" msgstr "" -#: includes/class-opentrust-admin.php:160 +#: includes/class-ettic-otc-admin.php:160 msgid "Select Proof Artifact" msgstr "" -#: includes/class-opentrust-admin.php:161 +#: includes/class-ettic-otc-admin.php:161 msgid "Use This File" msgstr "" -#: includes/class-opentrust-admin.php:162 -#: includes/class-opentrust-cpt.php:491 +#: includes/class-ettic-otc-admin.php:162 +#: includes/class-ettic-otc-cpt.php:522 msgid "Upload File" msgstr "" -#: includes/class-opentrust-admin.php:163 -#: includes/class-opentrust-cpt.php:491 +#: includes/class-ettic-otc-admin.php:163 +#: includes/class-ettic-otc-cpt.php:522 msgid "Replace File" msgstr "" -#: includes/class-opentrust-admin.php:178 +#: includes/class-ettic-otc-admin.php:178 msgid "No match in catalog, just keep typing to add manually." msgstr "" -#: includes/class-opentrust-admin.php:179 +#: includes/class-ettic-otc-admin.php:179 msgid "Auto-filled from catalog, you may want to verify this." msgstr "" -#: includes/class-opentrust-admin.php:180 +#: includes/class-ettic-otc-admin.php:180 msgid "Auto-filled template, please verify this matches how you use this service." msgstr "" -#: includes/class-opentrust-admin.php:181 +#: includes/class-ettic-otc-admin.php:181 msgid "click to autofill" msgstr "" -#: includes/class-opentrust-admin.php:182 +#: includes/class-ettic-otc-admin.php:182 msgid "Catalog suggestions" msgstr "" -#: includes/class-opentrust-admin.php:228 -msgid "OpenTrust requires pretty permalinks." +#: includes/class-ettic-otc-admin.php:228 +msgid "Open Trust Center requires pretty permalinks." msgstr "" #. translators: %s: link to Settings → Permalinks -#: includes/class-opentrust-admin.php:232 +#: includes/class-ettic-otc-admin.php:232 #, php-format msgid "Your site is using \"Plain\" permalinks. Please go to %s and choose any other option (Post name is the WordPress default)." msgstr "" -#: includes/class-opentrust-admin.php:233 +#: includes/class-ettic-otc-admin.php:233 msgid "Settings → Permalinks" msgstr "" -#: includes/class-opentrust-admin.php:238 -msgid "Without pretty permalinks, every link OpenTrust generates returns 404 — including the trust center page itself. Visitors will not be able to reach your policies, certifications, or chat." +#: includes/class-ettic-otc-admin.php:238 +msgid "Without pretty permalinks, every link Open Trust Center generates returns 404 — including the trust center page itself. Visitors will not be able to reach your policies, certifications, or chat." msgstr "" -#: includes/class-opentrust-admin.php:242 +#: includes/class-ettic-otc-admin.php:242 msgid "Read-only fallback if you cannot change permalinks" msgstr "" -#: includes/class-opentrust-admin.php:246 +#: includes/class-ettic-otc-admin.php:246 msgid "You can preview the trust center via raw query-string URLs:" msgstr "" -#: includes/class-opentrust-admin.php:254 +#: includes/class-ettic-otc-admin.php:254 msgid "This is for testing only." msgstr "" -#: includes/class-opentrust-admin.php:255 +#: includes/class-ettic-otc-admin.php:255 msgid "Switching to pretty permalinks is the only supported configuration." msgstr "" -#: includes/class-opentrust-chat.php:112 +#: includes/class-ettic-otc-chat.php:112 msgid "Invalid nonce — refresh the page and try again." msgstr "" -#: includes/class-opentrust-chat.php:140 +#: includes/class-ettic-otc-chat.php:140 msgid "Please complete the anti-abuse challenge and try again." msgstr "" -#: includes/class-opentrust-chat.php:152 +#: includes/class-ettic-otc-chat.php:152 msgid "You are sending messages too fast. Please wait a moment and try again." msgstr "" -#: includes/class-opentrust-chat.php:163 +#: includes/class-ettic-otc-chat.php:163 msgid "You have reached the per-session message limit. Please wait a bit and try again." msgstr "" -#: includes/class-opentrust-chat.php:183 +#: includes/class-ettic-otc-chat.php:183 msgid "AI chat is not configured on this site." msgstr "" -#: includes/class-opentrust-chat.php:192 +#: includes/class-ettic-otc-chat.php:192 msgid "Configured provider is unknown." msgstr "" -#: includes/class-opentrust-chat.php:201 +#: includes/class-ettic-otc-chat.php:201 msgid "No API key stored for the configured provider." msgstr "" -#: includes/class-opentrust-chat.php:214 +#: includes/class-ettic-otc-chat.php:214 msgid "Your message is empty." msgstr "" -#: includes/class-opentrust-chat.php:232 +#: includes/class-ettic-otc-chat.php:232 msgid "The daily chat budget for this site has been reached. Please try again later." msgstr "" -#: includes/class-opentrust-chat.php:394 +#: includes/class-ettic-otc-chat.php:394 msgid "Chat provider failed unexpectedly." msgstr "" #. translators: %s is the tool name (get_document or search_documents). -#: includes/class-opentrust-chat.php:722 +#: includes/class-ettic-otc-chat.php:722 #, php-format msgid "You already called %s with the same arguments earlier in this conversation. Pick a different document id from the index, or rephrase the search query with different keywords." msgstr "" -#: includes/class-opentrust-chat.php:732 +#: includes/class-ettic-otc-chat.php:732 msgid "Document id is required." msgstr "" #. translators: %s is the requested document id. -#: includes/class-opentrust-chat.php:741 +#: includes/class-ettic-otc-chat.php:741 #, php-format msgid "No document with id \"%s\". Pick one of the ids listed in the corpus index above." msgstr "" -#: includes/class-opentrust-chat.php:749 +#: includes/class-ettic-otc-chat.php:749 msgid "Search query is empty." msgstr "" -#: includes/class-opentrust-chat.php:752 +#: includes/class-ettic-otc-chat.php:752 msgid "Search index unavailable." msgstr "" #. translators: %s is the search query. -#: includes/class-opentrust-chat.php:758 +#: includes/class-ettic-otc-chat.php:758 #, php-format msgid "No documents matched \"%s\". Try broader keywords or pick a document id from the corpus index above." msgstr "" -#: includes/class-opentrust-chat.php:770 +#: includes/class-ettic-otc-chat.php:770 msgid "Search ranking returned no usable results." msgstr "" #. translators: %s is the unknown tool name. -#: includes/class-opentrust-chat.php:775 +#: includes/class-ettic-otc-chat.php:775 #, php-format msgid "Unknown tool: %s" msgstr "" -#: includes/class-opentrust-cpt.php:221 +#: includes/class-ettic-otc-cpt.php:252 msgid "Pick from the catalog or type your own subprocessor name" msgstr "" -#: includes/class-opentrust-cpt.php:224 +#: includes/class-ettic-otc-cpt.php:255 msgid "Pick from the catalog or type your own, e.g. Analytics or Transactional Email" msgstr "" -#: includes/class-opentrust-cpt.php:227 +#: includes/class-ettic-otc-cpt.php:258 msgid "Pick from the catalog or type your own, e.g. SOC 2 Type II or ISO 27001" msgstr "" -#: includes/class-opentrust-cpt.php:241 +#: includes/class-ettic-otc-cpt.php:272 #: templates/partials/policies.php:54 msgid "Policy" msgstr "" -#: includes/class-opentrust-cpt.php:242 +#: includes/class-ettic-otc-cpt.php:273 msgid "Add Policy" msgstr "" -#: includes/class-opentrust-cpt.php:243 +#: includes/class-ettic-otc-cpt.php:274 msgid "Add New Policy" msgstr "" -#: includes/class-opentrust-cpt.php:244 +#: includes/class-ettic-otc-cpt.php:275 msgid "Edit Policy" msgstr "" -#: includes/class-opentrust-cpt.php:245 +#: includes/class-ettic-otc-cpt.php:276 msgid "New Policy" msgstr "" -#: includes/class-opentrust-cpt.php:246 +#: includes/class-ettic-otc-cpt.php:277 msgid "View Policy" msgstr "" -#: includes/class-opentrust-cpt.php:247 +#: includes/class-ettic-otc-cpt.php:278 msgid "Search Policies" msgstr "" -#: includes/class-opentrust-cpt.php:248 +#: includes/class-ettic-otc-cpt.php:279 msgid "No policies found." msgstr "" -#: includes/class-opentrust-cpt.php:249 +#: includes/class-ettic-otc-cpt.php:280 msgid "No policies in trash." msgstr "" -#: includes/class-opentrust-cpt.php:275 +#: includes/class-ettic-otc-cpt.php:306 msgid "Certification" msgstr "" -#: includes/class-opentrust-cpt.php:276 +#: includes/class-ettic-otc-cpt.php:307 msgid "Add Certification" msgstr "" -#: includes/class-opentrust-cpt.php:277 +#: includes/class-ettic-otc-cpt.php:308 msgid "Add New Certification" msgstr "" -#: includes/class-opentrust-cpt.php:278 +#: includes/class-ettic-otc-cpt.php:309 msgid "Edit Certification" msgstr "" -#: includes/class-opentrust-cpt.php:279 +#: includes/class-ettic-otc-cpt.php:310 msgid "New Certification" msgstr "" -#: includes/class-opentrust-cpt.php:280 +#: includes/class-ettic-otc-cpt.php:311 msgid "Search Certifications" msgstr "" -#: includes/class-opentrust-cpt.php:281 +#: includes/class-ettic-otc-cpt.php:312 msgid "No certifications found." msgstr "" -#: includes/class-opentrust-cpt.php:282 +#: includes/class-ettic-otc-cpt.php:313 msgid "No certifications in trash." msgstr "" -#: includes/class-opentrust-cpt.php:308 +#: includes/class-ettic-otc-cpt.php:339 msgid "Subprocessor" msgstr "" -#: includes/class-opentrust-cpt.php:309 +#: includes/class-ettic-otc-cpt.php:340 msgid "Add Subprocessor" msgstr "" -#: includes/class-opentrust-cpt.php:310 +#: includes/class-ettic-otc-cpt.php:341 msgid "Add New Subprocessor" msgstr "" -#: includes/class-opentrust-cpt.php:311 +#: includes/class-ettic-otc-cpt.php:342 msgid "Edit Subprocessor" msgstr "" -#: includes/class-opentrust-cpt.php:312 +#: includes/class-ettic-otc-cpt.php:343 msgid "New Subprocessor" msgstr "" -#: includes/class-opentrust-cpt.php:313 +#: includes/class-ettic-otc-cpt.php:344 msgid "Search Subprocessors" msgstr "" -#: includes/class-opentrust-cpt.php:314 +#: includes/class-ettic-otc-cpt.php:345 msgid "No subprocessors found." msgstr "" -#: includes/class-opentrust-cpt.php:315 +#: includes/class-ettic-otc-cpt.php:346 msgid "No subprocessors in trash." msgstr "" -#: includes/class-opentrust-cpt.php:341 +#: includes/class-ettic-otc-cpt.php:372 msgid "Data Practice" msgstr "" -#: includes/class-opentrust-cpt.php:342 +#: includes/class-ettic-otc-cpt.php:373 msgid "Add Data Practice" msgstr "" -#: includes/class-opentrust-cpt.php:343 +#: includes/class-ettic-otc-cpt.php:374 msgid "Add New Data Practice" msgstr "" -#: includes/class-opentrust-cpt.php:344 +#: includes/class-ettic-otc-cpt.php:375 msgid "Edit Data Practice" msgstr "" -#: includes/class-opentrust-cpt.php:345 +#: includes/class-ettic-otc-cpt.php:376 msgid "New Data Practice" msgstr "" -#: includes/class-opentrust-cpt.php:346 +#: includes/class-ettic-otc-cpt.php:377 msgid "Search Data Practices" msgstr "" -#: includes/class-opentrust-cpt.php:347 +#: includes/class-ettic-otc-cpt.php:378 msgid "No data practices found." msgstr "" -#: includes/class-opentrust-cpt.php:348 +#: includes/class-ettic-otc-cpt.php:379 msgid "No data practices in trash." msgstr "" -#: includes/class-opentrust-cpt.php:374 +#: includes/class-ettic-otc-cpt.php:405 #: templates/chat.php:61 #: templates/trust-center.php:112 msgid "FAQ" msgstr "" -#: includes/class-opentrust-cpt.php:375 +#: includes/class-ettic-otc-cpt.php:406 msgid "Add FAQ" msgstr "" -#: includes/class-opentrust-cpt.php:376 +#: includes/class-ettic-otc-cpt.php:407 msgid "Add New FAQ" msgstr "" -#: includes/class-opentrust-cpt.php:377 +#: includes/class-ettic-otc-cpt.php:408 msgid "Edit FAQ" msgstr "" -#: includes/class-opentrust-cpt.php:378 +#: includes/class-ettic-otc-cpt.php:409 msgid "New FAQ" msgstr "" -#: includes/class-opentrust-cpt.php:379 +#: includes/class-ettic-otc-cpt.php:410 msgid "View FAQ" msgstr "" -#: includes/class-opentrust-cpt.php:380 +#: includes/class-ettic-otc-cpt.php:411 msgid "Search FAQs" msgstr "" -#: includes/class-opentrust-cpt.php:381 +#: includes/class-ettic-otc-cpt.php:412 msgid "No FAQs found." msgstr "" -#: includes/class-opentrust-cpt.php:382 +#: includes/class-ettic-otc-cpt.php:413 msgid "No FAQs in trash." msgstr "" -#: includes/class-opentrust-cpt.php:405 +#: includes/class-ettic-otc-cpt.php:436 msgid "Certification Details" msgstr "" -#: includes/class-opentrust-cpt.php:406 +#: includes/class-ettic-otc-cpt.php:437 msgid "Policy Details" msgstr "" -#: includes/class-opentrust-cpt.php:407 +#: includes/class-ettic-otc-cpt.php:438 msgid "Subprocessor Details" msgstr "" -#: includes/class-opentrust-cpt.php:408 +#: includes/class-ettic-otc-cpt.php:439 msgid "Data Practice Details" msgstr "" -#: includes/class-opentrust-cpt.php:409 +#: includes/class-ettic-otc-cpt.php:440 msgid "FAQ Details" msgstr "" -#: includes/class-opentrust-cpt.php:430 +#: includes/class-ettic-otc-cpt.php:461 msgid "Audited certification (issued by a third party)" msgstr "" -#: includes/class-opentrust-cpt.php:431 +#: includes/class-ettic-otc-cpt.php:462 msgid "Self-attested alignment (no external audit)" msgstr "" -#: includes/class-opentrust-cpt.php:435 +#: includes/class-ettic-otc-cpt.php:466 msgid "Active / currently met" msgstr "" -#: includes/class-opentrust-cpt.php:436 +#: includes/class-ettic-otc-cpt.php:467 msgid "In progress" msgstr "" -#: includes/class-opentrust-cpt.php:437 +#: includes/class-ettic-otc-cpt.php:468 msgid "Expired / lapsed" msgstr "" -#: includes/class-opentrust-cpt.php:441 +#: includes/class-ettic-otc-cpt.php:472 msgid "Certification Type" msgstr "" -#: includes/class-opentrust-cpt.php:447 +#: includes/class-ettic-otc-cpt.php:478 msgid "Audited means a third-party issued a formal certificate with dates (SOC 2, ISO 27001, PCI DSS). Self-attested means you adhere to the framework without an external audit — the honest framing for GDPR, CCPA, and most HIPAA posture claims." msgstr "" -#: includes/class-opentrust-cpt.php:451 -#: includes/class-opentrust-cpt.php:1026 +#: includes/class-ettic-otc-cpt.php:482 +#: includes/class-ettic-otc-cpt.php:1057 msgid "Status" msgstr "" -#: includes/class-opentrust-cpt.php:457 +#: includes/class-ettic-otc-cpt.php:488 msgid "\"Active\" for audited means you hold a current certificate. \"Active\" for self-attested means you currently meet the framework. Use \"In progress\" while working toward either." msgstr "" -#: includes/class-opentrust-cpt.php:461 -#: includes/class-opentrust-cpt.php:1025 +#: includes/class-ettic-otc-cpt.php:492 +#: includes/class-ettic-otc-cpt.php:1056 msgid "Issuing Body" msgstr "" -#: includes/class-opentrust-cpt.php:462 +#: includes/class-ettic-otc-cpt.php:493 msgid "e.g., AICPA, BSI Group, Schellman" msgstr "" -#: includes/class-opentrust-cpt.php:466 +#: includes/class-ettic-otc-cpt.php:497 msgid "Issue Date" msgstr "" -#: includes/class-opentrust-cpt.php:471 -#: includes/class-opentrust-cpt.php:1027 +#: includes/class-ettic-otc-cpt.php:502 +#: includes/class-ettic-otc-cpt.php:1058 msgid "Expiry Date" msgstr "" -#: includes/class-opentrust-cpt.php:476 +#: includes/class-ettic-otc-cpt.php:507 msgid "Framework Logo" msgstr "" -#: includes/class-opentrust-cpt.php:481 +#: includes/class-ettic-otc-cpt.php:512 msgid "Use the official framework mark where licensing allows (SOC 2, ISO, GDPR shield). Square images work best at 44×44." msgstr "" -#: includes/class-opentrust-cpt.php:485 +#: includes/class-ettic-otc-cpt.php:516 msgid "Proof Artifact" msgstr "" -#: includes/class-opentrust-cpt.php:488 -#: includes/class-opentrust-cpt.php:635 +#: includes/class-ettic-otc-cpt.php:519 +#: includes/class-ettic-otc-cpt.php:666 msgid "View file" msgstr "" -#: includes/class-opentrust-cpt.php:493 +#: includes/class-ettic-otc-cpt.php:524 msgid "Optional PDF the trust center can link to — e.g. the audit report, certificate, or policy mapping document. Shown as a download button on the card." msgstr "" -#: includes/class-opentrust-cpt.php:497 +#: includes/class-ettic-otc-cpt.php:528 msgid "Scope & Notes" msgstr "" -#: includes/class-opentrust-cpt.php:498 +#: includes/class-ettic-otc-cpt.php:529 msgid "e.g., We process EU personal data under GDPR. Our DPA covers customer data, and we support DSARs within 30 days." msgstr "" -#: includes/class-opentrust-cpt.php:499 +#: includes/class-ettic-otc-cpt.php:530 msgid "Required for self-attested frameworks so the card has meaningful content. One or two sentences on scope, how you meet the framework, or what prospects should know." msgstr "" #. translators: %s: policy version number -#: includes/class-opentrust-cpt.php:554 +#: includes/class-ettic-otc-cpt.php:585 #, php-format msgid "Version %s" msgstr "" -#: includes/class-opentrust-cpt.php:557 +#: includes/class-ettic-otc-cpt.php:588 msgid "Regular saves update the current version. Use the checkbox below to formally publish a new version." msgstr "" -#: includes/class-opentrust-cpt.php:566 +#: includes/class-ettic-otc-cpt.php:597 msgid "Publish as new version" msgstr "" #. translators: %1$d: current version number, %2$d: next version number -#: includes/class-opentrust-cpt.php:571 +#: includes/class-ettic-otc-cpt.php:602 #, php-format msgid "This will save the current content as v%1$d and create v%2$d. Only check this for formal, published changes — not minor edits." msgstr "" -#: includes/class-opentrust-cpt.php:580 +#: includes/class-ettic-otc-cpt.php:611 msgid "What changed?" msgstr "" -#: includes/class-opentrust-cpt.php:583 +#: includes/class-ettic-otc-cpt.php:614 msgid "e.g., Updated data retention from 90 to 60 days" msgstr "" -#: includes/class-opentrust-cpt.php:584 +#: includes/class-ettic-otc-cpt.php:615 msgid "Shown in the public version history." msgstr "" -#: includes/class-opentrust-cpt.php:590 +#: includes/class-ettic-otc-cpt.php:621 msgid "Policy ID" msgstr "" -#: includes/class-opentrust-cpt.php:591 +#: includes/class-ettic-otc-cpt.php:622 msgid "e.g., POL-012" msgstr "" -#: includes/class-opentrust-cpt.php:592 +#: includes/class-ettic-otc-cpt.php:623 msgid "Optional short reference (e.g., POL-012). Shown on the public listing and in security questionnaires." msgstr "" -#: includes/class-opentrust-cpt.php:596 -#: includes/class-opentrust-cpt.php:1065 +#: includes/class-ettic-otc-cpt.php:627 +#: includes/class-ettic-otc-cpt.php:1096 #: templates/partials/policies.php:55 msgid "Category" msgstr "" -#: includes/class-opentrust-cpt.php:605 +#: includes/class-ettic-otc-cpt.php:636 msgid "Effective Date" msgstr "" -#: includes/class-opentrust-cpt.php:610 +#: includes/class-ettic-otc-cpt.php:641 msgid "Next Review Date" msgstr "" -#: includes/class-opentrust-cpt.php:615 +#: includes/class-ettic-otc-cpt.php:646 msgid "Framework Citations" msgstr "" -#: includes/class-opentrust-cpt.php:626 +#: includes/class-ettic-otc-cpt.php:657 msgid "e.g., SOC 2 CC6.1, ISO 27001 A.9.2…" msgstr "" -#: includes/class-opentrust-cpt.php:628 +#: includes/class-ettic-otc-cpt.php:659 msgid "Framework or control references this policy satisfies. Appears as pill badges on the public page." msgstr "" -#: includes/class-opentrust-cpt.php:632 +#: includes/class-ettic-otc-cpt.php:663 msgid "PDF Attachment" msgstr "" -#: includes/class-opentrust-cpt.php:638 +#: includes/class-ettic-otc-cpt.php:669 msgid "Replace PDF" msgstr "" -#: includes/class-opentrust-cpt.php:638 +#: includes/class-ettic-otc-cpt.php:669 msgid "Upload PDF" msgstr "" -#: includes/class-opentrust-cpt.php:640 +#: includes/class-ettic-otc-cpt.php:671 msgid "Upload the signed PDF. Visitors see a \"Download PDF\" button only when a file is attached." msgstr "" -#: includes/class-opentrust-cpt.php:644 -#: includes/class-opentrust-cpt.php:800 +#: includes/class-ettic-otc-cpt.php:675 +#: includes/class-ettic-otc-cpt.php:831 msgid "Sort Order" msgstr "" -#: includes/class-opentrust-cpt.php:646 -#: includes/class-opentrust-cpt.php:802 +#: includes/class-ettic-otc-cpt.php:677 +#: includes/class-ettic-otc-cpt.php:833 msgid "Lower numbers appear first." msgstr "" -#: includes/class-opentrust-cpt.php:663 -#: includes/class-opentrust-cpt.php:734 -#: includes/class-opentrust-cpt.php:1096 +#: includes/class-ettic-otc-cpt.php:694 +#: includes/class-ettic-otc-cpt.php:765 +#: includes/class-ettic-otc-cpt.php:1127 #: templates/partials/data-practices.php:96 #: templates/partials/subprocessors.php:37 msgid "Purpose" msgstr "" -#: includes/class-opentrust-cpt.php:665 +#: includes/class-ettic-otc-cpt.php:696 msgid "What does this subprocessor do for your company?" msgstr "" -#: includes/class-opentrust-cpt.php:669 +#: includes/class-ettic-otc-cpt.php:700 #: templates/partials/subprocessors.php:38 msgid "Data Processed" msgstr "" -#: includes/class-opentrust-cpt.php:671 +#: includes/class-ettic-otc-cpt.php:702 msgid "What types of data does this subprocessor handle?" msgstr "" -#: includes/class-opentrust-cpt.php:675 +#: includes/class-ettic-otc-cpt.php:706 msgid "Country / Location" msgstr "" -#: includes/class-opentrust-cpt.php:676 +#: includes/class-ettic-otc-cpt.php:707 msgid "e.g., United States" msgstr "" -#: includes/class-opentrust-cpt.php:680 +#: includes/class-ettic-otc-cpt.php:711 #: templates/partials/subprocessors.php:41 msgid "Website" msgstr "" -#: includes/class-opentrust-cpt.php:687 +#: includes/class-ettic-otc-cpt.php:718 msgid "DPA Signed" msgstr "" -#: includes/class-opentrust-cpt.php:689 +#: includes/class-ettic-otc-cpt.php:720 msgid "A Data Processing Agreement (DPA) is a contract between you and the subprocessor covering how they handle personal data on your behalf. Check this box once your organization has signed one with this vendor." msgstr "" -#: includes/class-opentrust-cpt.php:719 +#: includes/class-ettic-otc-cpt.php:750 msgid "Data Items Collected" msgstr "" -#: includes/class-opentrust-cpt.php:728 -#: includes/class-opentrust-cpt.php:766 +#: includes/class-ettic-otc-cpt.php:759 +#: includes/class-ettic-otc-cpt.php:797 msgid "Type and press Enter..." msgstr "" -#: includes/class-opentrust-cpt.php:741 +#: includes/class-ettic-otc-cpt.php:772 #: templates/partials/data-practices.php:103 msgid "Legal Basis" msgstr "" -#: includes/class-opentrust-cpt.php:743 +#: includes/class-ettic-otc-cpt.php:774 msgid "— Select —" msgstr "" -#: includes/class-opentrust-cpt.php:750 +#: includes/class-ettic-otc-cpt.php:781 msgid "Retention Period" msgstr "" -#: includes/class-opentrust-cpt.php:751 +#: includes/class-ettic-otc-cpt.php:782 msgid "e.g., 30 days" msgstr "" -#: includes/class-opentrust-cpt.php:757 +#: includes/class-ettic-otc-cpt.php:788 #: templates/partials/data-practices.php:117 msgid "Shared With" msgstr "" -#: includes/class-opentrust-cpt.php:772 +#: includes/class-ettic-otc-cpt.php:803 msgid "Properties" msgstr "" -#: includes/class-opentrust-cpt.php:776 +#: includes/class-ettic-otc-cpt.php:807 msgid "Collected" msgstr "" -#: includes/class-opentrust-cpt.php:780 +#: includes/class-ettic-otc-cpt.php:811 msgid "Stored" msgstr "" -#: includes/class-opentrust-cpt.php:784 +#: includes/class-ettic-otc-cpt.php:815 msgid "Shared with third parties" msgstr "" -#: includes/class-opentrust-cpt.php:788 +#: includes/class-ettic-otc-cpt.php:819 msgid "Sold to third parties" msgstr "" -#: includes/class-opentrust-cpt.php:792 +#: includes/class-ettic-otc-cpt.php:823 msgid "Encrypted" msgstr "" -#: includes/class-opentrust-cpt.php:795 +#: includes/class-ettic-otc-cpt.php:826 msgid "Unchecked means an explicit \"No\". The AI assistant reports these values verbatim to visitors asking questions like \"Do you sell customer data?\"." msgstr "" -#: includes/class-opentrust-cpt.php:1064 +#: includes/class-ettic-otc-cpt.php:1095 #: templates/partials/policies.php:52 msgid "ID" msgstr "" -#: includes/class-opentrust-cpt.php:1066 -#: includes/class-opentrust-version.php:160 +#: includes/class-ettic-otc-cpt.php:1097 +#: includes/class-ettic-otc-version.php:160 #: templates/partials/policies.php:56 msgid "Version" msgstr "" -#: includes/class-opentrust-cpt.php:1067 +#: includes/class-ettic-otc-cpt.php:1098 msgid "PDF" msgstr "" -#: includes/class-opentrust-cpt.php:1097 +#: includes/class-ettic-otc-cpt.php:1128 #: templates/partials/subprocessors.php:39 msgid "Location" msgstr "" -#: includes/class-opentrust-cpt.php:1098 +#: includes/class-ettic-otc-cpt.php:1129 #: templates/partials/subprocessors.php:40 msgid "DPA" msgstr "" -#: includes/class-opentrust-cpt.php:1117 +#: includes/class-ettic-otc-cpt.php:1148 msgid "Data Items" msgstr "" -#: includes/class-opentrust-cpt.php:1118 -#: includes/class-opentrust-cpt.php:1187 +#: includes/class-ettic-otc-cpt.php:1149 +#: includes/class-ettic-otc-cpt.php:1218 msgid "Order" msgstr "" -#: includes/class-opentrust-cpt.php:1147 +#: includes/class-ettic-otc-cpt.php:1178 msgid "Related Policy" msgstr "" -#: includes/class-opentrust-cpt.php:1149 +#: includes/class-ettic-otc-cpt.php:1180 msgid "— None —" msgstr "" -#: includes/class-opentrust-cpt.php:1154 +#: includes/class-ettic-otc-cpt.php:1185 msgid "Optional — link this answer to a published policy for deeper context." msgstr "" -#: includes/class-opentrust-cpt.php:1159 +#: includes/class-ettic-otc-cpt.php:1190 msgid "Sort order:" msgstr "" -#: includes/class-opentrust-cpt.php:1160 +#: includes/class-ettic-otc-cpt.php:1191 msgid "Use the Page Attributes box below (Order field) to control FAQ order. Lower numbers appear first." msgstr "" -#: includes/class-opentrust-io.php:193 +#: includes/class-ettic-otc-io.php:206 msgid "PHP ZipArchive extension is required for export." msgstr "" -#: includes/class-opentrust-io.php:198 +#: includes/class-ettic-otc-io.php:211 msgid "Could not create temp file for export." msgstr "" -#: includes/class-opentrust-io.php:212 +#: includes/class-ettic-otc-io.php:225 msgid "Could not open ZIP for writing." msgstr "" -#: includes/class-opentrust-io.php:240 +#: includes/class-ettic-otc-io.php:257 msgid "Unrecognised export format." msgstr "" #. translators: %1$d: schema version found, %2$d: schema version expected -#: includes/class-opentrust-io.php:246 +#: includes/class-ettic-otc-io.php:263 #, php-format msgid "Schema version mismatch (found %1$d, expected %2$d)." msgstr "" #. translators: %1$s: their version, %2$s: our version -#: includes/class-opentrust-io.php:257 +#: includes/class-ettic-otc-io.php:282 #, php-format msgid "Plugin major version mismatch (export: %1$s, this site: %2$s)." msgstr "" -#: includes/class-opentrust-io.php:396 +#: includes/class-ettic-otc-io.php:422 msgid "PHP ZipArchive extension is required." msgstr "" -#: includes/class-opentrust-io.php:400 +#: includes/class-ettic-otc-io.php:426 msgid "Could not open uploaded archive." msgstr "" -#: includes/class-opentrust-io.php:405 +#: includes/class-ettic-otc-io.php:431 msgid "Archive is missing manifest.json." msgstr "" -#: includes/class-opentrust-io.php:409 +#: includes/class-ettic-otc-io.php:435 msgid "manifest.json could not be parsed." msgstr "" -#: includes/class-opentrust-io.php:697 +#: includes/class-ettic-otc-io.php:726 msgid "Could not reopen archive for media import." msgstr "" #. translators: %s: media path -#: includes/class-opentrust-io.php:712 +#: includes/class-ettic-otc-io.php:741 #, php-format msgid "Bundled media missing during import: %s" msgstr "" #. translators: %s: filename -#: includes/class-opentrust-io.php:732 +#: includes/class-ettic-otc-io.php:761 #, php-format msgid "Could not write attachment file: %s" msgstr "" -#: includes/class-opentrust-io.php:750 +#: includes/class-ettic-otc-io.php:779 msgid "Could not create attachment." msgstr "" -#: includes/class-opentrust-render.php:83 +#: includes/class-ettic-otc-render.php:83 msgid "Session expired. Please reload the page and try again." msgstr "" -#: includes/class-opentrust-render.php:89 +#: includes/class-ettic-otc-render.php:89 msgid "Please enter a question." msgstr "" -#: includes/class-opentrust-render.php:98 +#: includes/class-ettic-otc-render.php:98 msgid "AI chat is not configured." msgstr "" -#: includes/class-opentrust-render.php:270 +#: includes/class-ettic-otc-render.php:270 msgid "Page not found." msgstr "" -#: includes/class-opentrust-render.php:271 +#: includes/class-ettic-otc-render.php:271 #: templates/partials/policy-single.php:54 msgid "Back to Trust Center" msgstr "" -#: includes/class-opentrust-render.php:442 +#: includes/class-ettic-otc-render.php:442 msgid "Updated just now" msgstr "" #. translators: %d: number of minutes since last update -#: includes/class-opentrust-render.php:447 +#: includes/class-ettic-otc-render.php:447 #, php-format msgid "Updated %d minute ago" msgid_plural "Updated %d minutes ago" @@ -1789,7 +1794,7 @@ msgstr[0] "" msgstr[1] "" #. translators: %d: number of hours since last update -#: includes/class-opentrust-render.php:454 +#: includes/class-ettic-otc-render.php:454 #, php-format msgid "Updated %d hour ago" msgid_plural "Updated %d hours ago" @@ -1797,7 +1802,7 @@ msgstr[0] "" msgstr[1] "" #. translators: %d: number of days since last update -#: includes/class-opentrust-render.php:461 +#: includes/class-ettic-otc-render.php:461 #, php-format msgid "Updated %d day ago" msgid_plural "Updated %d days ago" @@ -1806,108 +1811,108 @@ msgstr[1] "" #. translators: %s = formatted date #. translators: %s: policy last updated date -#: includes/class-opentrust-render.php:467 +#: includes/class-ettic-otc-render.php:467 #: templates/partials/policy-single.php:111 #, php-format msgid "Updated %s" msgstr "" -#: includes/class-opentrust-render.php:511 +#: includes/class-ettic-otc-render.php:511 msgid "Security" msgstr "" -#: includes/class-opentrust-render.php:512 +#: includes/class-ettic-otc-render.php:512 msgid "Privacy" msgstr "" -#: includes/class-opentrust-render.php:513 +#: includes/class-ettic-otc-render.php:513 msgid "Compliance" msgstr "" -#: includes/class-opentrust-render.php:514 +#: includes/class-ettic-otc-render.php:514 msgid "Operational" msgstr "" -#: includes/class-opentrust-render.php:526 +#: includes/class-ettic-otc-render.php:526 msgid "Certified" msgstr "" -#: includes/class-opentrust-render.php:527 +#: includes/class-ettic-otc-render.php:527 msgid "In audit" msgstr "" -#: includes/class-opentrust-render.php:528 +#: includes/class-ettic-otc-render.php:528 msgid "Expired" msgstr "" -#: includes/class-opentrust-render.php:537 +#: includes/class-ettic-otc-render.php:537 msgid "Compliant" msgstr "" -#: includes/class-opentrust-render.php:538 +#: includes/class-ettic-otc-render.php:538 msgid "Working toward" msgstr "" -#: includes/class-opentrust-render.php:539 +#: includes/class-ettic-otc-render.php:539 msgid "Lapsed" msgstr "" -#: includes/class-opentrust-render.php:545 +#: includes/class-ettic-otc-render.php:545 msgid "Consent" msgstr "" -#: includes/class-opentrust-render.php:546 +#: includes/class-ettic-otc-render.php:546 msgid "Contractual Necessity" msgstr "" -#: includes/class-opentrust-render.php:547 +#: includes/class-ettic-otc-render.php:547 msgid "Legitimate Interest" msgstr "" -#: includes/class-opentrust-render.php:548 +#: includes/class-ettic-otc-render.php:548 msgid "Legal Obligation" msgstr "" -#: includes/class-opentrust-render.php:549 +#: includes/class-ettic-otc-render.php:549 msgid "Vital Interest" msgstr "" -#: includes/class-opentrust-render.php:550 +#: includes/class-ettic-otc-render.php:550 msgid "Public Interest" msgstr "" -#: includes/class-opentrust-version.php:127 +#: includes/class-ettic-otc-version.php:127 msgid "Version History" msgstr "" -#: includes/class-opentrust-version.php:149 +#: includes/class-ettic-otc-version.php:149 msgid "Current version:" msgstr "" -#: includes/class-opentrust-version.php:152 +#: includes/class-ettic-otc-version.php:152 msgid "Version history will appear after the first update." msgstr "" -#: includes/class-opentrust-version.php:162 +#: includes/class-ettic-otc-version.php:162 #: templates/partials/policies.php:58 msgid "Actions" msgstr "" -#: includes/class-opentrust-version.php:169 +#: includes/class-ettic-otc-version.php:169 #: templates/partials/policy-single.php:174 msgid "Current" msgstr "" -#: includes/class-opentrust-version.php:183 -#: includes/class-opentrust-version.php:184 +#: includes/class-ettic-otc-version.php:183 +#: includes/class-ettic-otc-version.php:184 msgid "View" msgstr "" -#: includes/class-opentrust-version.php:187 +#: includes/class-ettic-otc-version.php:187 msgid "Compare" msgstr "" -#: includes/class-opentrust-version.php:188 +#: includes/class-ettic-otc-version.php:188 msgid "Diff" msgstr "" @@ -1991,170 +1996,170 @@ msgstr "" msgid "A data practice describes a specific way a company collects, uses, stores, or shares information. Each practice usually spells out what data is involved, why it is collected, how long it is kept, and who it is shared with. Grouping data practices by category, such as account data, usage data, or support data, helps customers understand exactly what happens to their information." msgstr "" -#: includes/providers/class-opentrust-chat-provider-anthropic.php:44 -#: includes/providers/class-opentrust-chat-provider.php:46 +#: includes/providers/class-ettic-otc-chat-provider-anthropic.php:44 +#: includes/providers/class-ettic-otc-chat-provider.php:46 msgid "Anthropic" msgstr "" -#: includes/providers/class-opentrust-chat-provider-anthropic.php:63 +#: includes/providers/class-ettic-otc-chat-provider-anthropic.php:63 msgid "No models available — your account may not be authorized." msgstr "" -#: includes/providers/class-opentrust-chat-provider-anthropic.php:127 +#: includes/providers/class-ettic-otc-chat-provider-anthropic.php:127 msgid "Anthropic adapter missing required args." msgstr "" -#: includes/providers/class-opentrust-chat-provider-anthropic.php:219 +#: includes/providers/class-ettic-otc-chat-provider-anthropic.php:219 msgid "Anthropic request failed." msgstr "" -#: includes/providers/class-opentrust-chat-provider-anthropic.php:414 +#: includes/providers/class-ettic-otc-chat-provider-anthropic.php:414 msgid "Searching documents…" msgstr "" -#: includes/providers/class-opentrust-chat-provider-anthropic.php:415 +#: includes/providers/class-ettic-otc-chat-provider-anthropic.php:415 msgid "Reading documents…" msgstr "" -#: includes/providers/class-opentrust-chat-provider-openai.php:66 -#: includes/providers/class-opentrust-chat-provider.php:52 +#: includes/providers/class-ettic-otc-chat-provider-openai.php:66 +#: includes/providers/class-ettic-otc-chat-provider.php:52 msgid "OpenAI" msgstr "" -#: includes/providers/class-opentrust-chat-provider-openai.php:166 +#: includes/providers/class-ettic-otc-chat-provider-openai.php:166 msgid "OpenAI adapter missing required args." msgstr "" -#: includes/providers/class-opentrust-chat-provider-openai.php:253 +#: includes/providers/class-ettic-otc-chat-provider-openai.php:253 msgid "OpenAI request failed." msgstr "" -#: includes/providers/class-opentrust-chat-provider-openrouter.php:38 -#: includes/providers/class-opentrust-chat-provider.php:58 +#: includes/providers/class-ettic-otc-chat-provider-openrouter.php:38 +#: includes/providers/class-ettic-otc-chat-provider.php:58 msgid "OpenRouter" msgstr "" -#: includes/providers/class-opentrust-chat-provider.php:104 +#: includes/providers/class-ettic-otc-chat-provider.php:104 msgid "No chat models available for this key." msgstr "" -#: includes/providers/class-opentrust-chat-provider.php:117 +#: includes/providers/class-ettic-otc-chat-provider.php:117 msgid "API key is empty." msgstr "" -#: includes/providers/class-opentrust-chat-provider.php:123 +#: includes/providers/class-ettic-otc-chat-provider.php:123 msgid "Request failed." msgstr "" -#: includes/providers/class-opentrust-chat-provider.php:268 +#: includes/providers/class-ettic-otc-chat-provider.php:268 msgid "I couldn't find a confident answer in the published trust center documents. Please contact the team for help." msgstr "" #. translators: %s is the document title. -#: includes/providers/class-opentrust-chat-provider.php:342 +#: includes/providers/class-ettic-otc-chat-provider.php:342 #, php-format msgid "Read \"%s\"" msgstr "" #. translators: %s is the document title. -#: includes/providers/class-opentrust-chat-provider.php:344 +#: includes/providers/class-ettic-otc-chat-provider.php:344 #, php-format msgid "Reading \"%s\"" msgstr "" #. translators: %s is the document id. -#: includes/providers/class-opentrust-chat-provider.php:350 +#: includes/providers/class-ettic-otc-chat-provider.php:350 #, php-format msgid "Read %s" msgstr "" #. translators: %s is the document id. -#: includes/providers/class-opentrust-chat-provider.php:352 +#: includes/providers/class-ettic-otc-chat-provider.php:352 #, php-format msgid "Reading %s" msgstr "" -#: includes/providers/class-opentrust-chat-provider.php:354 +#: includes/providers/class-ettic-otc-chat-provider.php:354 msgid "Read a document" msgstr "" -#: includes/providers/class-opentrust-chat-provider.php:354 +#: includes/providers/class-ettic-otc-chat-provider.php:354 msgid "Reading a document" msgstr "" #. translators: %s is the search query. -#: includes/providers/class-opentrust-chat-provider.php:367 +#: includes/providers/class-ettic-otc-chat-provider.php:367 #, php-format msgid "Searched for \"%s\"" msgstr "" #. translators: %s is the search query. -#: includes/providers/class-opentrust-chat-provider.php:369 +#: includes/providers/class-ettic-otc-chat-provider.php:369 #, php-format msgid "Searching for \"%s\"" msgstr "" -#: includes/providers/class-opentrust-chat-provider.php:371 +#: includes/providers/class-ettic-otc-chat-provider.php:371 msgid "Searched documents" msgstr "" -#: includes/providers/class-opentrust-chat-provider.php:371 +#: includes/providers/class-ettic-otc-chat-provider.php:371 msgid "Searching documents" msgstr "" #. translators: %d is the number of documents that were read in parallel. -#: includes/providers/class-opentrust-chat-provider.php:399 +#: includes/providers/class-ettic-otc-chat-provider.php:399 #, php-format msgid "Read %d documents" msgstr "" #. translators: %d is the number of documents being read in parallel. -#: includes/providers/class-opentrust-chat-provider.php:401 +#: includes/providers/class-ettic-otc-chat-provider.php:401 #, php-format msgid "Reading %d documents" msgstr "" #. translators: %d is the number of search queries that were fired in parallel. -#: includes/providers/class-opentrust-chat-provider.php:406 +#: includes/providers/class-ettic-otc-chat-provider.php:406 #, php-format msgid "Ran %d searches" msgstr "" #. translators: %d is the number of search queries fired in parallel. -#: includes/providers/class-opentrust-chat-provider.php:408 +#: includes/providers/class-ettic-otc-chat-provider.php:408 #, php-format msgid "Running %d searches" msgstr "" #. translators: %d is the number of parallel retrieval calls (mixed types). -#: includes/providers/class-opentrust-chat-provider.php:412 +#: includes/providers/class-ettic-otc-chat-provider.php:412 #, php-format msgid "Ran %d retrievals" msgstr "" #. translators: %d is the number of parallel retrieval calls (mixed types). -#: includes/providers/class-opentrust-chat-provider.php:414 +#: includes/providers/class-ettic-otc-chat-provider.php:414 #, php-format msgid "Running %d retrievals" msgstr "" -#: includes/providers/class-opentrust-chat-provider.php:443 -#: includes/providers/class-opentrust-chat-provider.php:462 -#: includes/providers/class-opentrust-chat-provider.php:513 +#: includes/providers/class-ettic-otc-chat-provider.php:443 +#: includes/providers/class-ettic-otc-chat-provider.php:462 +#: includes/providers/class-ettic-otc-chat-provider.php:513 msgid "Refused outbound request to disallowed host." msgstr "" -#: includes/providers/class-opentrust-chat-provider.php:518 +#: includes/providers/class-ettic-otc-chat-provider.php:518 msgid "AI streaming requires the PHP cURL extension." msgstr "" -#: includes/providers/class-opentrust-chat-provider.php:620 +#: includes/providers/class-ettic-otc-chat-provider.php:620 msgid "AI streaming requires the WordPress cURL transport." msgstr "" #. translators: %d: HTTP status code returned by the provider -#: includes/providers/class-opentrust-chat-provider.php:673 -#: includes/providers/class-opentrust-chat-provider.php:728 +#: includes/providers/class-ettic-otc-chat-provider.php:673 +#: includes/providers/class-ettic-otc-chat-provider.php:728 #, php-format msgid "Provider returned HTTP %d" msgstr "" @@ -2464,11 +2469,6 @@ msgid_plural "%d Active Certifications" msgstr[0] "" msgstr[1] "" -#: templates/partials/hero.php:38 -#: templates/trust-center.php:19 -msgid "Trust Center" -msgstr "" - #: templates/partials/policies.php:27 msgid "Security Policies" msgstr "" @@ -2578,5 +2578,5 @@ msgid "© %1$s %2$s. All rights reserved." msgstr "" #: templates/trust-center.php:220 -msgid "Powered by OpenTrust" +msgid "Powered by Open Trust Center" msgstr "" diff --git a/languages/opentrust-nl_NL.mo b/languages/opentrust-nl_NL.mo deleted file mode 100644 index de08d27..0000000 Binary files a/languages/opentrust-nl_NL.mo and /dev/null differ diff --git a/languages/opentrust-nl_NL.po b/languages/opentrust-nl_NL.po deleted file mode 100644 index bc09e36..0000000 --- a/languages/opentrust-nl_NL.po +++ /dev/null @@ -1,2496 +0,0 @@ -# Copyright (C) 2026 OpenTrust. Licensed under GPL-2.0-or-later. -msgid "" -msgstr "" -"Project-Id-Version: OpenTrust 1.0.0\n" -"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/opentrust\n" -"Last-Translator: OpenTrust bundled starter \n" -"Language-Team: Dutch (Netherlands)\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2026-04-14T22:04:10+00:00\n" -"PO-Revision-Date: 2026-04-14 21:55+0000\n" -"Language: nl_NL\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: WP-CLI 2.12.0\n" -"X-Domain: opentrust\n" - -#. Plugin Name of the plugin -#. Author of the plugin -#: opentrust.php -#: includes/class-opentrust-admin.php:47 -#: includes/class-opentrust-admin.php:48 -#, fuzzy -msgid "OpenTrust" -msgstr "OpenTrust" - -#. Plugin URI of the plugin -#: opentrust.php -msgid "https://github.com/opentrust/opentrust" -msgstr "" - -#. Description of the plugin -#: opentrust.php -#, fuzzy -msgid "A self-hosted, open-source trust center for publishing security policies, subprocessors, certifications, and data practices." -msgstr "Een zelf-gehost, open-source vertrouwenscentrum voor het publiceren van beveiligingsbeleid, subverwerkers, certificeringen en gegevenspraktijken." - -#. Author URI of the plugin -#: opentrust.php -msgid "https://github.com/opentrust" -msgstr "" - -#: includes/class-opentrust-admin.php:58 -#: includes/class-opentrust-admin.php:59 -msgid "Settings" -msgstr "" - -#: includes/class-opentrust-admin.php:67 -#: includes/class-opentrust-admin.php:68 -#: includes/class-opentrust-admin.php:632 -msgid "Subscribers" -msgstr "" - -#: includes/class-opentrust-admin.php:76 -#: includes/class-opentrust-admin.php:77 -#: includes/class-opentrust-admin.php:1943 -msgid "Broadcast History" -msgstr "" - -#: includes/class-opentrust-admin.php:88 -#: includes/class-opentrust-admin.php:89 -msgid "Questions" -msgstr "" - -#: includes/class-opentrust-admin.php:111 -msgid "General Settings" -msgstr "" - -#: includes/class-opentrust-admin.php:116 -msgid "Endpoint Slug" -msgstr "" - -#: includes/class-opentrust-admin.php:117 -msgid "The URL path for your trust center (e.g., \"trust-center\" = yoursite.com/trust-center/)." -msgstr "" - -#: includes/class-opentrust-admin.php:120 -msgid "Page Title" -msgstr "" - -#: includes/class-opentrust-admin.php:122 -msgid "Company Name" -msgstr "" - -#: includes/class-opentrust-admin.php:124 -msgid "Tagline" -msgstr "" - -#: includes/class-opentrust-admin.php:125 -msgid "A short description displayed below the company name in the hero section." -msgstr "" - -#: includes/class-opentrust-admin.php:131 -msgid "Branding" -msgstr "" - -#: includes/class-opentrust-admin.php:136 -msgid "Logo" -msgstr "" - -#: includes/class-opentrust-admin.php:137 -msgid "AI Avatar" -msgstr "" - -#: includes/class-opentrust-admin.php:139 -msgid "Accent Color" -msgstr "" - -#: includes/class-opentrust-admin.php:140 -msgid "Used for buttons, links, and highlights. Choose a color that matches your brand." -msgstr "" - -#: includes/class-opentrust-admin.php:146 -msgid "Visible Sections" -msgstr "" - -#: includes/class-opentrust-admin.php:147 -msgid "Choose which sections to display on the trust center." -msgstr "" - -#: includes/class-opentrust-admin.php:151 -msgid "Sections" -msgstr "" - -#: includes/class-opentrust-admin.php:156 -msgid "Email Notifications" -msgstr "" - -#: includes/class-opentrust-admin.php:157 -msgid "Configure subscriber notifications for trust center updates." -msgstr "" - -#: includes/class-opentrust-admin.php:161 -msgid "Enable Notifications" -msgstr "" - -#: includes/class-opentrust-admin.php:162 -msgid "Show the subscribe form on the trust center and allow policy changes to be broadcast to subscribers." -msgstr "" - -#: includes/class-opentrust-admin.php:165 -msgid "From Name" -msgstr "" - -#: includes/class-opentrust-admin.php:166 -msgid "Sender name for notification emails. Defaults to company name." -msgstr "" - -#: includes/class-opentrust-admin.php:169 -msgid "Reply-To Email" -msgstr "" - -#: includes/class-opentrust-admin.php:170 -msgid "Reply-to address for notification emails. Defaults to admin email." -msgstr "" - -#: includes/class-opentrust-admin.php:176 -msgid "Spam Protection" -msgstr "" - -#: includes/class-opentrust-admin.php:177 -msgid "Protect the subscribe form from abuse with rate limiting and Cloudflare Turnstile." -msgstr "" - -#: includes/class-opentrust-admin.php:181 -msgid "Rate Limit" -msgstr "" - -#: includes/class-opentrust-admin.php:182 -msgid "Maximum subscribe attempts per IP address per hour. Set to 0 to disable." -msgstr "" - -#: includes/class-opentrust-admin.php:185 -msgid "Turnstile Site Key" -msgstr "" - -#: includes/class-opentrust-admin.php:186 -msgid "Public site key from your Cloudflare Turnstile widget. Leave blank to disable." -msgstr "" - -#: includes/class-opentrust-admin.php:189 -msgid "Turnstile Secret Key" -msgstr "" - -#: includes/class-opentrust-admin.php:190 -msgid "Secret key from Cloudflare Turnstile. Stored securely — never exposed to the frontend." -msgstr "" - -#: includes/class-opentrust-admin.php:249 -msgid "Low contrast on white backgrounds" -msgstr "" - -#: includes/class-opentrust-admin.php:250 -msgid "Using your exact color on white backgrounds" -msgstr "" - -#: includes/class-opentrust-admin.php:253 -msgid "Your chosen color is too light for buttons, links, and borders on white sections. On those surfaces OpenTrust will use a darker, on-brand variant:" -msgstr "" - -#: includes/class-opentrust-admin.php:256 -msgid "You've chosen to keep your exact color on white backgrounds. Buttons, links, and borders in those sections may be hard to read." -msgstr "" - -#: includes/class-opentrust-admin.php:268 -msgid "The hero and navigation still use your exact color." -msgstr "" - -#: includes/class-opentrust-admin.php:279 -msgid "Use my exact color anyway — skip the contrast adjustment." -msgstr "" - -#: includes/class-opentrust-admin.php:292 -msgid "Select Logo" -msgstr "" - -#: includes/class-opentrust-admin.php:293 -msgid "Used in the hero and sticky nav. A white version is recommended — it sits on a dark background." -msgstr "" - -#: includes/class-opentrust-admin.php:300 -msgid "Select Avatar" -msgstr "" - -#: includes/class-opentrust-admin.php:301 -msgid "Square image used as the avatar on AI chat responses. Use a colored background with a light or dark favicon or logo on top." -msgstr "" - -#: includes/class-opentrust-admin.php:316 -#: includes/class-opentrust-cpt.php:337 -#: includes/class-opentrust-cpt.php:509 -#: includes/class-opentrust-cpt.php:547 -msgid "Remove" -msgstr "" - -#: includes/class-opentrust-admin.php:328 -msgid "Enable email notifications" -msgstr "" - -#: includes/class-opentrust-admin.php:342 -msgid "per hour" -msgstr "" - -#: includes/class-opentrust-admin.php:357 -msgid "Enter secret key…" -msgstr "" - -#: includes/class-opentrust-admin.php:360 -msgid "Key saved" -msgstr "" - -#: includes/class-opentrust-admin.php:372 -#: templates/partials/certifications.php:21 -#, fuzzy -msgid "Certifications & Compliance" -msgstr "Certificeringen & compliance" - -#: includes/class-opentrust-admin.php:373 -#: includes/class-opentrust-cpt.php:65 -#: includes/class-opentrust-cpt.php:75 -#: templates/chat.php:44 -#: templates/partials/hero.php:55 -#: templates/trust-center.php:106 -#, fuzzy -msgid "Policies" -msgstr "Beleid" - -#: includes/class-opentrust-admin.php:374 -#: includes/class-opentrust-cpt.php:132 -#: includes/class-opentrust-cpt.php:141 -#: templates/chat.php:46 -#: templates/partials/hero.php:61 -#: templates/partials/subprocessors.php:20 -#: templates/trust-center.php:108 -#, fuzzy -msgid "Subprocessors" -msgstr "Subverwerkers" - -#: includes/class-opentrust-admin.php:375 -#: includes/class-opentrust-cpt.php:165 -#: includes/class-opentrust-cpt.php:174 -#: templates/chat.php:47 -#: templates/partials/data-practices.php:34 -#: templates/trust-center.php:109 -#, fuzzy -msgid "Data Practices" -msgstr "Gegevensverwerking" - -#: includes/class-opentrust-admin.php:501 -msgid "View Trust Center" -msgstr "" - -#: includes/class-opentrust-admin.php:508 -#: includes/class-opentrust-render.php:823 -msgid "General" -msgstr "" - -#: includes/class-opentrust-admin.php:512 -msgid "AI Chat" -msgstr "" - -#: includes/class-opentrust-admin.php:515 -msgid "Live" -msgstr "" - -#: includes/class-opentrust-admin.php:592 -msgid "This email is already in the subscriber list." -msgstr "" - -#: includes/class-opentrust-admin.php:596 -msgid "Subscriber added and marked as verified." -msgstr "" - -#: includes/class-opentrust-admin.php:597 -msgid "Could not add subscriber." -msgstr "" - -#: includes/class-opentrust-admin.php:611 -msgid "Subscriber deleted." -msgstr "" - -#: includes/class-opentrust-admin.php:619 -msgid "Subscriber verified. No confirmation email sent." -msgstr "" - -#: includes/class-opentrust-admin.php:621 -msgid "Could not verify subscriber." -msgstr "" - -#. translators: 1: imported, 2: updated, 3: skipped, 4: errors -#: includes/class-opentrust-admin.php:647 -#, php-format -msgid "Import complete: %1$d imported, %2$d updated, %3$d skipped, %4$d errors." -msgstr "" - -#: includes/class-opentrust-admin.php:656 -msgid "Show errors" -msgstr "" - -#. translators: %d: number of additional errors -#: includes/class-opentrust-admin.php:664 -#, php-format -msgid "… and %d more." -msgstr "" - -#: includes/class-opentrust-admin.php:676 -#: includes/class-opentrust-admin.php:710 -msgid "Add subscriber" -msgstr "" - -#: includes/class-opentrust-admin.php:681 -#: includes/class-opentrust-admin.php:805 -#, fuzzy -msgid "Email" -msgstr "E-mail" - -#: includes/class-opentrust-admin.php:685 -#: includes/class-opentrust-admin.php:806 -#: templates/partials/subprocessors.php:36 -#: templates/partials/subscribe.php:58 -#, fuzzy -msgid "Name" -msgstr "Naam" - -#: includes/class-opentrust-admin.php:686 -#: templates/partials/subscribe.php:60 -msgid "Jane Doe" -msgstr "" - -#: includes/class-opentrust-admin.php:689 -#: includes/class-opentrust-admin.php:807 -#: templates/partials/subscribe.php:64 -#, fuzzy -msgid "Company" -msgstr "Bedrijf" - -#: includes/class-opentrust-admin.php:690 -#: templates/partials/subscribe.php:66 -msgid "Acme Inc." -msgstr "" - -#: includes/class-opentrust-admin.php:694 -msgid "Notify about" -msgstr "" - -#: includes/class-opentrust-admin.php:707 -msgid "Mark as verified immediately (skip confirmation email)" -msgstr "" - -#: includes/class-opentrust-admin.php:714 -msgid "By default, the subscriber receives a confirmation email they must click to activate. Check \"Mark as verified\" to skip that step and activate them immediately — only do this when you have consent on file." -msgstr "" - -#: includes/class-opentrust-admin.php:728 -msgid "Import from CSV" -msgstr "" - -#: includes/class-opentrust-admin.php:735 -msgid "CSV file" -msgstr "" - -#: includes/class-opentrust-admin.php:739 -msgid "If email exists" -msgstr "" - -#: includes/class-opentrust-admin.php:741 -msgid "Skip existing (safest)" -msgstr "" - -#: includes/class-opentrust-admin.php:742 -msgid "Update — merge non-empty fields" -msgstr "" - -#: includes/class-opentrust-admin.php:743 -msgid "Replace — overwrite all fields" -msgstr "" - -#: includes/class-opentrust-admin.php:752 -msgid "Verify everyone on import" -msgstr "" - -#: includes/class-opentrust-admin.php:753 -msgid "Overrides the Status column in the CSV. Every row is set to active and no confirmation emails are sent. Uncheck to respect the CSV Status column — pending rows will remain pending and must be verified manually from the subscriber list." -msgstr "" - -#: includes/class-opentrust-admin.php:760 -msgid "Import CSV" -msgstr "" - -#: includes/class-opentrust-admin.php:763 -msgid "Download example CSV" -msgstr "" - -#: includes/class-opentrust-admin.php:768 -msgid "Required column: email. Optional: name, company, status, categories, subscribed, confirmed. Categories can be semicolon- or comma-separated. Maximum 5,000 rows, 2 MB file size. Confirmation emails are never sent on import." -msgstr "" - -#. translators: %d: total subscriber count -#: includes/class-opentrust-admin.php:780 -#, php-format -msgid "All (%d)" -msgstr "" - -#. translators: %d: active subscriber count -#: includes/class-opentrust-admin.php:787 -#, php-format -msgid "Active (%d)" -msgstr "" - -#. translators: %d: pending subscriber count -#: includes/class-opentrust-admin.php:794 -#, php-format -msgid "Pending (%d)" -msgstr "" - -#: includes/class-opentrust-admin.php:797 -msgid "Export CSV" -msgstr "" - -#: includes/class-opentrust-admin.php:808 -#: includes/class-opentrust-cpt.php:314 -#: includes/class-opentrust-cpt.php:805 -#, fuzzy -msgid "Status" -msgstr "Status" - -#: includes/class-opentrust-admin.php:809 -msgid "Categories" -msgstr "" - -#: includes/class-opentrust-admin.php:810 -msgid "Subscribed" -msgstr "" - -#: includes/class-opentrust-admin.php:811 -#: includes/class-opentrust-version.php:163 -#: templates/partials/policies.php:40 -#, fuzzy -msgid "Actions" -msgstr "Acties" - -#: includes/class-opentrust-admin.php:816 -msgid "No subscribers yet." -msgstr "" - -#: includes/class-opentrust-admin.php:851 -msgid "Mark this subscriber as verified? No confirmation email will be sent." -msgstr "" - -#: includes/class-opentrust-admin.php:852 -msgid "Verify" -msgstr "" - -#: includes/class-opentrust-admin.php:858 -msgid "Delete this subscriber?" -msgstr "" - -#: includes/class-opentrust-admin.php:859 -msgid "Delete" -msgstr "" - -#: includes/class-opentrust-admin.php:950 -msgid "No file was uploaded." -msgstr "" - -#: includes/class-opentrust-admin.php:956 -msgid "File upload failed. Please try again." -msgstr "" - -#: includes/class-opentrust-admin.php:962 -msgid "Invalid upload." -msgstr "" - -#: includes/class-opentrust-admin.php:968 -msgid "File must be between 1 byte and 2 MB." -msgstr "" - -#: includes/class-opentrust-admin.php:978 -msgid "File must be a .csv file." -msgstr "" - -#: includes/class-opentrust-admin.php:986 -msgid "File appears to be binary, not a CSV." -msgstr "" - -#: includes/class-opentrust-admin.php:1031 -msgid "Heads up: citation fidelity is not guaranteed on your active provider." -msgstr "" - -#. translators: %s: provider label, e.g. OpenAI -#: includes/class-opentrust-admin.php:1036 -#, php-format -msgid "You are currently using %s. Only Anthropic uses a structural Citations API — every other provider relies on prompted citation tags the model can ignore or fabricate. For a published trust center, switch to Anthropic below." -msgstr "" - -#: includes/class-opentrust-admin.php:1047 -msgid "OpenTrust uses Anthropic Claude with the native Citations API to answer visitor questions about your trust center. Every claim the assistant makes is tied to an exact quote from one of your published documents — so no policy text is invented and nothing is paraphrased into something you did not actually publish." -msgstr "" - -#: includes/class-opentrust-admin.php:1054 -msgid "Why Anthropic, and not OpenAI or any other provider?" -msgstr "" - -#: includes/class-opentrust-admin.php:1059 -msgid "A trust center is a compliance surface. If the assistant invents a security commitment you never made, that is not a UX papercut — it is a misrepresentation of your security posture, and your customers and auditors will hold you to it." -msgstr "" - -#: includes/class-opentrust-admin.php:1067 -msgid "Anthropic is the only major provider that exposes a structural Citations API. Documents are sent as typed blocks and the model emits citations as first-class events containing the exact source document and the exact quoted text. The model literally cannot return a citation for text that is not in your source documents." -msgstr "" - -#: includes/class-opentrust-admin.php:1073 -msgid "Every other provider (including OpenAI and any model accessed via OpenRouter) relies on prompted citation tags that we parse out of the answer after the fact. That works most of the time, but the model can ignore the instructions, make up document IDs, or attach a citation to a sentence it actually hallucinated. We support these providers as an escape hatch for organizations that genuinely cannot use Anthropic for procurement or data-residency reasons — but we very, very strongly recommend you do not run a public trust center on them." -msgstr "" - -#: includes/class-opentrust-admin.php:1104 -msgid "Choose a provider and add your key" -msgstr "" - -#: includes/class-opentrust-admin.php:1116 -msgid "Step 1 — Connect Anthropic" -msgstr "" - -#: includes/class-opentrust-admin.php:1122 -msgid "Advanced: use a different provider (not recommended)" -msgstr "" - -#: includes/class-opentrust-admin.php:1125 -msgid "These providers cannot guarantee citation fidelity." -msgstr "" - -#: includes/class-opentrust-admin.php:1127 -msgid "OpenAI and OpenRouter rely on prompted [[cite:document-id]] tags that we parse out of the answer after generation. The model can ignore the instruction, invent document IDs, or attach a citation to a sentence it actually hallucinated. We cannot detect when this happens." -msgstr "" - -#: includes/class-opentrust-admin.php:1130 -msgid "Do not use these providers for a published trust center" -msgstr "" - -#: includes/class-opentrust-admin.php:1131 -msgid "unless your organization genuinely cannot use Anthropic for procurement, contractual, or data-residency reasons. Inaccurate claims about your security posture are a real compliance risk." -msgstr "" - -#: includes/class-opentrust-admin.php:1170 -msgid "Required for citation fidelity" -msgstr "" - -#: includes/class-opentrust-admin.php:1176 -msgid "Uses Claude with the native Citations API. Every quote the assistant attributes to one of your documents is structurally guaranteed to come from that document." -msgstr "" - -#. translators: %s: provider name (e.g. Anthropic) -#: includes/class-opentrust-admin.php:1184 -#, php-format -msgid "Get a %s API key" -msgstr "" - -#: includes/class-opentrust-admin.php:1197 -msgid "Remove the saved key for this provider?" -msgstr "" - -#: includes/class-opentrust-admin.php:1198 -msgid "Replace key" -msgstr "" - -#. translators: %s: provider name (e.g. Anthropic) -#: includes/class-opentrust-admin.php:1208 -#, php-format -msgid "Paste your %s API key…" -msgstr "" - -#: includes/class-opentrust-admin.php:1212 -msgid "Validate & save" -msgstr "" - -#: includes/class-opentrust-admin.php:1229 -msgid "Step 2 — Pick a model and tune defaults" -msgstr "" - -#: includes/class-opentrust-admin.php:1251 -msgid "Active model" -msgstr "" - -#: includes/class-opentrust-admin.php:1255 -msgid "No cached models found. Use Refresh to re-fetch the model list." -msgstr "" - -#: includes/class-opentrust-admin.php:1263 -msgid "Recommended" -msgstr "" - -#: includes/class-opentrust-admin.php:1270 -msgid "Refresh models" -msgstr "" - -#. translators: %s: human-readable time difference (e.g. "5 minutes") -#: includes/class-opentrust-admin.php:1280 -#, php-format -msgid "Model list cached %s ago." -msgstr "" - -#: includes/class-opentrust-admin.php:1288 -msgid "Daily token budget" -msgstr "" - -#: includes/class-opentrust-admin.php:1291 -msgid "Hard cap per site per day. Default 500,000 tokens (~$12/day at Sonnet 4.5 rates)." -msgstr "" - -#: includes/class-opentrust-admin.php:1295 -msgid "Monthly token budget" -msgstr "" - -#: includes/class-opentrust-admin.php:1298 -msgid "Hard cap per site per month. Default 10,000,000 tokens." -msgstr "" - -#: includes/class-opentrust-admin.php:1302 -msgid "Rate limit — per IP" -msgstr "" - -#: includes/class-opentrust-admin.php:1304 -msgid "messages per minute" -msgstr "" - -#: includes/class-opentrust-admin.php:1308 -msgid "Rate limit — per session" -msgstr "" - -#: includes/class-opentrust-admin.php:1310 -msgid "messages per hour" -msgstr "" - -#: includes/class-opentrust-admin.php:1314 -msgid "Max message length" -msgstr "" - -#: includes/class-opentrust-admin.php:1316 -msgid "characters" -msgstr "" - -#: includes/class-opentrust-admin.php:1321 -msgid "Refuse-to-answer contact URL" -msgstr "" - -#: includes/class-opentrust-admin.php:1324 -msgid "When the AI cannot confidently answer a question, it links here. Leave blank to use the trust center home." -msgstr "" - -#: includes/class-opentrust-admin.php:1329 -msgid "Visitor display" -msgstr "" - -#: includes/class-opentrust-admin.php:1333 -msgid "Show \"Powered by {model}\" under the chat input" -msgstr "" - -#: includes/class-opentrust-admin.php:1339 -msgid "Analytics logging" -msgstr "" - -#: includes/class-opentrust-admin.php:1343 -msgid "Log anonymized visitor questions for admin review (90-day auto-purge, no PII)" -msgstr "" - -#: includes/class-opentrust-admin.php:1349 -msgid "Advanced — Turnstile anti-abuse" -msgstr "" - -#: includes/class-opentrust-admin.php:1351 -msgid "Cloudflare Turnstile is optional but recommended for public sites. It challenges suspicious visitors on the first message of each session. You need a free Cloudflare account to get site/secret keys." -msgstr "" - -#: includes/class-opentrust-admin.php:1355 -msgid "Enable Turnstile for chat" -msgstr "" - -#: includes/class-opentrust-admin.php:1359 -msgid "Require Turnstile verification on first chat message" -msgstr "" - -#: includes/class-opentrust-admin.php:1361 -msgid "Reuses the Turnstile site/secret keys you configure in the General tab." -msgstr "" - -#: includes/class-opentrust-admin.php:1366 -msgid "Save AI settings" -msgstr "" - -#: includes/class-opentrust-admin.php:1413 -#: includes/class-opentrust-admin.php:1478 -#: includes/class-opentrust-admin.php:1515 -#: includes/class-opentrust-admin.php:1750 -#: includes/class-opentrust-admin.php:1791 -#: includes/class-opentrust-admin.php:1808 -msgid "You do not have permission to perform this action." -msgstr "" - -#: includes/class-opentrust-admin.php:1422 -#: includes/class-opentrust-admin.php:1486 -#: includes/class-opentrust-admin.php:1522 -msgid "Unknown provider." -msgstr "" - -#: includes/class-opentrust-admin.php:1426 -msgid "API key cannot be empty." -msgstr "" - -#: includes/class-opentrust-admin.php:1433 -msgid "Validation failed." -msgstr "" - -#. translators: 1: provider label, 2: provider error message -#: includes/class-opentrust-admin.php:1435 -#, php-format -msgid "%1$s rejected the key: %2$s" -msgstr "" - -#. translators: 1: provider label, 2: number of models -#: includes/class-opentrust-admin.php:1471 -#, php-format -msgid "%1$s key validated. Found %2$d model(s)." -msgstr "" - -#: includes/class-opentrust-admin.php:1509 -msgid "Key removed." -msgstr "" - -#: includes/class-opentrust-admin.php:1528 -msgid "No key on file for this provider." -msgstr "" - -#: includes/class-opentrust-admin.php:1534 -msgid "Refresh failed." -msgstr "" - -#. translators: %s: error message from the provider -#: includes/class-opentrust-admin.php:1536 -#, php-format -msgid "Refresh failed: %s" -msgstr "" - -#. translators: %d: number of models -#: includes/class-opentrust-admin.php:1552 -#, php-format -msgid "Model list refreshed. Found %d model(s)." -msgstr "" - -#: includes/class-opentrust-admin.php:1618 -msgid "AI Questions" -msgstr "" - -#: includes/class-opentrust-admin.php:1621 -msgid "Questions visitors have asked your trust center chat. Identifiers are hashed and rows auto-purge after 90 days." -msgstr "" - -#: includes/class-opentrust-admin.php:1627 -msgid "Logging is ON" -msgstr "" - -#: includes/class-opentrust-admin.php:1629 -msgid "Logging is OFF" -msgstr "" - -#. translators: %d: number of questions -#: includes/class-opentrust-admin.php:1635 -#, php-format -msgid "%d question logged in the last 90 days" -msgid_plural "%d questions logged in the last 90 days" -msgstr[0] "" -msgstr[1] "" - -#: includes/class-opentrust-admin.php:1639 -msgid "Toggle visitor question logging?" -msgstr "" - -#: includes/class-opentrust-admin.php:1640 -msgid "Disable logging" -msgstr "" - -#: includes/class-opentrust-admin.php:1640 -msgid "Enable logging" -msgstr "" - -#: includes/class-opentrust-admin.php:1648 -#, fuzzy -msgid "Search" -msgstr "Zoeken" - -#: includes/class-opentrust-admin.php:1649 -msgid "Search questions…" -msgstr "" - -#: includes/class-opentrust-admin.php:1652 -#: includes/class-opentrust-admin.php:1679 -msgid "Model" -msgstr "" - -#: includes/class-opentrust-admin.php:1654 -msgid "Any" -msgstr "" - -#: includes/class-opentrust-admin.php:1661 -msgid "From" -msgstr "" - -#: includes/class-opentrust-admin.php:1665 -msgid "To" -msgstr "" - -#: includes/class-opentrust-admin.php:1668 -msgid "Filter" -msgstr "" - -#: includes/class-opentrust-admin.php:1669 -msgid "Reset" -msgstr "" - -#: includes/class-opentrust-admin.php:1670 -msgid "Download CSV" -msgstr "" - -#: includes/class-opentrust-admin.php:1677 -#: includes/class-opentrust-version.php:162 -msgid "Date" -msgstr "" - -#: includes/class-opentrust-admin.php:1678 -msgid "Question" -msgstr "" - -#: includes/class-opentrust-admin.php:1680 -msgid "Cites" -msgstr "" - -#: includes/class-opentrust-admin.php:1681 -msgid "Tokens" -msgstr "" - -#: includes/class-opentrust-admin.php:1682 -msgid "Latency" -msgstr "" - -#: includes/class-opentrust-admin.php:1687 -msgid "No questions logged yet." -msgstr "" - -#: includes/class-opentrust-admin.php:1696 -msgid "REFUSED" -msgstr "" - -#: includes/class-opentrust-admin.php:1736 -msgid "Danger zone" -msgstr "" - -#: includes/class-opentrust-admin.php:1737 -msgid "Permanently delete all logged questions? This cannot be undone." -msgstr "" - -#: includes/class-opentrust-admin.php:1738 -msgid "Clear entire question log" -msgstr "" - -#: includes/class-opentrust-admin.php:1799 -msgid "Question log cleared." -msgstr "" - -#: includes/class-opentrust-admin.php:1818 -msgid "Logging enabled." -msgstr "" - -#: includes/class-opentrust-admin.php:1818 -msgid "Logging disabled." -msgstr "" - -#: includes/class-opentrust-admin.php:1866 -msgid "Select Badge Image" -msgstr "" - -#: includes/class-opentrust-admin.php:1867 -msgid "Use as Badge" -msgstr "" - -#: includes/class-opentrust-admin.php:1882 -msgid "No match in catalog, just keep typing to add manually." -msgstr "" - -#: includes/class-opentrust-admin.php:1883 -msgid "Auto-filled from catalog, you may want to verify this." -msgstr "" - -#: includes/class-opentrust-admin.php:1884 -msgid "Auto-filled template, please verify this matches how you use this service." -msgstr "" - -#: includes/class-opentrust-admin.php:1885 -msgid "click to autofill" -msgstr "" - -#: includes/class-opentrust-admin.php:1886 -msgid "Catalog suggestions" -msgstr "" - -#: includes/class-opentrust-admin.php:1945 -msgid "Every policy broadcast sent from the plugin is logged here. One row per broadcast event." -msgstr "" - -#: includes/class-opentrust-admin.php:1949 -msgid "No broadcasts yet. When you save a policy with \"Broadcast this change to subscribers\" ticked, the send will be logged here." -msgstr "" - -#: includes/class-opentrust-admin.php:1954 -#: includes/class-opentrust-cpt.php:66 -#: templates/partials/policies.php:36 -#, fuzzy -msgid "Policy" -msgstr "Beleid" - -#: includes/class-opentrust-admin.php:1955 -msgid "Sent at" -msgstr "" - -#: includes/class-opentrust-admin.php:1956 -msgid "Delivered" -msgstr "" - -#: includes/class-opentrust-admin.php:1957 -msgid "Failed" -msgstr "" - -#: includes/class-opentrust-admin.php:1972 -msgid "(deleted policy)" -msgstr "" - -#: includes/class-opentrust-admin.php:2042 -msgid "OpenTrust requires pretty permalinks." -msgstr "" - -#. translators: %s: link to Settings → Permalinks -#: includes/class-opentrust-admin.php:2046 -#, php-format -msgid "Your site is using \"Plain\" permalinks. Please go to %s and choose any other option (Post name is the WordPress default)." -msgstr "" - -#: includes/class-opentrust-admin.php:2047 -msgid "Settings → Permalinks" -msgstr "" - -#: includes/class-opentrust-admin.php:2052 -msgid "Without pretty permalinks, every link OpenTrust generates returns 404 — including the trust center page itself, the subscribe form, and every confirmation, unsubscribe, and broadcast email link sent to subscribers. Visitors will not be able to subscribe, confirm, or unsubscribe." -msgstr "" - -#: includes/class-opentrust-admin.php:2056 -msgid "Read-only fallback if you cannot change permalinks" -msgstr "" - -#: includes/class-opentrust-admin.php:2060 -msgid "You can preview the trust center via raw query-string URLs, but no email links will work and visitors cannot subscribe:" -msgstr "" - -#: includes/class-opentrust-admin.php:2068 -msgid "This is for testing only." -msgstr "" - -#: includes/class-opentrust-admin.php:2069 -msgid "Switching to pretty permalinks is the only supported configuration." -msgstr "" - -#: includes/class-opentrust-chat.php:90 -#, fuzzy -msgid "Invalid nonce — refresh the page and try again." -msgstr "Ongeldige nonce — ververs de pagina en probeer opnieuw." - -#: includes/class-opentrust-chat.php:113 -#, fuzzy -msgid "Please complete the anti-abuse challenge and try again." -msgstr "Voltooi de anti-misbruikcontrole en probeer opnieuw." - -#: includes/class-opentrust-chat.php:125 -msgid "You are sending messages too fast. Please wait a moment and try again." -msgstr "" - -#: includes/class-opentrust-chat.php:136 -msgid "You have reached the per-session message limit. Please wait a bit and try again." -msgstr "" - -#: includes/class-opentrust-chat.php:156 -msgid "AI chat is not configured on this site." -msgstr "" - -#: includes/class-opentrust-chat.php:165 -msgid "Configured provider is unknown." -msgstr "" - -#: includes/class-opentrust-chat.php:174 -msgid "No API key stored for the configured provider." -msgstr "" - -#: includes/class-opentrust-chat.php:187 -msgid "Your message is empty." -msgstr "" - -#: includes/class-opentrust-chat.php:197 -msgid "Published content exceeds the AI chat size limit." -msgstr "" - -#: includes/class-opentrust-chat.php:207 -msgid "The daily chat budget for this site has been reached. Please try again later." -msgstr "" - -#: includes/class-opentrust-chat.php:389 -msgid "Chat provider failed unexpectedly." -msgstr "" - -#: includes/class-opentrust-cpt.php:46 -msgid "Pick from the catalog or type your own, e.g. Datadog, Stripe, or AWS" -msgstr "" - -#: includes/class-opentrust-cpt.php:49 -msgid "Pick from the catalog or type your own, e.g. Analytics or Transactional Email" -msgstr "" - -#: includes/class-opentrust-cpt.php:52 -msgid "Pick from the catalog or type your own, e.g. SOC 2 Type II or ISO 27001" -msgstr "" - -#: includes/class-opentrust-cpt.php:67 -msgid "Add Policy" -msgstr "" - -#: includes/class-opentrust-cpt.php:68 -msgid "Add New Policy" -msgstr "" - -#: includes/class-opentrust-cpt.php:69 -msgid "Edit Policy" -msgstr "" - -#: includes/class-opentrust-cpt.php:70 -msgid "New Policy" -msgstr "" - -#: includes/class-opentrust-cpt.php:71 -#, fuzzy -msgid "View Policy" -msgstr "Beleid bekijken" - -#: includes/class-opentrust-cpt.php:72 -msgid "Search Policies" -msgstr "" - -#: includes/class-opentrust-cpt.php:73 -msgid "No policies found." -msgstr "" - -#: includes/class-opentrust-cpt.php:74 -msgid "No policies in trash." -msgstr "" - -#: includes/class-opentrust-cpt.php:99 -#: includes/class-opentrust-cpt.php:108 -#: templates/chat.php:45 -#: templates/partials/hero.php:49 -#: templates/trust-center.php:107 -#, fuzzy -msgid "Certifications" -msgstr "Certificeringen" - -#: includes/class-opentrust-cpt.php:100 -msgid "Certification" -msgstr "" - -#: includes/class-opentrust-cpt.php:101 -msgid "Add Certification" -msgstr "" - -#: includes/class-opentrust-cpt.php:102 -msgid "Add New Certification" -msgstr "" - -#: includes/class-opentrust-cpt.php:103 -msgid "Edit Certification" -msgstr "" - -#: includes/class-opentrust-cpt.php:104 -msgid "New Certification" -msgstr "" - -#: includes/class-opentrust-cpt.php:105 -msgid "Search Certifications" -msgstr "" - -#: includes/class-opentrust-cpt.php:106 -msgid "No certifications found." -msgstr "" - -#: includes/class-opentrust-cpt.php:107 -msgid "No certifications in trash." -msgstr "" - -#: includes/class-opentrust-cpt.php:133 -msgid "Subprocessor" -msgstr "" - -#: includes/class-opentrust-cpt.php:134 -msgid "Add Subprocessor" -msgstr "" - -#: includes/class-opentrust-cpt.php:135 -msgid "Add New Subprocessor" -msgstr "" - -#: includes/class-opentrust-cpt.php:136 -msgid "Edit Subprocessor" -msgstr "" - -#: includes/class-opentrust-cpt.php:137 -msgid "New Subprocessor" -msgstr "" - -#: includes/class-opentrust-cpt.php:138 -msgid "Search Subprocessors" -msgstr "" - -#: includes/class-opentrust-cpt.php:139 -msgid "No subprocessors found." -msgstr "" - -#: includes/class-opentrust-cpt.php:140 -msgid "No subprocessors in trash." -msgstr "" - -#: includes/class-opentrust-cpt.php:166 -msgid "Data Practice" -msgstr "" - -#: includes/class-opentrust-cpt.php:167 -msgid "Add Data Practice" -msgstr "" - -#: includes/class-opentrust-cpt.php:168 -msgid "Add New Data Practice" -msgstr "" - -#: includes/class-opentrust-cpt.php:169 -msgid "Edit Data Practice" -msgstr "" - -#: includes/class-opentrust-cpt.php:170 -msgid "New Data Practice" -msgstr "" - -#: includes/class-opentrust-cpt.php:171 -msgid "Search Data Practices" -msgstr "" - -#: includes/class-opentrust-cpt.php:172 -msgid "No data practices found." -msgstr "" - -#: includes/class-opentrust-cpt.php:173 -msgid "No data practices in trash." -msgstr "" - -#: includes/class-opentrust-cpt.php:202 -msgid "Certification Details" -msgstr "" - -#: includes/class-opentrust-cpt.php:207 -msgid "Email subscribers" -msgstr "" - -#: includes/class-opentrust-cpt.php:210 -msgid "Policy Details" -msgstr "" - -#: includes/class-opentrust-cpt.php:211 -msgid "Subprocessor Details" -msgstr "" - -#: includes/class-opentrust-cpt.php:212 -msgid "Data Practice Details" -msgstr "" - -#: includes/class-opentrust-cpt.php:231 -msgid "Broadcast triggered, but no active subscribers are opted in to policy updates." -msgstr "" - -#. translators: %d: subscriber count -#: includes/class-opentrust-cpt.php:235 -#, php-format -msgid "Broadcast just sent to %d subscriber." -msgid_plural "Broadcast just sent to %d subscribers." -msgstr[0] "" -msgstr[1] "" - -#. translators: 1: delivered count, 2: failed count -#: includes/class-opentrust-cpt.php:239 -#, php-format -msgid "Broadcast finished: %1$d delivered, %2$d failed." -msgstr "" - -#: includes/class-opentrust-cpt.php:251 -msgid "Broadcast this change to subscribers" -msgstr "" - -#: includes/class-opentrust-cpt.php:253 -msgid "Emails all active subscribers who opted in to policy updates. The checkbox resets after each save." -msgstr "" - -#. translators: %s: date and time -#: includes/class-opentrust-cpt.php:261 -#, php-format -msgid "Last broadcast: %s" -msgstr "" - -#. translators: 1: delivered count, 2: failed count -#: includes/class-opentrust-cpt.php:266 -#, php-format -msgid "%1$d delivered, %2$d failed" -msgstr "" - -#: includes/class-opentrust-cpt.php:288 -msgid "Certified (audited by a third party)" -msgstr "" - -#: includes/class-opentrust-cpt.php:289 -msgid "Compliant (self-attested adherence)" -msgstr "" - -#: includes/class-opentrust-cpt.php:293 -#: includes/class-opentrust-render.php:829 -#, fuzzy -msgid "Active" -msgstr "Actief" - -#: includes/class-opentrust-cpt.php:294 -#: includes/class-opentrust-render.php:830 -#, fuzzy -msgid "In Progress" -msgstr "In aanvraag" - -#: includes/class-opentrust-cpt.php:295 -#: includes/class-opentrust-render.php:831 -#, fuzzy -msgid "Expired" -msgstr "Verlopen" - -#: includes/class-opentrust-cpt.php:299 -msgid "Certification Type" -msgstr "" - -#: includes/class-opentrust-cpt.php:305 -msgid "Certified means a third-party auditor issued a certificate with dates. Compliant means you self-attest adherence to the standard without a formal audit." -msgstr "" - -#: includes/class-opentrust-cpt.php:309 -#: includes/class-opentrust-cpt.php:804 -#, fuzzy -msgid "Issuing Body" -msgstr "Uitgegeven door" - -#: includes/class-opentrust-cpt.php:310 -msgid "e.g., AICPA, ISO/IEC, PCI Security Standards Council" -msgstr "" - -#: includes/class-opentrust-cpt.php:323 -msgid "Issue Date" -msgstr "" - -#: includes/class-opentrust-cpt.php:328 -#: includes/class-opentrust-cpt.php:806 -#, fuzzy -msgid "Expiry Date" -msgstr "Vervaldatum" - -#: includes/class-opentrust-cpt.php:333 -msgid "Badge Image" -msgstr "" - -#: includes/class-opentrust-cpt.php:336 -msgid "Select Badge" -msgstr "" - -#: includes/class-opentrust-cpt.php:341 -msgid "Description" -msgstr "" - -#: includes/class-opentrust-cpt.php:343 -msgid "Brief description of this certification and its scope." -msgstr "" - -#. translators: %s: policy version number -#: includes/class-opentrust-cpt.php:371 -#: templates/partials/policy-single.php:97 -#, fuzzy -#, php-format -msgid "Version %s" -msgstr "Versie %s" - -#: includes/class-opentrust-cpt.php:374 -msgid "Regular saves update the current version. Use the checkbox below to formally publish a new version." -msgstr "" - -#: includes/class-opentrust-cpt.php:383 -msgid "Publish as new version" -msgstr "" - -#. translators: %1$d: current version number, %2$d: next version number -#: includes/class-opentrust-cpt.php:388 -#, php-format -msgid "This will save the current content as v%1$d and create v%2$d. Only check this for formal, published changes — not minor edits." -msgstr "" - -#: includes/class-opentrust-cpt.php:397 -msgid "What changed?" -msgstr "" - -#: includes/class-opentrust-cpt.php:400 -msgid "e.g., Updated data retention from 90 to 60 days" -msgstr "" - -#: includes/class-opentrust-cpt.php:401 -msgid "Shown in the public version history." -msgstr "" - -#: includes/class-opentrust-cpt.php:408 -#: includes/class-opentrust-cpt.php:835 -#: templates/partials/policies.php:37 -#, fuzzy -msgid "Category" -msgstr "Categorie" - -#: includes/class-opentrust-cpt.php:417 -msgid "Effective Date" -msgstr "" - -#: includes/class-opentrust-cpt.php:422 -msgid "Next Review Date" -msgstr "" - -#: includes/class-opentrust-cpt.php:427 -#: includes/class-opentrust-cpt.php:556 -msgid "Sort Order" -msgstr "" - -#: includes/class-opentrust-cpt.php:429 -#: includes/class-opentrust-cpt.php:558 -msgid "Lower numbers appear first." -msgstr "" - -#: includes/class-opentrust-cpt.php:435 -msgid "Allow PDF download" -msgstr "" - -#: includes/class-opentrust-cpt.php:453 -#: includes/class-opentrust-cpt.php:518 -#: includes/class-opentrust-cpt.php:854 -#: templates/partials/data-practices.php:96 -#: templates/partials/subprocessors.php:37 -#, fuzzy -msgid "Purpose" -msgstr "Doel" - -#: includes/class-opentrust-cpt.php:455 -msgid "What does this subprocessor do for your company?" -msgstr "" - -#: includes/class-opentrust-cpt.php:459 -#: templates/partials/subprocessors.php:38 -#, fuzzy -msgid "Data Processed" -msgstr "Verwerkte gegevens" - -#: includes/class-opentrust-cpt.php:461 -msgid "What types of data does this subprocessor handle?" -msgstr "" - -#: includes/class-opentrust-cpt.php:465 -msgid "Country / Location" -msgstr "" - -#: includes/class-opentrust-cpt.php:466 -msgid "e.g., United States" -msgstr "" - -#: includes/class-opentrust-cpt.php:470 -#: templates/partials/subprocessors.php:41 -#, fuzzy -msgid "Website" -msgstr "Website" - -#: includes/class-opentrust-cpt.php:477 -#, fuzzy -msgid "DPA Signed" -msgstr "DPA ondertekend" - -#: includes/class-opentrust-cpt.php:479 -msgid "A Data Processing Agreement (DPA) is a contract between you and the subprocessor covering how they handle personal data on your behalf. Check this box once your organization has signed one with this vendor." -msgstr "" - -#: includes/class-opentrust-cpt.php:503 -msgid "Data Items Collected" -msgstr "" - -#: includes/class-opentrust-cpt.php:512 -#: includes/class-opentrust-cpt.php:550 -msgid "Type and press Enter..." -msgstr "" - -#: includes/class-opentrust-cpt.php:525 -#: templates/partials/data-practices.php:103 -#, fuzzy -msgid "Legal Basis" -msgstr "Wettelijke grondslag" - -#: includes/class-opentrust-cpt.php:527 -msgid "— Select —" -msgstr "" - -#: includes/class-opentrust-cpt.php:534 -msgid "Retention Period" -msgstr "" - -#: includes/class-opentrust-cpt.php:535 -msgid "e.g., 30 days" -msgstr "" - -#: includes/class-opentrust-cpt.php:541 -#: templates/partials/data-practices.php:117 -#, fuzzy -msgid "Shared With" -msgstr "Gedeeld met" - -#: includes/class-opentrust-cpt.php:653 -msgid "Broadcast triggered, but no active subscribers are opted in to policy updates. Nothing was sent." -msgstr "" - -#. translators: %d: subscriber count -#: includes/class-opentrust-cpt.php:657 -#, php-format -msgid "Broadcast sent to %d subscriber." -msgid_plural "Broadcast sent to %d subscribers." -msgstr[0] "" -msgstr[1] "" - -#. translators: 1: delivered count, 2: failed count -#: includes/class-opentrust-cpt.php:661 -#, php-format -msgid "Broadcast finished: %1$d delivered, %2$d failed. Check your SMTP configuration." -msgstr "" - -#: includes/class-opentrust-cpt.php:836 -#: includes/class-opentrust-version.php:161 -#: templates/partials/policies.php:38 -#, fuzzy -msgid "Version" -msgstr "Versie" - -#: includes/class-opentrust-cpt.php:855 -#: templates/partials/subprocessors.php:39 -#, fuzzy -msgid "Location" -msgstr "Locatie" - -#: includes/class-opentrust-cpt.php:856 -#: templates/partials/subprocessors.php:40 -#, fuzzy -msgid "DPA" -msgstr "DPA" - -#: includes/class-opentrust-cpt.php:875 -#, fuzzy -msgid "Data Items" -msgstr "Gegevensitems" - -#: includes/class-opentrust-cpt.php:876 -msgid "Order" -msgstr "" - -#: includes/class-opentrust-notify.php:20 -msgid "Policy Updates" -msgstr "" - -#: includes/class-opentrust-notify.php:21 -msgid "Certification Updates" -msgstr "" - -#: includes/class-opentrust-notify.php:22 -msgid "Subprocessor Changes" -msgstr "" - -#: includes/class-opentrust-notify.php:23 -msgid "Data Practice Changes" -msgstr "" - -#: includes/class-opentrust-notify.php:185 -#, fuzzy -msgid "Please enter a valid email address." -msgstr "Voer een geldig e-mailadres in." - -#: includes/class-opentrust-notify.php:192 -msgid "This email is already subscribed." -msgstr "" - -#: includes/class-opentrust-notify.php:239 -msgid "Something went wrong. Please try again later." -msgstr "" - -#: includes/class-opentrust-notify.php:247 -#, fuzzy -msgid "Please check your inbox and click the confirmation link." -msgstr "Controleer je inbox en klik op de bevestigingslink." - -#: includes/class-opentrust-notify.php:456 -msgid "Could not read the uploaded file." -msgstr "" - -#: includes/class-opentrust-notify.php:463 -msgid "Could not open the uploaded file." -msgstr "" - -#. translators: %d: row cap -#: includes/class-opentrust-notify.php:488 -#, php-format -msgid "Import stopped at row %d (maximum row cap reached)." -msgstr "" - -#: includes/class-opentrust-notify.php:509 -msgid "CSV is missing a required \"email\" column." -msgstr "" - -#. translators: 1: row number -#: includes/class-opentrust-notify.php:527 -#, php-format -msgid "Row %d: invalid or missing email." -msgstr "" - -#. translators: 1: row number, 2: email -#: includes/class-opentrust-notify.php:536 -#, php-format -msgid "Row %1$d: duplicate email %2$s in CSV — skipped." -msgstr "" - -#. translators: 1: row number, 2: email -#: includes/class-opentrust-notify.php:618 -#, php-format -msgid "Row %1$d: failed to update %2$s." -msgstr "" - -#. translators: 1: row number, 2: email -#: includes/class-opentrust-notify.php:642 -#, php-format -msgid "Row %1$d: failed to insert %2$s." -msgstr "" - -#. translators: %s: subscriber name -#: includes/class-opentrust-notify.php:896 -#: includes/class-opentrust-notify.php:951 -#, fuzzy -#, php-format -msgid "Hi %s," -msgstr "Hoi %s," - -#: includes/class-opentrust-notify.php:897 -#: includes/class-opentrust-notify.php:952 -msgid "Hi," -msgstr "" - -#. translators: 1: company name, 2: policy title -#: includes/class-opentrust-notify.php:905 -#, fuzzy -#, php-format -msgid "[%1$s] Policy updated: %2$s" -msgstr "[%1$s] Beleid bijgewerkt: %2$s" - -#. translators: %s: company name -#: includes/class-opentrust-notify.php:945 -#, fuzzy -#, php-format -msgid "Confirm your subscription to %s Trust Center updates" -msgstr "Bevestig je aanmelding voor Trust Center-updates van %s" - -#: includes/class-opentrust-notify.php:1090 -#: includes/class-opentrust-notify.php:1247 -#, fuzzy -msgid "Invalid request." -msgstr "Ongeldig verzoek." - -#: includes/class-opentrust-notify.php:1150 -msgid "Too many attempts. Please try again later." -msgstr "" - -#: includes/class-opentrust-notify.php:1212 -#, fuzzy -msgid "Please complete the security check." -msgstr "Voltooi de beveiligingscontrole." - -#: includes/class-opentrust-notify.php:1235 -msgid "Security verification failed. Please try again." -msgstr "" - -#: includes/class-opentrust-notify.php:1262 -#, fuzzy -msgid "Please select at least one category." -msgstr "Selecteer ten minste één categorie." - -#: includes/class-opentrust-notify.php:1267 -msgid "Your preferences have been updated." -msgstr "" - -#: includes/class-opentrust-notify.php:1268 -msgid "Could not update preferences. Please try again." -msgstr "" - -#: includes/class-opentrust-render.php:87 -msgid "Session expired. Please reload the page and try again." -msgstr "" - -#: includes/class-opentrust-render.php:93 -#, fuzzy -msgid "Please enter a question." -msgstr "Voer een vraag in." - -#: includes/class-opentrust-render.php:102 -msgid "AI chat is not configured." -msgstr "" - -#: includes/class-opentrust-render.php:107 -msgid "AI chat is temporarily unavailable." -msgstr "" - -#: includes/class-opentrust-render.php:420 -msgid "Page not found." -msgstr "" - -#: includes/class-opentrust-render.php:421 -#: templates/partials/policy-single.php:56 -#: templates/partials/subscribe.php:21 -#, fuzzy -msgid "Back to Trust Center" -msgstr "Terug naar Trust Center" - -#: includes/class-opentrust-render.php:750 -msgid "Updated just now" -msgstr "" - -#. translators: %d: number of minutes since last update -#: includes/class-opentrust-render.php:755 -#, fuzzy -#, php-format -msgid "Updated %d minute ago" -msgid_plural "Updated %d minutes ago" -msgstr[0] "%d minuut geleden bijgewerkt" -msgstr[1] "%d minuten geleden bijgewerkt" - -#. translators: %d: number of hours since last update -#: includes/class-opentrust-render.php:762 -#, fuzzy -#, php-format -msgid "Updated %d hour ago" -msgid_plural "Updated %d hours ago" -msgstr[0] "%d uur geleden bijgewerkt" -msgstr[1] "%d uur geleden bijgewerkt" - -#. translators: %d: number of days since last update -#: includes/class-opentrust-render.php:769 -#, fuzzy -#, php-format -msgid "Updated %d day ago" -msgid_plural "Updated %d days ago" -msgstr[0] "%d dag geleden bijgewerkt" -msgstr[1] "%d dagen geleden bijgewerkt" - -#. translators: %s = formatted date -#: includes/class-opentrust-render.php:775 -#, fuzzy -#, php-format -msgid "Updated %s" -msgstr "Bijgewerkt op %s" - -#: includes/class-opentrust-render.php:819 -msgid "Security" -msgstr "" - -#: includes/class-opentrust-render.php:820 -msgid "Privacy" -msgstr "" - -#: includes/class-opentrust-render.php:821 -msgid "Compliance" -msgstr "" - -#: includes/class-opentrust-render.php:822 -msgid "Operational" -msgstr "" - -#: includes/class-opentrust-render.php:837 -msgid "Consent" -msgstr "" - -#: includes/class-opentrust-render.php:838 -msgid "Contractual Necessity" -msgstr "" - -#: includes/class-opentrust-render.php:839 -msgid "Legitimate Interest" -msgstr "" - -#: includes/class-opentrust-render.php:840 -msgid "Legal Obligation" -msgstr "" - -#: includes/class-opentrust-render.php:841 -msgid "Vital Interest" -msgstr "" - -#: includes/class-opentrust-render.php:842 -msgid "Public Interest" -msgstr "" - -#: includes/class-opentrust-render.php:848 -msgid "Account Information" -msgstr "" - -#: includes/class-opentrust-render.php:849 -msgid "Contact Information" -msgstr "" - -#: includes/class-opentrust-render.php:850 -msgid "Personal Data" -msgstr "" - -#: includes/class-opentrust-render.php:851 -msgid "Financial Data" -msgstr "" - -#: includes/class-opentrust-render.php:852 -msgid "Usage & Analytics" -msgstr "" - -#: includes/class-opentrust-render.php:853 -msgid "Device & Technical" -msgstr "" - -#: includes/class-opentrust-render.php:854 -msgid "Behavioral Data" -msgstr "" - -#: includes/class-opentrust-render.php:855 -msgid "User Content" -msgstr "" - -#: includes/class-opentrust-render.php:856 -msgid "Communications" -msgstr "" - -#: includes/class-opentrust-render.php:857 -msgid "Location Data" -msgstr "" - -#: includes/class-opentrust-render.php:858 -msgid "Identity & Verification" -msgstr "" - -#: includes/class-opentrust-render.php:859 -msgid "Marketing & Preferences" -msgstr "" - -#: includes/class-opentrust-render.php:860 -msgid "Sensitive Data" -msgstr "" - -#: includes/class-opentrust-render.php:861 -msgid "Health Data" -msgstr "" - -#: includes/class-opentrust-version.php:128 -msgid "Version History" -msgstr "" - -#: includes/class-opentrust-version.php:150 -msgid "Current version:" -msgstr "" - -#: includes/class-opentrust-version.php:153 -msgid "Version history will appear after the first update." -msgstr "" - -#: includes/class-opentrust-version.php:170 -#: templates/partials/policy-single.php:158 -msgid "Current" -msgstr "" - -#: includes/class-opentrust-version.php:184 -#: includes/class-opentrust-version.php:185 -msgid "View" -msgstr "" - -#: includes/class-opentrust-version.php:188 -msgid "Compare" -msgstr "" - -#: includes/class-opentrust-version.php:189 -msgid "Diff" -msgstr "" - -#: includes/providers/class-opentrust-chat-provider-anthropic.php:37 -#: includes/providers/class-opentrust-chat-provider.php:45 -msgid "Anthropic" -msgstr "" - -#: includes/providers/class-opentrust-chat-provider-anthropic.php:51 -#: includes/providers/class-opentrust-chat-provider-openai.php:80 -msgid "API key is empty." -msgstr "" - -#: includes/providers/class-opentrust-chat-provider-anthropic.php:64 -#: includes/providers/class-opentrust-chat-provider-openai.php:90 -msgid "Request failed." -msgstr "" - -#: includes/providers/class-opentrust-chat-provider-anthropic.php:70 -msgid "No models available — your account may not be authorized." -msgstr "" - -#: includes/providers/class-opentrust-chat-provider-anthropic.php:133 -msgid "Anthropic adapter missing required args." -msgstr "" - -#: includes/providers/class-opentrust-chat-provider-anthropic.php:201 -msgid "Anthropic request failed." -msgstr "" - -#: includes/providers/class-opentrust-chat-provider-anthropic.php:258 -msgid "Tool call produced no content blocks." -msgstr "" - -#: includes/providers/class-opentrust-chat-provider-anthropic.php:286 -#: includes/providers/class-opentrust-chat-provider-openai.php:315 -msgid "Conversation exceeded tool-use depth limit." -msgstr "" - -#: includes/providers/class-opentrust-chat-provider-openai.php:66 -#: includes/providers/class-opentrust-chat-provider.php:51 -msgid "OpenAI" -msgstr "" - -#: includes/providers/class-opentrust-chat-provider-openai.php:96 -msgid "No chat models available for this key." -msgstr "" - -#: includes/providers/class-opentrust-chat-provider-openai.php:183 -msgid "OpenAI adapter missing required args." -msgstr "" - -#: includes/providers/class-opentrust-chat-provider-openai.php:259 -msgid "OpenAI request failed." -msgstr "" - -#: includes/providers/class-opentrust-chat-provider-openrouter.php:38 -#: includes/providers/class-opentrust-chat-provider.php:57 -msgid "OpenRouter" -msgstr "" - -#: includes/providers/class-opentrust-chat-provider.php:141 -#: includes/providers/class-opentrust-chat-provider.php:160 -#: includes/providers/class-opentrust-chat-provider.php:211 -msgid "Refused outbound request to disallowed host." -msgstr "" - -#. translators: %d: HTTP status code returned by the provider -#: includes/providers/class-opentrust-chat-provider.php:276 -#: includes/providers/class-opentrust-chat-provider.php:302 -#, php-format -msgid "Provider returned HTTP %d" -msgstr "" - -#. translators: %s: company name -#: templates/chat.php:56 -#, fuzzy -#, php-format -msgid "Ask %s — Trust Center" -msgstr "Vraag het aan %s — Trust Center" - -#: templates/chat.php:92 -#: templates/trust-center.php:100 -msgid "Skip to content" -msgstr "" - -#: templates/chat.php:94 -#: templates/partials/policy-single.php:36 -#: templates/trust-center.php:111 -msgid "Trust center navigation" -msgstr "" - -#: templates/chat.php:123 -msgid "Ask AI is not configured" -msgstr "" - -#: templates/chat.php:124 -msgid "The site administrator has not enabled the AI chat feature yet." -msgstr "" - -#: templates/chat.php:126 -msgid "Browse trust center" -msgstr "" - -#: templates/chat.php:154 -#: templates/chat.php:323 -#, fuzzy -msgid "You" -msgstr "Jij" - -#: templates/chat.php:156 -#: templates/chat.php:173 -#: templates/chat.php:324 -#, fuzzy -msgid "just now" -msgstr "zojuist" - -#: templates/chat.php:180 -#: templates/chat.php:316 -#, fuzzy -msgid "Sources" -msgstr "Bronnen" - -#: templates/chat.php:190 -#: templates/chat.php:326 -#, fuzzy -msgid "AI-generated answer. Not legal, security, or compliance advice. Verify against the sources above." -msgstr "Dit antwoord is door AI gegenereerd. Geen juridisch, beveiligings- of compliance-advies. Controleer het altijd aan de hand van de bronnen hierboven." - -#: templates/chat.php:200 -msgid "JavaScript is disabled — you can still ask one question below. The answer will load as a regular page." -msgstr "" - -#: templates/chat.php:203 -msgid "Your question" -msgstr "" - -#: templates/chat.php:210 -msgid "Ask" -msgstr "" - -#: templates/chat.php:221 -#, fuzzy -msgid "Are you SOC 2 compliant?" -msgstr "Zijn jullie SOC 2-compliant?" - -#: templates/chat.php:222 -#, fuzzy -msgid "Where is customer data stored?" -msgstr "Waar worden klantgegevens opgeslagen?" - -#: templates/chat.php:223 -msgid "What's your incident response process?" -msgstr "" - -#: templates/chat.php:224 -#, fuzzy -msgid "Which subprocessors do you use?" -msgstr "Welke subverwerkers gebruiken jullie?" - -#: templates/chat.php:236 -msgid "Suggested questions" -msgstr "" - -#: templates/chat.php:252 -msgid "Ask a question" -msgstr "" - -#: templates/chat.php:259 -#: templates/chat.php:303 -#, fuzzy -msgid "Ask anything about our security and compliance…" -msgstr "Stel een vraag over onze beveiliging en compliance…" - -#: templates/chat.php:261 -#: templates/chat.php:314 -#, fuzzy -msgid "Start a new conversation" -msgstr "Start een nieuw gesprek" - -#: templates/chat.php:264 -#: templates/chat.php:304 -#, fuzzy -msgid "Send" -msgstr "Versturen" - -#. translators: 1: link to trust center, 2: company name -#: templates/chat.php:275 -#, php-format -msgid "Grounded in the published %1$s for %2$s. AI-generated, not legal, security, or compliance advice. Always check the sources." -msgstr "" - -#: templates/chat.php:276 -msgid "trust center" -msgstr "" - -#: templates/chat.php:305 -#, fuzzy -msgid "Stop" -msgstr "Stoppen" - -#: templates/chat.php:306 -#, fuzzy -msgid "Thinking…" -msgstr "Denkt na…" - -#: templates/chat.php:307 -#, fuzzy -msgid "Connection lost. Retry?" -msgstr "Verbinding verbroken. Opnieuw proberen?" - -#: templates/chat.php:308 -#, fuzzy -msgid "Contact security team →" -msgstr "Neem contact op met het beveiligingsteam →" - -#: templates/chat.php:309 -#, fuzzy -msgid "Copy" -msgstr "Kopiëren" - -#: templates/chat.php:310 -#, fuzzy -msgid "Copied" -msgstr "Gekopieerd" - -#: templates/chat.php:311 -#, fuzzy -msgid "Share" -msgstr "Delen" - -#: templates/chat.php:312 -#: templates/partials/policy-single.php:123 -#, fuzzy -msgid "Print" -msgstr "Afdrukken" - -#: templates/chat.php:313 -#, fuzzy -msgid "Link copied" -msgstr "Link gekopieerd" - -#: templates/chat.php:315 -#, fuzzy -msgid "This conversation is getting long. Start fresh for better answers." -msgstr "Dit gesprek wordt lang — begin opnieuw voor de beste antwoorden." - -#: templates/chat.php:317 -#, fuzzy -msgid "I don't see enough information in our trust center to answer that confidently." -msgstr "In ons Trust Center staat niet genoeg informatie om die vraag met zekerheid te beantwoorden." - -#: templates/chat.php:318 -#, fuzzy -msgid "The AI provider returned an error. Please try again." -msgstr "De AI-provider gaf een fout. Probeer het opnieuw." - -#: templates/chat.php:319 -#, fuzzy -msgid "AI is temporarily unavailable. Please try again in a few minutes or browse our published content." -msgstr "De AI is tijdelijk niet beschikbaar. Probeer het over enkele minuten opnieuw of bekijk onze gepubliceerde content." - -#: templates/chat.php:320 -#, fuzzy -msgid "Message is too long." -msgstr "Het bericht is te lang." - -#: templates/chat.php:321 -#, fuzzy -msgid "Please wait a moment before asking again." -msgstr "Wacht even voordat je opnieuw een vraag stelt." - -#: templates/chat.php:322 -#, fuzzy -msgid "Cite source" -msgstr "Bron citeren" - -#: templates/chat.php:325 -#, fuzzy -msgid "No content returned by the model." -msgstr "Het model gaf geen antwoord terug." - -#. translators: %s: company name -#: templates/emails/confirmation.php:41 -#, fuzzy -#, php-format -msgid "Thank you for subscribing to trust center updates from %s. Please confirm your subscription by clicking the button below." -msgstr "Bedankt dat je je hebt aangemeld voor Trust Center-updates van %s. Bevestig je aanmelding door op de knop hieronder te klikken." - -#: templates/emails/confirmation.php:51 -msgid "Confirm subscription" -msgstr "" - -#: templates/emails/confirmation.php:57 -msgid "If the button above does not work, copy and paste this URL into your browser:" -msgstr "" - -#: templates/emails/confirmation.php:63 -msgid "If you did not request this subscription, you can safely ignore this email." -msgstr "" - -#. translators: 1: company name, 2: policy title -#: templates/emails/policy-broadcast.php:43 -#, fuzzy -#, php-format -msgid "%1$s has updated the following policy on our trust center:" -msgstr "%1$s heeft het volgende beleid bijgewerkt op het Trust Center:" - -#: templates/emails/policy-broadcast.php:55 -msgid "Effective date:" -msgstr "" - -#: templates/emails/policy-broadcast.php:65 -msgid "View the updated policy" -msgstr "" - -#: templates/emails/policy-broadcast.php:77 -msgid "Manage preferences" -msgstr "" - -#: templates/emails/policy-broadcast.php:81 -#: templates/partials/subscribe.php:185 -#, fuzzy -msgid "Unsubscribe" -msgstr "Uitschrijven" - -#: templates/partials/certifications.php:22 -#, fuzzy -msgid "Our active certifications and compliance frameworks demonstrate our commitment to protecting your data." -msgstr "Onze actieve certificeringen en compliance-frameworks laten zien hoe serieus we jouw gegevens beschermen." - -#: templates/partials/certifications.php:59 -#, fuzzy -msgid "Compliant" -msgstr "Compliant" - -#. translators: %s: certification issue date -#: templates/partials/certifications.php:77 -#, fuzzy -#, php-format -msgid "Issued %s" -msgstr "Uitgegeven op %s" - -#. translators: %s: certification expiry date -#: templates/partials/certifications.php:81 -#, fuzzy -#, php-format -msgid "Expires %s" -msgstr "Verloopt op %s" - -#: templates/partials/chat-budget-exhausted.php:18 -msgid "Ask AI is taking a breather" -msgstr "" - -#: templates/partials/chat-budget-exhausted.php:19 -msgid "We've hit the daily question limit. Chat will be back soon — in the meantime, you can still browse the full trust center." -msgstr "" - -#: templates/partials/chat-budget-exhausted.php:22 -msgid "Browse policies" -msgstr "" - -#: templates/partials/chat-budget-exhausted.php:29 -msgid "Contact us" -msgstr "" - -#. translators: %s: company name -#: templates/partials/chat-empty-state.php:29 -#, fuzzy -#, php-format -msgid "Ask about %s's security and compliance" -msgstr "Vraag ons over de beveiliging en compliance van %s" - -#. translators: 1: model identifier, 2: sources summary -#: templates/partials/chat-empty-state.php:37 -#, fuzzy -#, php-format -msgid "Powered by %1$s. Grounded in %2$s." -msgstr "Mogelijk gemaakt door %1$s. Gebaseerd op %2$s." - -#. translators: %s: sources summary -#: templates/partials/chat-empty-state.php:49 -#, fuzzy -#, php-format -msgid "Grounded in %s." -msgstr "Gebaseerd op %s." - -#: templates/partials/chat-empty-state.php:60 -msgid "Conversation" -msgstr "" - -#: templates/partials/data-practices.php:35 -#, fuzzy -msgid "What we collect and how we handle your data." -msgstr "Welke gegevens we verzamelen en hoe we ze gebruiken." - -#. translators: %1$d = number, %2$s = category name -#: templates/partials/data-practices.php:73 -#, fuzzy -#, php-format -msgid "View %1$d more %2$s items" -msgstr "Bekijk nog %1$d %2$s-items" - -#: templates/partials/data-practices.php:110 -#, fuzzy -msgid "Retention" -msgstr "Bewaartermijn" - -#. translators: %d: number of active certifications -#: templates/partials/hero.php:31 -#, fuzzy -#, php-format -msgid "%d Active Certification" -msgid_plural "%d Active Certifications" -msgstr[0] "%d Actieve certificering" -msgstr[1] "%d Actieve certificeringen" - -#: templates/partials/hero.php:38 -#: templates/trust-center.php:20 -#, fuzzy -msgid "Trust Center" -msgstr "Trust Center" - -#: templates/partials/policies.php:21 -#, fuzzy -msgid "Security Policies" -msgstr "Beveiligingsbeleid" - -#: templates/partials/policies.php:22 -#, fuzzy -msgid "Our published security and compliance policies are regularly reviewed and updated." -msgstr "Ons gepubliceerde beveiligings- en compliancebeleid wordt regelmatig herzien en bijgewerkt." - -#: templates/partials/policies.php:39 -#, fuzzy -msgid "Last Updated" -msgstr "Laatst bijgewerkt" - -#. translators: %s: policy version number -#: templates/partials/policies.php:63 -#, fuzzy -#, php-format -msgid "v%s" -msgstr "v%s" - -#: templates/partials/policies.php:67 -#: templates/partials/policy-single.php:119 -#, fuzzy -msgid "Download PDF" -msgstr "Download PDF" - -#. translators: %s: policy title -#: templates/partials/policies.php:73 -#, fuzzy -#, php-format -msgid "View %s" -msgstr "%s bekijken" - -#. translators: %1$s: version number, %2$s: link to current version -#: templates/partials/policy-single.php:66 -#, fuzzy -#, php-format -msgid "You are viewing version %1$s. %2$s" -msgstr "Je bekijkt versie %1$s. %2$s" - -#: templates/partials/policy-single.php:68 -#: templates/partials/policy-single.php:81 -#, fuzzy -msgid "View current version" -msgstr "Huidige versie bekijken" - -#. translators: %1$s: effective date, %2$s: link to previous version -#: templates/partials/policy-single.php:78 -#, fuzzy -#, php-format -msgid "This version takes effect on %1$s. %2$s" -msgstr "Deze versie gaat in op %1$s. %2$s" - -#. translators: %s: policy effective date -#: templates/partials/policy-single.php:104 -#, fuzzy -#, php-format -msgid "Effective: %s" -msgstr "Ingangsdatum: %s" - -#. translators: %s: policy last updated date -#: templates/partials/policy-single.php:111 -#, fuzzy -#, php-format -msgid "Updated: %s" -msgstr "Bijgewerkt: %s" - -#. translators: %d: number of policy versions -#: templates/partials/policy-single.php:135 -#, fuzzy -#, php-format -msgid "Version history (%d)" -msgstr "Versiegeschiedenis (%d)" - -#. translators: %d: version number -#: templates/partials/policy-single.php:150 -#, fuzzy -#, php-format -msgid "v%d" -msgstr "v%d" - -#: templates/partials/subprocessors.php:21 -#, fuzzy -msgid "Third-party services that process data on our behalf, along with their purposes and data handling agreements." -msgstr "Externe diensten die namens ons gegevens verwerken, met hun doel en verwerkersovereenkomsten." - -#: templates/partials/subprocessors.php:53 -#: templates/partials/subprocessors.php:57 -#, fuzzy -msgid "more" -msgstr "meer" - -#: templates/partials/subprocessors.php:64 -#, fuzzy -msgid "Signed" -msgstr "Ondertekend" - -#: templates/partials/subscribe-cta.php:21 -msgid "Stay informed" -msgstr "" - -#: templates/partials/subscribe-cta.php:23 -msgid "Get notified when we update our policies, add subprocessors, or change our compliance posture." -msgstr "" - -#: templates/partials/subscribe-cta.php:29 -#: templates/partials/subscribe.php:30 -#, fuzzy -msgid "Subscribe to updates" -msgstr "Abonneer op updates" - -#: templates/partials/subscribe-cta.php:31 -msgid "RSS Feed" -msgstr "" - -#: templates/partials/subscribe-cta.php:33 -msgid "RSS" -msgstr "" - -#. translators: %s: company name -#: templates/partials/subscribe.php:34 -#, fuzzy -#, php-format -msgid "Get email notifications when %s updates their trust center." -msgstr "Ontvang e-mailmeldingen zodra %s hun Trust Center bijwerkt." - -#: templates/partials/subscribe.php:50 -#, fuzzy -msgid "Email address" -msgstr "E-mailadres" - -#: templates/partials/subscribe.php:52 -msgid "you@company.com" -msgstr "" - -#: templates/partials/subscribe.php:72 -#: templates/partials/subscribe.php:165 -msgid "Notify me about" -msgstr "" - -#: templates/partials/subscribe.php:89 -#: templates/partials/subscribe.php:115 -#, fuzzy -msgid "Subscribe" -msgstr "Abonneren" - -#: templates/partials/subscribe.php:93 -msgid "We will only use your email to send trust center updates. You can unsubscribe at any time." -msgstr "" - -#: templates/partials/subscribe.php:105 -msgid "Subscription confirmed!" -msgstr "" - -#: templates/partials/subscribe.php:106 -msgid "You will now receive email notifications when our trust center is updated." -msgstr "" - -#: templates/partials/subscribe.php:113 -msgid "Invalid or expired link" -msgstr "" - -#: templates/partials/subscribe.php:114 -msgid "This confirmation link is no longer valid. Please subscribe again." -msgstr "" - -#: templates/partials/subscribe.php:126 -msgid "Unsubscribed" -msgstr "" - -#: templates/partials/subscribe.php:127 -msgid "You have been unsubscribed and will no longer receive trust center updates." -msgstr "" - -#: templates/partials/subscribe.php:134 -msgid "Invalid link" -msgstr "" - -#: templates/partials/subscribe.php:135 -msgid "This unsubscribe link is not valid." -msgstr "" - -#: templates/partials/subscribe.php:146 -msgid "Notification preferences" -msgstr "" - -#. translators: %s: email address -#: templates/partials/subscribe.php:150 -#, fuzzy -#, php-format -msgid "Manage notifications for %s." -msgstr "Beheer meldingen voor %s." - -#: templates/partials/subscribe.php:176 -msgid "Save preferences" -msgstr "" - -#: templates/partials/subscribe.php:183 -msgid "Want to stop all notifications?" -msgstr "" - -#: templates/trust-center.php:22 -#, fuzzy -msgid "Transparency and security you can trust." -msgstr "Transparante beveiliging om op te vertrouwen." - -#: templates/trust-center.php:141 -#: templates/trust-center.php:239 -#, fuzzy -msgid "Ask AI" -msgstr "Vraag de AI" - -#: templates/trust-center.php:194 -msgid "Trust center content is being prepared. Check back soon." -msgstr "" - -#. translators: %s: company name -#: templates/trust-center.php:215 -#, fuzzy -#, php-format -msgid "© %1$s %2$s. All rights reserved." -msgstr "© %1$s %2$s. Alle rechten voorbehouden." - -#: templates/trust-center.php:222 -msgid "Powered by OpenTrust" -msgstr "" - diff --git a/open-trust-center-by-ettic.php b/open-trust-center-by-ettic.php new file mode 100644 index 0000000..a730e16 --- /dev/null +++ b/open-trust-center-by-ettic.php @@ -0,0 +1,96 @@ + > @@ -67,7 +67,7 @@ <?php echo esc_html(sprintf( /* translators: %s: company name */ - __('Ask %s — Trust Center', 'opentrust'), + __('Ask %s — Trust Center', 'open-trust-center-by-ettic'), $ot_company_name ?: get_bloginfo('name') )); ?> @@ -75,131 +75,131 @@ 'defer']); - wp_print_scripts('opentrust-turnstile'); + wp_register_script('ettic-otc-turnstile', 'https://challenges.cloudflare.com/turnstile/v0/api.js', [], null, ['strategy' => 'defer']); + wp_print_scripts('ettic-otc-turnstile'); ?> - + - + -
vv post_modified))); ?>
v post_modified))); ?> - - + +  |  - - + +
+
+
@@ -49,13 +49,13 @@ - + - - - - - + + + + + @@ -74,21 +74,21 @@ + printf(esc_html__('v%s', 'open-trust-center-by-ettic'), esc_html((string) $ot_policy['version'])); ?> - diff --git a/templates/partials/policy-single.php b/templates/partials/policy-single.php index 8ac9f5e..e475baa 100644 --- a/templates/partials/policy-single.php +++ b/templates/partials/policy-single.php @@ -19,7 +19,7 @@ $ot_is_pending = $ot_data['is_pending'] ?? false; $ot_versions = $ot_data['policy_versions'] ?? []; -$ot_category_labels = OpenTrust_Render::policy_category_labels(); +$ot_category_labels = Ettic_OTC_Render::policy_category_labels(); $ot_category_label = $ot_category_labels[$ot_meta['category']] ?? $ot_meta['category']; $ot_ref_id = (string) ($ot_meta['ref_id'] ?? ''); $ot_citations = $ot_meta['citations'] ?? []; @@ -33,145 +33,145 @@ $ot_current_url = trailingslashit($ot_base_url) . 'policy/' . $ot_policy->post_name . '/'; ?> -
- + - + - - + + -
    +
      -
    • +
    @@ -96,24 +96,24 @@
+ - " class="ettic-otc-policy-actions__pdf" title="" download> - " class="ettic-otc-policy-actions__view" aria-label="">→ + echo esc_attr(sprintf(__('View %s', 'open-trust-center-by-ettic'), $ot_policy['title'])); ?>">→
+
+
@@ -33,12 +33,12 @@ - - - - - - + + + + + + @@ -48,23 +48,23 @@ ?> - -
- - + + + - - + + + - + - + - + diff --git a/templates/trust-center.php b/templates/trust-center.php index e7ed859..cb809f2 100644 --- a/templates/trust-center.php +++ b/templates/trust-center.php @@ -16,9 +16,9 @@ $ot_hsl = $ot_data['hsl']; $ot_view = $ot_data['view'] ?? 'main'; -$ot_page_title = (string) (($ot_settings['page_title'] ?? '') ?: __('Trust Center', 'opentrust')); +$ot_page_title = (string) (($ot_settings['page_title'] ?? '') ?: __('Trust Center', 'open-trust-center-by-ettic')); $ot_company_name = (string) ($ot_settings['company_name'] ?? ''); -$ot_tagline = (string) (($ot_settings['tagline'] ?? '') ?: __('Transparency and security you can trust.', 'opentrust')); +$ot_tagline = (string) (($ot_settings['tagline'] ?? '') ?: __('Transparency and security you can trust.', 'open-trust-center-by-ettic')); $ot_logo_url = $ot_data['logo_url'] ?? ''; $ot_base_url = $ot_data['base_url'] ?? '/'; @@ -48,8 +48,8 @@ $ot_ai_enabled = !empty($ot_settings['ai_enabled']) && !empty($ot_settings['ai_provider']) && !empty($ot_settings['ai_model']) - && class_exists('OpenTrust_Chat_Secrets') - && OpenTrust_Chat_Secrets::get((string) $ot_settings['ai_provider']) !== null; + && class_exists('Ettic_OTC_Chat_Secrets') + && Ettic_OTC_Chat_Secrets::get((string) $ot_settings['ai_provider']) !== null; $ot_accent_contrast = ((int) $ot_hsl['l'] < 55) ? '#ffffff' : '#111827'; @@ -62,7 +62,7 @@ // WCAG adjustment — keep their exact colour everywhere, contrast be damned. $ot_accent_l_safe = !empty($ot_settings['accent_force_exact']) ? (int) $ot_hsl['l'] - : OpenTrust::accent_safe_lightness((string) ($ot_settings['accent_color'] ?? '#2563EB')); + : Ettic_OTC::accent_safe_lightness((string) ($ot_settings['accent_color'] ?? '#2563EB')); ?> > @@ -83,65 +83,65 @@ - + - + -