Skip to content

Commit 6ed98ad

Browse files
authored
fix(content-distribution): keep node post in status_on_publish when hub schedules (#302)
1 parent 781533b commit 6ed98ad

2 files changed

Lines changed: 123 additions & 9 deletions

File tree

includes/content-distribution/class-incoming-post.php

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -682,19 +682,42 @@ public function insert( $payload = [] ) {
682682
/**
683683
* Post status handling.
684684
*
685-
* If post is being published, use the incoming or stored
686-
* `status_on_publish` if available. Otherwise, use the post status from
687-
* the payload.
685+
* If post is being published, apply the stored `status_on_publish`
686+
* override if one exists. For new posts, use the incoming payload
687+
* value instead. If no override is configured, the post status is
688+
* left unchanged for existing posts and defaults to the incoming
689+
* status for new ones. If post is being scheduled (future) and
690+
* `status_on_publish` is a non-publish status, keep the node post
691+
* in that status. This prevents WP cron from scheduling
692+
* `publish_future_post` and auto-publishing the node post.
693+
* Otherwise, use the post status from the payload.
688694
*/
689695
if ( $post_data['post_status'] === 'publish' ) {
690696
if ( $is_new_post ) {
691-
$postarr['post_status'] = $this->payload['status_on_publish'];
697+
$postarr['post_status'] = isset( $this->payload['status_on_publish'] ) ? $this->payload['status_on_publish'] : $post_data['post_status'];
692698
} else {
693699
$status_on_publish = get_post_meta( $this->ID, self::STATUS_ON_PUBLISH_META, true );
694700
if ( $status_on_publish ) {
695701
$postarr['post_status'] = $status_on_publish;
696702
}
697703
}
704+
} elseif ( $post_data['post_status'] === 'future' ) {
705+
if ( $is_new_post ) {
706+
if ( isset( $this->payload['status_on_publish'] ) ) {
707+
$status_on_publish = $this->payload['status_on_publish'];
708+
} else {
709+
$status_on_publish = '';
710+
}
711+
} else {
712+
$status_on_publish = get_post_meta( $this->ID, self::STATUS_ON_PUBLISH_META, true );
713+
}
714+
715+
if ( $status_on_publish && 'publish' !== $status_on_publish ) {
716+
$postarr['post_status'] = $status_on_publish;
717+
} else {
718+
// If status_on_publish is 'publish' or unset, mirror the hub's schedule.
719+
$postarr['post_status'] = 'future';
720+
}
698721
} else {
699722
$postarr['post_status'] = $post_data['post_status'];
700723
}
@@ -743,11 +766,16 @@ public function insert( $payload = [] ) {
743766
// Handle `status_on_publish` meta.
744767
if ( $post_data['post_status'] !== 'publish' && $is_new_post ) {
745768
// Store the publish status for new posts.
746-
update_post_meta(
747-
$post_id,
748-
self::STATUS_ON_PUBLISH_META,
749-
$this->payload['status_on_publish']
750-
);
769+
if ( isset( $this->payload['status_on_publish'] ) ) {
770+
// Only store the meta if the key is present. An absent status_on_publish
771+
// means no override was configured — the node will fall back to safe
772+
// defaults when the hub later sends 'publish' or 'future'.
773+
update_post_meta(
774+
$post_id,
775+
self::STATUS_ON_PUBLISH_META,
776+
$this->payload['status_on_publish']
777+
);
778+
}
751779
} elseif ( $post_data['post_status'] === 'publish' && ! $is_new_post ) {
752780
// Clean up the meta for published posts so it's not re-published after
753781
// being unpublished.

tests/unit-tests/content-distribution/test-incoming-post.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,92 @@ public function test_pending_distribution() {
524524
$this->assertSame( 'publish', get_post_status( $post_id ) );
525525
}
526526

527+
/**
528+
* Test that a scheduled (future) hub post does not auto-publish on the node
529+
* when status_on_publish is set to a non-publish status.
530+
*
531+
* Regression test for: hub distributes a draft, then schedules it shortly
532+
* after. The scheduling sync sends post_status='future' to the node. If the
533+
* node applies that status directly, WordPress cron will find a future-status
534+
* post and auto-publish it via wp_publish_post(), bypassing the
535+
* status_on_publish setting entirely.
536+
*
537+
* The node should keep the post in the status_on_publish state, such as draft,
538+
* so that WP cron never has a chance to publish it.
539+
*/
540+
public function test_future_status_with_non_publish_status_on_publish() {
541+
$payload = $this->get_sample_payload();
542+
543+
// Simulate Event 1: hub distributes the post as a draft.
544+
// status_on_publish='draft' means the node should never auto-publish.
545+
$payload['post_data']['post_status'] = 'draft';
546+
$payload['status_on_publish'] = 'draft';
547+
548+
$post_id = $this->incoming_post->insert( $payload );
549+
$this->assertSame( 'draft', get_post_status( $post_id ) );
550+
551+
// Simulate Event 2: hub schedules the post, syncing post_status='future'.
552+
// The node must not apply 'future', as a future-status post with a past date
553+
// is auto-published by WP cron, bypassing status_on_publish entirely.
554+
$payload['post_data']['post_status'] = 'future';
555+
$this->incoming_post->insert( $payload );
556+
557+
$this->assertSame( 'draft', get_post_status( $post_id ) );
558+
}
559+
560+
/**
561+
* Test that a scheduled (future) hub post mirrors the future status on the
562+
* node when status_on_publish is 'publish'.
563+
*
564+
* When a publisher wants the node to publish in sync with the hub, the node
565+
* should mirror the 'future' status so that WP cron fires on both sites at
566+
* the same scheduled time.
567+
*/
568+
public function test_future_status_with_publish_status_on_publish() {
569+
$payload = $this->get_sample_payload();
570+
571+
$payload['post_data']['post_status'] = 'draft';
572+
$payload['status_on_publish'] = 'publish';
573+
574+
$post_id = $this->incoming_post->insert( $payload );
575+
576+
// Hub schedules the post. date_gmt must be in the future or WordPress
577+
// will immediately publish the post rather than storing it as 'future'.
578+
$payload['post_data']['post_status'] = 'future';
579+
$payload['post_data']['date_gmt'] = gmdate( 'Y-m-d H:i:s', strtotime( '+1 week' ) );
580+
$this->incoming_post->insert( $payload );
581+
582+
// Node should mirror the hub's scheduled status so both publish together.
583+
$this->assertSame( 'future', get_post_status( $post_id ) );
584+
}
585+
586+
/**
587+
* Test that a scheduled (future) hub post mirrors the future status on the
588+
* node when status_on_publish is absent from the payload.
589+
*
590+
* The sample payload includes status_on_publish by default, so it is
591+
* explicitly unset here to simulate a payload that omits the key.
592+
* The node should fall back to mirroring the hub's future status.
593+
*/
594+
public function test_future_status_with_unset_status_on_publish() {
595+
$payload = $this->get_sample_payload();
596+
597+
// Remove status_on_publish to simulate a payload that omits the key.
598+
unset( $payload['status_on_publish'] );
599+
600+
$payload['post_data']['post_status'] = 'draft';
601+
$post_id = $this->incoming_post->insert( $payload );
602+
603+
// Hub schedules the post. date_gmt must be in the future or WordPress
604+
// will immediately publish the post rather than storing it as 'future'.
605+
$payload['post_data']['post_status'] = 'future';
606+
$payload['post_data']['date_gmt'] = gmdate( 'Y-m-d H:i:s', strtotime( '+1 week' ) );
607+
$this->incoming_post->insert( $payload );
608+
609+
// With no status_on_publish set, the node should mirror the hub's schedule.
610+
$this->assertSame( 'future', get_post_status( $post_id ) );
611+
}
612+
527613
/**
528614
* Test that "status on publish" only applies once.
529615
*/

0 commit comments

Comments
 (0)