From 0806e81c2cacd714703af8f4eadde6803f20b3b1 Mon Sep 17 00:00:00 2001 From: "John R. D'Orazio" Date: Tue, 16 Jun 2026 16:21:12 -0400 Subject: [PATCH] fix(submissions): walk the polylang group when checking submitter meta MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cdcf_is_public_submission() resolved its post to "the EN sibling" via cdcf_get_source_post_id() and read submitter meta from there. For a non-English public submission the meta lives on the originally-submitted post (the language the human used), and the auto-translated EN sibling has no meta — so the gate returned false for every sibling whose own post lacked the meta. cdcf_link_referral_on_publish then bailed and the sibling was never appended to its language's projects page community_projects relationship field. Now walks pll_get_post_translations() for the entire group and returns true if ANY sibling carries _submission_submitter_email or _referral_submitter_email. Falls back to a single-post check when Polylang is missing or the post is in no group. Production trigger 2026-06-16: community_project 1534 ("Enciclopedia Católica") submitted in Spanish. ES sibling 1534 carried the referral meta and was appended to /es/proyectos. EN sibling 1556 (and IT/FR/PT/DE) got auto-translated and worker-promoted to publish, but the publish hook saw no meta on the EN-resolved post and bailed — so the encyclopedia never appeared on /projects, /it/progetti, /fr/projets, /pt/projetos, /de/projekte. Replaces the old test_is_public_submission_resolves_translation_to_source_via_polylang with test_is_public_submission_true_when_any_sibling_in_the_polylang_group_carries_submitter_meta covering the production scenario (ES carries meta, called with the EN sibling, also with another non-source sibling, also with the source itself). The three legacy single-post tests gain a pll_get_post_translations stub that returns an empty group, exercising the new fallback path. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../includes/admin/submission-lifecycle.php | 46 +++++++++++++--- .../tests/SubmissionLifecycleTest.php | 54 +++++++++++++------ 2 files changed, 76 insertions(+), 24 deletions(-) diff --git a/wordpress/themes/cdcf-headless/includes/admin/submission-lifecycle.php b/wordpress/themes/cdcf-headless/includes/admin/submission-lifecycle.php index f865dd5..d378b53 100644 --- a/wordpress/themes/cdcf-headless/includes/admin/submission-lifecycle.php +++ b/wordpress/themes/cdcf-headless/includes/admin/submission-lifecycle.php @@ -36,16 +36,46 @@ function cdcf_get_source_post_id(int $post_id): int { } /** - * True if the source (EN) post has submitter meta from the public - * submission/referral form. Works whether called with the EN post ID - * or a translation's ID — resolves to source via cdcf_get_source_post_id(). + * True if any post in this post's Polylang translation group carries + * submitter meta from the public submission/referral form. + * + * The submitter meta is written ONCE on the originally-submitted post + * — which is whatever language the human submitted in. A Spanish + * submission carries `_referral_submitter_email` on the ES post; the + * auto-translated EN/IT/FR/PT/DE siblings have no meta. So the helper + * walks the whole Polylang group and returns true if ANY sibling has + * the meta — not just the EN one — so the publish hook can recognize + * the submission regardless of which language sibling fired it. + * + * Production occurrence 2026-06-16: community_project 1534 was submitted + * in Spanish; the worker-promoted EN sibling 1556 failed the old + * EN-walk check and was never appended to /projects's + * community_projects field. + * + * Falls back to a single-post check when Polylang isn't active or the + * post is in no group. */ function cdcf_is_public_submission(int $post_id): bool { - $source_id = cdcf_get_source_post_id($post_id); - return (bool) ( - get_post_meta($source_id, '_submission_submitter_email', true) - || get_post_meta($source_id, '_referral_submitter_email', true) - ); + $candidate_ids = [$post_id]; + if (function_exists('pll_get_post_translations')) { + $group = pll_get_post_translations($post_id); + if (is_array($group) && !empty($group)) { + $candidate_ids = array_map('intval', array_values($group)); + } + } + + foreach ($candidate_ids as $id) { + if ($id <= 0) { + continue; + } + if ( + get_post_meta($id, '_submission_submitter_email', true) + || get_post_meta($id, '_referral_submitter_email', true) + ) { + return true; + } + } + return false; } /** diff --git a/wordpress/themes/cdcf-headless/tests/SubmissionLifecycleTest.php b/wordpress/themes/cdcf-headless/tests/SubmissionLifecycleTest.php index 403f0ad..dcf7cac 100644 --- a/wordpress/themes/cdcf-headless/tests/SubmissionLifecycleTest.php +++ b/wordpress/themes/cdcf-headless/tests/SubmissionLifecycleTest.php @@ -117,6 +117,7 @@ public function test_get_source_post_id_falls_back_to_input_when_pll_returns_zer public function test_is_public_submission_true_when_submission_email_present(): void { Functions\when('pll_get_post')->justReturn(0); + Functions\when('pll_get_post_translations')->justReturn([]); Functions\when('get_post_meta')->alias( static fn(int $id, string $key) => $key === '_submission_submitter_email' ? 'user@example.com' @@ -130,6 +131,7 @@ public function test_is_public_submission_true_when_submission_email_present(): public function test_is_public_submission_true_when_referral_email_present(): void { Functions\when('pll_get_post')->justReturn(0); + Functions\when('pll_get_post_translations')->justReturn([]); Functions\when('get_post_meta')->alias( static fn(int $id, string $key) => $key === '_referral_submitter_email' ? 'referrer@example.com' @@ -143,35 +145,55 @@ public function test_is_public_submission_true_when_referral_email_present(): vo public function test_is_public_submission_false_when_no_submitter_meta(): void { Functions\when('pll_get_post')->justReturn(0); + Functions\when('pll_get_post_translations')->justReturn([]); Functions\when('get_post_meta')->justReturn(''); $this->allowAllFunctionsToExist(); $this->assertFalse(cdcf_is_public_submission(42)); } - public function test_is_public_submission_resolves_translation_to_source_via_polylang(): void + public function test_is_public_submission_true_when_any_sibling_in_the_polylang_group_carries_submitter_meta(): void { - // Called with translation ID 99; source is 42; meta on 42 says - // public submission. The function should return true. + // Walk the full Polylang group rather than the (EN-hardcoded) source + // returned by cdcf_get_source_post_id(). A Spanish public submission + // carries the submitter meta on the ES post itself; the auto-translated + // EN sibling has no meta. The submission still has to be treated as + // public when the hook fires for ANY sibling (including the EN one). + // + // Production occurrence 2026-06-16: community_project 1534 was submitted + // in Spanish with referral meta on 1534. After publish, the worker + // promoted EN sibling 1556 to publish, transition_post_status fired + // for 1556, the helper walked to 1556 (already the EN sibling) and + // saw no submitter meta — so cdcf_link_referral_on_publish bailed and + // 1556 was never appended to the EN /projects page's community_projects + // field. + $group = ['en' => 200, 'it' => 201, 'es' => 100, 'fr' => 202, 'pt' => 203, 'de' => 204]; Functions\when('pll_get_post')->alias( - static fn(int $id, string $lang) => $id === 99 && $lang === 'en' ? 42 : 0 + static fn(int $id, string $lang) => $group[$lang] ?? 0 + ); + Functions\when('pll_get_post_translations')->alias( + static fn(int $id) => in_array($id, $group, true) ? $group : [] ); - $metaCalls = []; Functions\when('get_post_meta')->alias( - function (int $id, string $key) use (&$metaCalls): string { - $metaCalls[] = $id; - return $id === 42 && $key === '_submission_submitter_email' - ? 'user@example.com' - : ''; - } + static fn(int $id, string $key) => + $id === 100 && $key === '_referral_submitter_email' + ? 'submitter@example.com' + : '' ); $this->allowAllFunctionsToExist(); - $this->assertTrue(cdcf_is_public_submission(99)); - // Meta reads should target the resolved source (42), not the - // translation (99). - $this->assertContains(42, $metaCalls); - $this->assertNotContains(99, $metaCalls); + $this->assertTrue( + cdcf_is_public_submission(200), + 'Calling with the EN sibling (200) must still recognize the submission as public — the submitter meta lives on the ES original (100) in the same Polylang group.' + ); + $this->assertTrue( + cdcf_is_public_submission(204), + 'Calling with any other sibling (e.g. DE 204) must also recognize the submission as public.' + ); + $this->assertTrue( + cdcf_is_public_submission(100), + 'Calling with the source post itself (100) must recognize the submission as public — this is the simple legacy path.' + ); } // ─── cdcf_enqueue_translations_for_submission ─────────────────────