Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,18 @@ function cdcf_repend_submission_on_untrash($new_status, $old_status, $post): voi
* Only acts on the English source post — when the worker promotes
* a translation sibling to publish, this hook fires again, but
* there's nothing more to enqueue at that point.
*
* The actual enqueue work is deferred to `shutdown` rather than run
* synchronously here. Running it inline puts our nested wp_insert_post
* + pll_save_post_translations calls inside Polylang's own save_post
* chain for the source post (and each freshly-inserted draft), where
* the multi-post group save silently fails to persist. Observed on
* production 2026-06-16: FamilyGraph submission 1381 was published
* with EN/IT/ES/FR/PT/DE siblings created and worker-translated, but
* the Polylang translation group came out empty on all six posts and
* Phase 0's attachment translation siblings were never created. The
* identical pll_save_post_translations call from outside the save
* chain (via /cdcf/v1/link-translations) persisted on the first try.
*/
function cdcf_enqueue_translations_on_publish($new_status, $old_status, $post): void {
if ($new_status === $old_status) {
Expand All @@ -480,7 +492,10 @@ function cdcf_enqueue_translations_on_publish($new_status, $old_status, $post):
return;
}

cdcf_enqueue_translations_for_submission($source_id, $post->post_type);
$post_type = $post->post_type;
add_action('shutdown', static function () use ($source_id, $post_type): void {
cdcf_enqueue_translations_for_submission($source_id, $post_type);
});
}

/**
Expand Down
37 changes: 36 additions & 1 deletion wordpress/themes/cdcf-headless/tests/SubmissionLifecycleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -886,8 +886,20 @@ public function test_publish_hook_skips_non_public_submission_posts(): void
$this->assertTrue(true);
}

public function test_publish_hook_enqueues_translations_for_public_submission(): void
public function test_publish_hook_defers_enqueue_to_shutdown_for_public_submission(): void
{
// The publish hook MUST defer cdcf_enqueue_translations_for_submission to
// the `shutdown` action — running it synchronously inside
// transition_post_status puts our pll_save_post_translations call inside
// Polylang's own nested save_post chain for the source post + each
// freshly-inserted draft, which silently drops the multi-post group save.
//
// Observed in production 2026-06-16: FamilyGraph submission 1381 was
// published with EN/IT/ES/FR/PT/DE siblings created and worker-translated,
// but the Polylang group came out empty on all six posts AND Phase 0's
// attachment translation siblings were never created. Re-running the
// identical pll_save_post_translations call from outside the save chain
// (via /cdcf/v1/link-translations) persisted on the first attempt.
$this->stubCommonFunctions();
Functions\when('pll_get_post')->justReturn(0); // self-source
Functions\when('get_post_meta')->alias(
Expand All @@ -896,6 +908,15 @@ public function test_publish_hook_enqueues_translations_for_public_submission():
: ''
);

$shutdownCallback = null;
Functions\when('add_action')->alias(
function (string $hook, callable $cb) use (&$shutdownCallback): void {
if ($hook === 'shutdown') {
$shutdownCallback = $cb;
}
}
);

$enqueueCall = null;
Functions\when('cdcf_enqueue_translations_for_submission')->alias(
function (int $en_id, string $type) use (&$enqueueCall): void {
Expand All @@ -906,6 +927,20 @@ function (int $en_id, string $type) use (&$enqueueCall): void {

cdcf_enqueue_translations_on_publish('publish', 'draft', $this->fakePost());

// Synchronously: NOT called.
$this->assertNull(
$enqueueCall,
'cdcf_enqueue_translations_for_submission must NOT be invoked synchronously inside transition_post_status — it must be deferred to the `shutdown` action so Polylang\'s save_post chain settles first.'
);

// A shutdown callback was registered.
$this->assertNotNull(
$shutdownCallback,
'cdcf_enqueue_translations_on_publish must call add_action(\'shutdown\', $callback) to defer the enqueue out of the post-save chain.'
);

// Driving the deferred callback fires the enqueue with the source post's args.
$shutdownCallback();
$this->assertSame([100, 'project'], $enqueueCall);
}

Expand Down