feat(theme): point admin "View Post" links at the headless frontend#134
Conversation
In headless mode WordPress still served its own permalink (cms.catholicdigitalcommons.org/<slug>/) for the admin "View Post" link, sending editors to a dead WP-side URL. Add a post_link filter that rewrites a published post's permalink to the Next.js frontend at /blog/<slug>, with an /<lang> prefix for non-default Polylang locales — the same slug→path mapping as app/api/preview/route.ts. This covers the block editor (REST `link` field), classic editor, admin bar, and the post-list "View" row action. The filter bails on WPGraphQL requests: the frontend consumes the unmodified `uri` field (derived from the permalink — e.g. the page sitemap), so rewriting permalinks during a GraphQL request would corrupt that data. REST requests, which feed the block-editor link, are not GraphQL requests and so still get the frontend URL. Scope is posts only (flat /blog/ route); pages and CPTs keep their default permalinks. Bodies live in includes/frontend-permalinks.php so they're unit-tested (tests/FrontendPostPermalinkTest.php, 6 cases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis pull request adds a WordPress-to-Next.js post permalink rewriting system. Published posts that match the ChangesPost Permalink Rewriting
Sequence DiagramsequenceDiagram
participant WP as WordPress
participant Filter as cdcf_filter_post_permalink
participant URLGen as cdcf_frontend_post_url
participant Frontend as Next.js Frontend
WP->>Filter: post_link hook with permalink and post
alt GraphQL Request
Filter-->>WP: return original permalink unchanged
else Regular Request
Filter->>URLGen: construct frontend URL for post
alt Published post with slug
URLGen-->>Filter: /blog/<slug> with optional locale prefix
else Ineligible post
URLGen-->>Filter: null
end
alt Frontend URL generated
Filter-->>WP: rewritten frontend URL
else No frontend URL available
Filter-->>WP: original permalink
end
end
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes 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 docstrings
🧪 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 | 21 |
| Duplication | 0 |
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/includes/frontend-permalinks.php`:
- Around line 44-50: The code currently treats 'en' as the default language when
building the $prefix; instead of hardcoding 'en' call
pll_default_language('slug') and compare $lang against that value (use
pll_get_post_language($post->ID, 'slug') for $lang as already written), so set
$default = function_exists('pll_default_language') ?
pll_default_language('slug') : 'en' (or empty string) and make $prefix = ($lang
&& $lang !== $default) ? '/' . $lang : ''; update the return that concatenates
$frontend, $prefix and '/blog/' . $post->post_name accordingly.
🪄 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: 92253cfd-040a-4e95-b53e-06bf7a57346a
📒 Files selected for processing (4)
wordpress/themes/cdcf-headless/functions.phpwordpress/themes/cdcf-headless/includes/frontend-permalinks.phpwordpress/themes/cdcf-headless/tests/FrontendPostPermalinkTest.phpwordpress/themes/cdcf-headless/tests/bootstrap.php
| // Polylang language slug ("en", "it", …); empty when Polylang is off. | ||
| $lang = function_exists('pll_get_post_language') | ||
| ? pll_get_post_language($post->ID, 'slug') | ||
| : ''; | ||
| $prefix = ($lang && $lang !== 'en') ? '/' . $lang : ''; | ||
|
|
||
| return $frontend . $prefix . '/blog/' . $post->post_name; |
There was a problem hiding this comment.
Use pll_default_language('slug') instead of hardcoding 'en' as the default language.
Line 48 assumes the default language is always 'en', but this breaks if the site is configured with a different default language. The codebase pattern (seen in includes/translation.php:127) is to call pll_default_language('slug') to retrieve the actual default dynamically.
🔧 Proposed fix
// Polylang language slug ("en", "it", …); empty when Polylang is off.
$lang = function_exists('pll_get_post_language')
? pll_get_post_language($post->ID, 'slug')
: '';
- $prefix = ($lang && $lang !== 'en') ? '/' . $lang : '';
+
+ $default_lang = function_exists('pll_default_language')
+ ? pll_default_language('slug')
+ : 'en';
+ $prefix = ($lang && $lang !== $default_lang) ? '/' . $lang : '';
return $frontend . $prefix . '/blog/' . $post->post_name;Based on learnings, the theme uses pll_default_language('slug') to determine the default language throughout (e.g., includes/translation.php:127).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Polylang language slug ("en", "it", …); empty when Polylang is off. | |
| $lang = function_exists('pll_get_post_language') | |
| ? pll_get_post_language($post->ID, 'slug') | |
| : ''; | |
| $prefix = ($lang && $lang !== 'en') ? '/' . $lang : ''; | |
| return $frontend . $prefix . '/blog/' . $post->post_name; | |
| // Polylang language slug ("en", "it", …); empty when Polylang is off. | |
| $lang = function_exists('pll_get_post_language') | |
| ? pll_get_post_language($post->ID, 'slug') | |
| : ''; | |
| $default_lang = function_exists('pll_default_language') | |
| ? pll_default_language('slug') | |
| : 'en'; | |
| $prefix = ($lang && $lang !== $default_lang) ? '/' . $lang : ''; | |
| return $frontend . $prefix . '/blog/' . $post->post_name; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wordpress/themes/cdcf-headless/includes/frontend-permalinks.php` around lines
44 - 50, The code currently treats 'en' as the default language when building
the $prefix; instead of hardcoding 'en' call pll_default_language('slug') and
compare $lang against that value (use pll_get_post_language($post->ID, 'slug')
for $lang as already written), so set $default =
function_exists('pll_default_language') ? pll_default_language('slug') : 'en'
(or empty string) and make $prefix = ($lang && $lang !== $default) ? '/' . $lang
: ''; update the return that concatenates $frontend, $prefix and '/blog/' .
$post->post_name accordingly.
Problem
In headless mode WordPress still hands out its own permalink for the admin View Post link. Editing a post and clicking the View icon sends you to e.g.
https://cms.catholicdigitalcommons.org/welcome-to-the-catholic-digital-commons-foundation/— a dead WP-side URL — instead of the live frontendhttps://catholicdigitalcommons.org/blog/welcome-to-the-catholic-digital-commons-foundation.A
preview_post_linkfilter already routed drafts to Next.js draft mode, but there was nopost_linkfilter for published posts.Fix
New
includes/frontend-permalinks.php(registered viaadd_filter('post_link', …)infunctions.php):cdcf_frontend_post_url($post)— maps a published post toCDCF_FRONTEND_URL+/blog/<slug>, with an/<lang>prefix for non-default Polylang locales. Mirrors the slug→path mapping inapp/api/preview/route.ts:59-61so preview and published URLs agree.cdcf_filter_post_permalink()— the filter. Bails on WPGraphQL requests: the frontend consumes the unmodifiedurifield (derived from the permalink — e.g. the page sitemap inlib/wordpress/api.ts), so rewriting permalinks during a GraphQL request would corrupt that data. REST requests — which feed the block editor's View Post link via thelinkfield — are not GraphQL requests, so they still get the frontend URL.This covers the block editor, classic editor, admin bar, and the post-list "View" row action. Only
publish-status posts with a real slug are rewritten; drafts keep WP behavior and use the existing preview link.Scope
Posts only (the reported case;
/blog/<slug>is flat and unambiguous). Pages have the same broken link but can be hierarchical (parent/child) — left for a follow-up rather than guessing the mapping. CPTs keep their default permalinks. Frontend URL is emitted without a trailing slash to match the frontend's canonical routing (sitemap, internal links).Testing
tests/FrontendPostPermalinkTest.php(6 cases): en + non-default locale mapping, non-post types, unpublished/slugless opt-out, the rewrite, and the GraphQL bail. Registered in the test bootstrap.composer test: 342 tests pass.php -lclean on all changed files.Deployment
This is a WordPress theme change, so it only reaches the live
cms.host on a production deploy (if: env.ENVIRONMENT == 'production') — staging shares the prod WP backend and doesn't ship the theme.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests