feat(rest): POST /cdcf/v1/link-term-translations + Python client method#205
Conversation
Term equivalent of /cdcf/v1/link-translations. Polylang's term-side language + group helpers (pll_set_term_language, pll_save_term_translations) are PHP-only — there's no native REST surface for them, so corrupted term Polylang groups (like the post-#201 ConfessIt state where term 171 "examen" ended up language=fr with no en sibling in its translation group) can't be repaired from a Python script alone. This adds the thin REST wrapper that fixes that. Endpoint shape: POST /cdcf/v1/link-term-translations Auth: edit_posts capability (same baseline as /link-translations) Body: { "taxonomy": "project_tag", "translations": { "en": 169, "it": 231, "es": 241, ... } } Returns: { success: true, taxonomy, translations } on success; WP_Error with appropriate HTTP status on validation/save failure. Validation matches /link-translations' shape: - polylang_missing (500): pll_set_term_language or pll_save_term_translations not defined - invalid_taxonomy (400): taxonomy slug missing or doesn't exist - invalid_translations (400): not an array, or fewer than 2 entries - invalid_term (400): any term_id doesn't exist in the named taxonomy (also catches get_term WP_Error return) - link_failed (500): pll_save_term_translations returned false 9 tests covering each WP_Error path + the happy path (sets language on each term in order, then one atomic save with the full map, returns the validated translations dict). 557/557 theme suite green. Python client + CLI: - CdcfClient.link_term_translations(taxonomy, translations) wraps the POST - `scripts/cdcf_api.py link-term-translations --taxonomy X --translations '{...}'` Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 17 minutes and 16 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis PR introduces a new feature for atomically linking Polylang translation term IDs within a taxonomy. The change spans a REST handler, endpoint registration, Python CLI client, and comprehensive test coverage across all layers. ChangesPolylang Term Translation Linking
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 27 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@wordpress/themes/cdcf-headless/functions.php`:
- Around line 442-458: Update the register_rest_route args for the translations
parameter to add a sanitize_callback that enforces and returns a safe
associative array: inside the callback (referenced from the
cdcf/v1/link-term-translations and cdcf/v1/link-translations route registrations
in functions.php) first decode/ensure the input is an associative array with at
least two entries, then validate each language key against a strict pattern or
whitelist (e.g., /^[a-z]{2}(-[A-Z]{2})?$/ or your Polylang-allowed codes) and
remove/reject invalid keys, and for each value cast/validate IDs using absint or
numeric checks (dropping non-numeric/zero values); return the sanitized map or
null/wp_error on failure so includes/handlers/link-term-translations.php and the
link-translations handler receive only validated language codes and integer IDs.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 635fc0b5-bfdf-433d-beff-1450cc209719
📒 Files selected for processing (5)
scripts/cdcf_api.pywordpress/themes/cdcf-headless/functions.phpwordpress/themes/cdcf-headless/includes/handlers/link-term-translations.phpwordpress/themes/cdcf-headless/tests/LinkTermTranslationsHandlerTest.phpwordpress/themes/cdcf-headless/tests/bootstrap.php
CodeRabbit on #205 flagged that the `translations` arg on both /cdcf/v1/link-translations and /cdcf/v1/link-term-translations had only type=object declared at register_rest_route — no sanitize_callback — which violates the convention CLAUDE.md settled in #111 ("Every cdcf/v1 route declares its sanitize_callback per field in the args block; handlers trust the sanitized input and do NOT re-sanitize"). Adds a shared helper cdcf_sanitize_translations_map() in a new includes/sanitizers.php: - Non-array input → empty array - Each key must match /^[a-z]{2}(-[A-Z]{2})?$/ (ISO 639-1 alone plus optional ISO 3166-1 region; matches Polylang slug shape) - Each value coerced via absint(); zero/negative dropped silently - Invalid entries are silently dropped (the handlers already return contextual WP_Error on count < 2, so the registration-time drop plus handler-level structural check still produces meaningful 400s on bad input) Applied as sanitize_callback to BOTH route registrations — the helper is shared, so adding to both is trivial overhead. Handlers unchanged per CLAUDE.md's no-defense-in-depth-re-sanitization rule (the existing (int) casts in the handlers are now redundant but harmless; leaving them avoids a wider change to this PR's scope). 8 new tests in SanitizersTest.php covering: - non-array/null/int/object input → empty array - empty array → empty array - well-formed map passes through with int coercion (string + float) - ISO 639-1 alone + ISO 639-1 + ISO 3166-1 region (en-US, pt-BR, etc.) - drops malformed keys (3-letter, uppercase, digits, underscore-region, 3-letter-region, empty) - drops non-string keys (int, numeric-string PHP-coerced to int) - drops zero/negative/non-numeric values via absint - 6-lang production map round-trips intact 565/565 theme suite green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CodeRabbit on #207 flagged that translate_all accepts any integer and forwards it directly to /cdcf/v1/translate-all. The backend's absint() sanitization silently coerces a negative ID into a different positive ID, which would enqueue translations for the wrong source post — a real silent-data-corruption defect. Adds an explicit `source_id <= 0` guard that raises ValueError with a clear message. The CLI (argparse type=int) already constrained input to integers, but library callers had no protection; this closes that gap. Other client methods that take IDs (link_translations, link_term_translations) are already protected by #205's cdcf_sanitize_translations_map dropping non-positive values — only translate_all sent a single scalar through with no sanitizer in path. Smoke-tested: source_id=0, -1, -1000 all raise ValueError before any HTTP call. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Adds the term equivalent of the existing
/cdcf/v1/link-translationspost endpoint. Polylang's term-side language + group helpers (pll_set_term_language,pll_save_term_translations) are PHP-only — there's no native REST surface for them — so corrupted Polylang term groups (like the post-#201 ConfessIt state where term 171examenended uplanguage=frwith noenentry in its group) can't be repaired from a Python script alone. This is the thin REST wrapper that closes the gap.Endpoint
Returns
{ success: true, taxonomy, translations }on success, orWP_Errorwith appropriate HTTP status on validation/save failure.Validation mirrors
/link-translations:polylang_missingpll_set_term_languageorpll_save_term_translationsundefinedinvalid_taxonomyinvalid_translationsinvalid_termget_termWP_Error)link_failedpll_save_term_translationsreturnedfalseTest plan
php -lclean on the new handler +functions.phpcomposer test --working-dir=wordpress/themes/cdcf-headless— 557/557 pass (+9 new tests covering eachWP_Errorpath + happy path with per-term language set order + atomic save with the full map)examinationsiblings for IT/ES/FR/PTPython client + CLI
scripts/cdcf_api.py link-term-translations \ --taxonomy project_tag \ --translations '{"en":169,"it":231,"es":241,"fr":252,"pt":264,"de":275}'Deploy
Backend (theme) change — ship via
gh workflow run deploy.yml -f environment=productionafter merge. A baredeploy.ymlrun defaults to staging and skips the WP theme steps.Related
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests