Skip to content

Commit 6d35469

Browse files
shantanu2704GaryJonesclaude
committed
fix: prevent PHP warnings when pull_content receives non-array posts
When a syndication client's get_posts() method returns a non-array value (e.g., false, null), the pull_content() method would trigger PHP warnings from count() and foreach operations on non-array values. Added is_array() checks before array operations to handle cases where the client returns an error or empty response. Also fixed indentation issue where syn_last_pull_time update was incorrectly nested inside the posts processing block, causing it to not update when there were no posts to process. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Gary Jones <gary@garyjones.io> Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ee56255 commit 6d35469

File tree

4 files changed

+296
-43
lines changed

4 files changed

+296
-43
lines changed

includes/class-wp-push-syndication-server.php

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,69 +1337,71 @@ public function pull_content( $sites = array() ) {
13371337

13381338
$post_types_processed = array();
13391339

1340-
if ( count( $posts ) > 0 ) {
1340+
if ( is_array( $posts ) && count( $posts ) > 0 ) {
13411341
Syndication_Logger::log_post_info( $site_id, $status = 'start_import', $message = sprintf( __( 'starting import for site id %d with %d posts', 'push-syndication' ), $site_id, count( $posts ) ), $log_time = null, $extra = array() );
13421342
} else {
13431343
Syndication_Logger::log_post_info( $site_id, $status = 'no_posts', $message = sprintf( __( 'no posts for site id %d', 'push-syndication' ), $site_id ), $log_time = null, $extra = array() );
13441344
}
13451345

1346-
foreach( $posts as $post ) {
1346+
if ( is_array( $posts ) && ! empty( $posts) ) {
1347+
foreach( $posts as $post ) {
13471348

1348-
if ( ! in_array( $post['post_type'], $post_types_processed ) ) {
1349-
remove_post_type_support( $post['post_type'], 'revisions' );
1350-
$post_types_processed[] = $post['post_type'];
1351-
}
1352-
1353-
if ( empty( $post['post_guid'] ) ) {
1354-
Syndication_Logger::log_post_error( $site_id, $status = 'no_post_guid', $message = sprintf( __( 'skipping post no guid', 'push-syndication' ) ), $log_time = null, $extra = array( 'post' => $post ) );
1355-
continue;
1356-
}
1357-
$post_id = $this->find_post_by_guid( $post['post_guid'], $post, $site );
1358-
1359-
if ( $post_id ) {
1360-
$pull_edit_shortcircuit = apply_filters( 'syn_pre_pull_edit_post_shortcircuit', false, $post, $site, $transport_type, $client );
1361-
if ( true === $pull_edit_shortcircuit ) {
1362-
Syndication_Logger::log_post_info( $site_id, $status = 'skip_pre_pull_edit_post', $message = sprintf( __( 'skipping post per syn_pre_pull_edit_post_shortcircuit', 'push-syndication' ) ), $log_time = null, $extra = array( 'post' => $post ) );
1363-
continue;
1349+
if ( ! in_array( $post['post_type'], $post_types_processed ) ) {
1350+
remove_post_type_support( $post['post_type'], 'revisions' );
1351+
$post_types_processed[] = $post['post_type'];
13641352
}
1365-
// if updation is disabled continue
1366-
if( $this->push_syndicate_settings['update_pulled_posts'] != 'on' ) {
1367-
Syndication_Logger::log_post_info( $site_id, $status = 'skip_update_pulled_posts', $message = sprintf( __( 'skipping post update per update_pulled_posts setting', 'push-syndication' ) ), $log_time = null, $extra = array( 'post' => $post ) );
1353+
1354+
if ( empty( $post['post_guid'] ) ) {
1355+
Syndication_Logger::log_post_error( $site_id, $status = 'no_post_guid', $message = sprintf( __( 'skipping post no guid', 'push-syndication' ) ), $log_time = null, $extra = array( 'post' => $post ) );
13681356
continue;
13691357
}
1370-
$post['ID'] = $post_id;
1358+
$post_id = $this->find_post_by_guid( $post['post_guid'], $post, $site );
13711359

1372-
$post = apply_filters( 'syn_pull_edit_post', $post, $site, $client );
1360+
if ( $post_id ) {
1361+
$pull_edit_shortcircuit = apply_filters( 'syn_pre_pull_edit_post_shortcircuit', false, $post, $site, $transport_type, $client );
1362+
if ( true === $pull_edit_shortcircuit ) {
1363+
Syndication_Logger::log_post_info( $site_id, $status = 'skip_pre_pull_edit_post', $message = sprintf( __( 'skipping post per syn_pre_pull_edit_post_shortcircuit', 'push-syndication' ) ), $log_time = null, $extra = array( 'post' => $post ) );
1364+
continue;
1365+
}
1366+
// if updation is disabled continue
1367+
if( $this->push_syndicate_settings['update_pulled_posts'] != 'on' ) {
1368+
Syndication_Logger::log_post_info( $site_id, $status = 'skip_update_pulled_posts', $message = sprintf( __( 'skipping post update per update_pulled_posts setting', 'push-syndication' ) ), $log_time = null, $extra = array( 'post' => $post ) );
1369+
continue;
1370+
}
1371+
$post['ID'] = $post_id;
13731372

1374-
$result = wp_update_post( $post, true );
1373+
$post = apply_filters( 'syn_pull_edit_post', $post, $site, $client );
13751374

1376-
do_action( 'syn_post_pull_edit_post', $result, $post, $site, $transport_type, $client );
1375+
$result = wp_update_post( $post, true );
13771376

1378-
$updated_post_ids[] = (int) $result;
1377+
do_action( 'syn_post_pull_edit_post', $result, $post, $site, $transport_type, $client );
13791378

1380-
} else {
1381-
$pull_new_shortcircuit = apply_filters( 'syn_pre_pull_new_post_shortcircuit', false, $post, $site, $transport_type, $client );
1382-
if ( true === $pull_new_shortcircuit ) {
1383-
Syndication_Logger::log_post_info( $site_id, $status = 'syn_pre_pull_new_post_shortcircuit', $message = sprintf( __( 'skipping post per syn_pre_pull_edit_post_shortcircuit', 'push-syndication' ) ), $log_time = null, $extra = array( 'post' => $post ) );
1384-
continue;
1385-
}
1386-
$post = apply_filters( 'syn_pull_new_post', $post, $site, $client );
1379+
$updated_post_ids[] = (int) $result;
13871380

1388-
$result = wp_insert_post( $post, true );
1381+
} else {
1382+
$pull_new_shortcircuit = apply_filters( 'syn_pre_pull_new_post_shortcircuit', false, $post, $site, $transport_type, $client );
1383+
if ( true === $pull_new_shortcircuit ) {
1384+
Syndication_Logger::log_post_info( $site_id, $status = 'syn_pre_pull_new_post_shortcircuit', $message = sprintf( __( 'skipping post per syn_pre_pull_edit_post_shortcircuit', 'push-syndication' ) ), $log_time = null, $extra = array( 'post' => $post ) );
1385+
continue;
1386+
}
1387+
$post = apply_filters( 'syn_pull_new_post', $post, $site, $client );
13891388

1390-
do_action( 'syn_post_pull_new_post', $result, $post, $site, $transport_type, $client );
1389+
$result = wp_insert_post( $post, true );
13911390

1392-
if( !is_wp_error( $result ) ) {
1393-
update_post_meta( $result, 'syn_post_guid', $post['post_guid'] );
1394-
update_post_meta( $result, 'syn_source_site_id', $site_id );
1395-
}
1391+
do_action( 'syn_post_pull_new_post', $result, $post, $site, $transport_type, $client );
13961392

1397-
$updated_post_ids[] = (int) $result;
1393+
if( !is_wp_error( $result ) ) {
1394+
update_post_meta( $result, 'syn_post_guid', $post['post_guid'] );
1395+
update_post_meta( $result, 'syn_source_site_id', $site_id );
1396+
}
1397+
1398+
$updated_post_ids[] = (int) $result;
1399+
}
13981400
}
1399-
}
14001401

1401-
foreach ( $post_types_processed as $post_type ) {
1402-
add_post_type_support( $post_type, 'revisions' );
1402+
foreach ( $post_types_processed as $post_type ) {
1403+
add_post_type_support( $post_type, 'revisions' );
1404+
}
14031405
}
14041406

14051407
update_post_meta( $site_id, 'syn_last_pull_time', current_time( 'timestamp', 1 ) );
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
/**
3+
* Tests for the pull_content functionality in WP_Push_Syndication_Server.
4+
*
5+
* @package Automattic\Syndication\Tests
6+
*/
7+
8+
namespace Syndication\Tests;
9+
10+
use Yoast\WPTestUtils\WPIntegration\TestCase as WPIntegrationTestCase;
11+
12+
/**
13+
* Class PullContentTest
14+
*
15+
* @covers WP_Push_Syndication_Server::pull_content
16+
*/
17+
class PullContentTest extends WPIntegrationTestCase {
18+
19+
/**
20+
* Test that pull_content handles non-array posts without PHP warnings.
21+
*
22+
* This tests the fix for PHP warnings that occurred when a client's
23+
* get_posts() method returned a non-array value (e.g., false, null).
24+
*
25+
* @covers WP_Push_Syndication_Server::pull_content
26+
*/
27+
public function test_pull_content_handles_non_array_posts(): void {
28+
global $push_syndication_server;
29+
30+
// Configure mock client to return false.
31+
\Syndication_Mock_Client::set_posts( false );
32+
33+
// Create a site post with pull enabled.
34+
$site_id = $this->factory()->post->create(
35+
array(
36+
'post_type' => 'syn_site',
37+
'post_status' => 'publish',
38+
)
39+
);
40+
41+
// Enable the site for syndication.
42+
update_post_meta( $site_id, 'syn_site_enabled', 'on' );
43+
44+
// Use our mock transport type.
45+
update_post_meta( $site_id, 'syn_transport_type', 'Mock' );
46+
47+
// Get the site post object to pass directly.
48+
$site = get_post( $site_id );
49+
50+
// This should not trigger any PHP warnings.
51+
// If is_array() check is missing, count() on false would warn.
52+
$push_syndication_server->pull_content( array( $site ) );
53+
54+
// Verify the last pull time was still updated.
55+
$last_pull_time = get_post_meta( $site_id, 'syn_last_pull_time', true );
56+
$this->assertNotEmpty( $last_pull_time, 'Last pull time should be updated even when no posts returned' );
57+
}
58+
59+
/**
60+
* Test that pull_content handles null posts without PHP warnings.
61+
*
62+
* @covers WP_Push_Syndication_Server::pull_content
63+
*/
64+
public function test_pull_content_handles_null_posts(): void {
65+
global $push_syndication_server;
66+
67+
\Syndication_Mock_Client::set_posts( null );
68+
69+
$site_id = $this->factory()->post->create(
70+
array(
71+
'post_type' => 'syn_site',
72+
'post_status' => 'publish',
73+
)
74+
);
75+
76+
update_post_meta( $site_id, 'syn_site_enabled', 'on' );
77+
update_post_meta( $site_id, 'syn_transport_type', 'Mock' );
78+
79+
$site = get_post( $site_id );
80+
81+
// Should not trigger warnings.
82+
$push_syndication_server->pull_content( array( $site ) );
83+
84+
$last_pull_time = get_post_meta( $site_id, 'syn_last_pull_time', true );
85+
$this->assertNotEmpty( $last_pull_time );
86+
}
87+
88+
/**
89+
* Test that pull_content handles empty array without issues.
90+
*
91+
* @covers WP_Push_Syndication_Server::pull_content
92+
*/
93+
public function test_pull_content_handles_empty_array(): void {
94+
global $push_syndication_server;
95+
96+
\Syndication_Mock_Client::set_posts( array() );
97+
98+
$site_id = $this->factory()->post->create(
99+
array(
100+
'post_type' => 'syn_site',
101+
'post_status' => 'publish',
102+
)
103+
);
104+
105+
update_post_meta( $site_id, 'syn_site_enabled', 'on' );
106+
update_post_meta( $site_id, 'syn_transport_type', 'Mock' );
107+
108+
$site = get_post( $site_id );
109+
110+
$push_syndication_server->pull_content( array( $site ) );
111+
112+
$last_pull_time = get_post_meta( $site_id, 'syn_last_pull_time', true );
113+
$this->assertNotEmpty( $last_pull_time, 'Last pull time should be updated even with empty posts array' );
114+
}
115+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<?php
2+
/**
3+
* Mock syndication client for testing.
4+
*
5+
* Named to match the factory's expected format: Syndication_{transport_type}_Client
6+
* Use transport_type 'Mock' to load this client.
7+
*
8+
* @package Automattic\Syndication\Tests
9+
*/
10+
11+
/**
12+
* Mock client for testing pull_content with various return values.
13+
*/
14+
class Syndication_Mock_Client implements Syndication_Client {
15+
16+
/**
17+
* Posts to return from get_posts().
18+
*
19+
* @var mixed
20+
*/
21+
private static $posts_to_return = array();
22+
23+
/**
24+
* Set the posts that get_posts() will return.
25+
*
26+
* @param mixed $posts Posts to return (can be array, false, null, etc.).
27+
*/
28+
public static function set_posts( $posts ) {
29+
self::$posts_to_return = $posts;
30+
}
31+
32+
/**
33+
* Constructor.
34+
*
35+
* @param int $site_id Site ID.
36+
*/
37+
public function __construct( $site_id ) {}
38+
39+
/**
40+
* Get posts from the remote site.
41+
*
42+
* @param array $args Arguments.
43+
* @return mixed
44+
*/
45+
public function get_posts( $args = array() ) {
46+
return self::$posts_to_return;
47+
}
48+
49+
/**
50+
* Create a new post on the remote site.
51+
*
52+
* @param int $post_id Post ID.
53+
* @return bool
54+
*/
55+
public function new_post( $post_id ) {
56+
return false;
57+
}
58+
59+
/**
60+
* Edit a post on the remote site.
61+
*
62+
* @param int $post_id Post ID.
63+
* @param int $ext_id External ID.
64+
* @return bool
65+
*/
66+
public function edit_post( $post_id, $ext_id ) {
67+
return false;
68+
}
69+
70+
/**
71+
* Delete a post on the remote site.
72+
*
73+
* @param int $ext_id External ID.
74+
* @return bool
75+
*/
76+
public function delete_post( $ext_id ) {
77+
return false;
78+
}
79+
80+
/**
81+
* Get a single post from the remote site.
82+
*
83+
* @param int $ext_id External ID.
84+
* @return bool
85+
*/
86+
public function get_post( $ext_id ) {
87+
return false;
88+
}
89+
90+
/**
91+
* Test the connection to the remote site.
92+
*
93+
* @return bool
94+
*/
95+
public function test_connection() {
96+
return true;
97+
}
98+
99+
/**
100+
* Check if a post exists on the remote site.
101+
*
102+
* @param int $ext_id External ID.
103+
* @return bool
104+
*/
105+
public function is_post_exists( $ext_id ) {
106+
return false;
107+
}
108+
109+
/**
110+
* Get client data.
111+
*
112+
* @return array
113+
*/
114+
public static function get_client_data() {
115+
return array(
116+
'id' => 'Mock',
117+
'modes' => array( 'pull' ),
118+
'name' => 'Mock Client',
119+
);
120+
}
121+
122+
/**
123+
* Display settings.
124+
*
125+
* @param object $site Site object.
126+
*/
127+
public static function display_settings( $site ) {}
128+
129+
/**
130+
* Save settings.
131+
*
132+
* @param int $site_id Site ID.
133+
*/
134+
public static function save_settings( $site_id ) {}
135+
}

tests/bootstrap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,5 @@ function (): void {
6969
* Load test dependencies.
7070
*/
7171
require_once __DIR__ . '/Integration/EncryptorTestCase.php';
72+
require_once __DIR__ . '/Integration/Syndication_Mock_Client.php';
7273
}

0 commit comments

Comments
 (0)