Skip to content
Open
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
4 changes: 2 additions & 2 deletions src/wp-admin/includes/meta-boxes.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ function post_submit_meta_box( $post, $args = array() ) {
} elseif ( ! empty( $post->post_password ) ) {
$visibility = 'password';
$visibility_trans = __( 'Password protected' );
} elseif ( 'post' === $post_type && is_sticky( $post_id ) ) {
} elseif ( post_type_supports( $post_type, 'sticky' ) && is_sticky( $post_id ) ) {
$visibility = 'public';
$visibility_trans = __( 'Public, Sticky' );
} else {
Expand Down Expand Up @@ -210,7 +210,7 @@ function post_submit_meta_box( $post, $args = array() ) {
<input type="hidden" name="hidden_post_visibility" id="hidden-post-visibility" value="<?php echo esc_attr( $visibility ); ?>" />
<input type="radio" name="visibility" id="visibility-radio-public" value="public" <?php checked( $visibility, 'public' ); ?> /> <label for="visibility-radio-public" class="selectit"><?php _e( 'Public' ); ?></label><br />

<?php if ( 'post' === $post_type && current_user_can( 'edit_others_posts' ) ) : ?>
<?php if ( post_type_supports( $post_type, 'sticky' ) && current_user_can( 'edit_others_posts' ) ) : ?>
<span id="sticky-span"><input id="sticky" name="sticky" type="checkbox" value="sticky" <?php checked( is_sticky( $post_id ) ); ?> /> <label for="sticky" class="selectit"><?php _e( 'Stick this post to the front page' ); ?></label><br /></span>
<?php endif; ?>

Expand Down
2 changes: 1 addition & 1 deletion src/wp-admin/includes/template.php
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ function get_inline_data( $post ) {
}
}

if ( ! $post_type_object->hierarchical ) {
if ( post_type_supports( $post->post_type, 'sticky' ) ) {
echo '<div class="sticky">' . ( is_sticky( $post->ID ) ? 'sticky' : '' ) . '</div>';
}

Expand Down
12 changes: 4 additions & 8 deletions src/wp-includes/class-wp-xmlrpc-server.php
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,7 @@ protected function _prepare_post( $post, $fields ) {
'menu_order' => (int) $post['menu_order'],
'comment_status' => $post['comment_status'],
'ping_status' => $post['ping_status'],
'sticky' => ( 'post' === $post['post_type'] && is_sticky( $post['ID'] ) ),
'sticky' => ( post_type_supports( $post['post_type'], 'sticky' ) && is_sticky( $post['ID'] ) ),
);

// Thumbnail.
Expand Down Expand Up @@ -1466,10 +1466,8 @@ protected function _insert_post( $user, $content_struct ) {
if ( get_post_type( $post_data['ID'] ) !== $post_data['post_type'] ) {
return new IXR_Error( 401, __( 'The post type may not be changed.' ) );
}
} else {
if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->edit_posts ) ) {
} elseif ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( $post_type->cap->edit_posts ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to post on this site.' ) );
}
}

switch ( $post_data['post_status'] ) {
Expand Down Expand Up @@ -6071,10 +6069,8 @@ public function mw_editPost( $args ) {
// Empty value deletes, non-empty value adds/updates.
if ( empty( $content_struct['wp_post_thumbnail'] ) ) {
delete_post_thumbnail( $post_id );
} else {
if ( set_post_thumbnail( $post_id, $content_struct['wp_post_thumbnail'] ) === false ) {
} elseif ( set_post_thumbnail( $post_id, $content_struct['wp_post_thumbnail'] ) === false ) {
return new IXR_Error( 404, __( 'Invalid attachment ID.' ) );
}
}
unset( $content_struct['wp_post_thumbnail'] );
}
Expand Down Expand Up @@ -6363,7 +6359,7 @@ public function mw_getRecentPosts( $args ) {
'wp_post_format' => $post_format,
'date_modified' => $post_modified,
'date_modified_gmt' => $post_modified_gmt,
'sticky' => ( 'post' === $entry['post_type'] && is_sticky( $entry['ID'] ) ),
'sticky' => ( post_type_supports( $entry['post_type'], 'sticky' ) && is_sticky( $entry['ID'] ) ),
'wp_post_thumbnail' => get_post_thumbnail_id( $entry['ID'] ),
);
}
Expand Down
59 changes: 47 additions & 12 deletions src/wp-includes/post.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ function create_initial_post_types() {
'rest_controller_class' => 'WP_REST_Posts_Controller',
)
);
add_post_type_support( 'post', 'sticky' );

register_post_type(
'page',
Expand Down Expand Up @@ -643,7 +644,8 @@ function create_initial_post_types() {
array(
'label' => _x( 'Published', 'post status' ),
'public' => true,
'_builtin' => true, /* internal use only. */
'_builtin' => true, /*
internal use only. */
/* translators: %s: Number of published posts. */
'label_count' => _n_noop(
'Published <span class="count">(%s)</span>',
Expand All @@ -657,7 +659,8 @@ function create_initial_post_types() {
array(
'label' => _x( 'Scheduled', 'post status' ),
'protected' => true,
'_builtin' => true, /* internal use only. */
'_builtin' => true, /*
internal use only. */
/* translators: %s: Number of scheduled posts. */
'label_count' => _n_noop(
'Scheduled <span class="count">(%s)</span>',
Expand All @@ -671,7 +674,8 @@ function create_initial_post_types() {
array(
'label' => _x( 'Draft', 'post status' ),
'protected' => true,
'_builtin' => true, /* internal use only. */
'_builtin' => true, /*
internal use only. */
/* translators: %s: Number of draft posts. */
'label_count' => _n_noop(
'Draft <span class="count">(%s)</span>',
Expand All @@ -686,7 +690,8 @@ function create_initial_post_types() {
array(
'label' => _x( 'Pending', 'post status' ),
'protected' => true,
'_builtin' => true, /* internal use only. */
'_builtin' => true, /*
internal use only. */
/* translators: %s: Number of pending posts. */
'label_count' => _n_noop(
'Pending <span class="count">(%s)</span>',
Expand All @@ -701,7 +706,8 @@ function create_initial_post_types() {
array(
'label' => _x( 'Private', 'post status' ),
'private' => true,
'_builtin' => true, /* internal use only. */
'_builtin' => true, /*
internal use only. */
/* translators: %s: Number of private posts. */
'label_count' => _n_noop(
'Private <span class="count">(%s)</span>',
Expand All @@ -715,7 +721,8 @@ function create_initial_post_types() {
array(
'label' => _x( 'Trash', 'post status' ),
'internal' => true,
'_builtin' => true, /* internal use only. */
'_builtin' => true, /*
internal use only. */
/* translators: %s: Number of trashed posts. */
'label_count' => _n_noop(
'Trash <span class="count">(%s)</span>',
Expand Down Expand Up @@ -750,7 +757,8 @@ function create_initial_post_types() {
array(
'label' => _x( 'Pending', 'request status' ),
'internal' => true,
'_builtin' => true, /* internal use only. */
'_builtin' => true, /*
internal use only. */
/* translators: %s: Number of pending requests. */
'label_count' => _n_noop(
'Pending <span class="count">(%s)</span>',
Expand All @@ -765,7 +773,8 @@ function create_initial_post_types() {
array(
'label' => _x( 'Confirmed', 'request status' ),
'internal' => true,
'_builtin' => true, /* internal use only. */
'_builtin' => true, /*
internal use only. */
/* translators: %s: Number of confirmed requests. */
'label_count' => _n_noop(
'Confirmed <span class="count">(%s)</span>',
Expand All @@ -780,7 +789,8 @@ function create_initial_post_types() {
array(
'label' => _x( 'Failed', 'request status' ),
'internal' => true,
'_builtin' => true, /* internal use only. */
'_builtin' => true, /*
internal use only. */
/* translators: %s: Number of failed requests. */
'label_count' => _n_noop(
'Failed <span class="count">(%s)</span>',
Expand All @@ -795,7 +805,8 @@ function create_initial_post_types() {
array(
'label' => _x( 'Completed', 'request status' ),
'internal' => true,
'_builtin' => true, /* internal use only. */
'_builtin' => true, /*
internal use only. */
/* translators: %s: Number of completed requests. */
'label_count' => _n_noop(
'Completed <span class="count">(%s)</span>',
Expand Down Expand Up @@ -2858,6 +2869,16 @@ function is_sticky( $post_id = 0 ) {
$post_id = get_the_ID();
}

$post = get_post( $post_id );
if ( ! $post ) {
return false;
}

// Check if the post type supports stickiness.
if ( ! post_type_supports( $post->post_type, 'sticky' ) ) {
return false;
}

$stickies = get_option( 'sticky_posts' );

if ( is_array( $stickies ) ) {
Expand Down Expand Up @@ -3263,7 +3284,15 @@ function sanitize_post_field( $field, $value, $post_id, $context = 'display' ) {
* @param int $post_id Post ID.
*/
function stick_post( $post_id ) {
$post_id = (int) $post_id;
$post_id = (int) $post_id;

$post = get_post( $post_id );

// Check if the post type supports stickiness (only for existing posts).
if ( $post && ! post_type_supports( $post->post_type, 'sticky' ) ) {
return;
}

$stickies = get_option( 'sticky_posts' );
$updated = false;

Expand Down Expand Up @@ -3300,7 +3329,13 @@ function stick_post( $post_id ) {
* @param int $post_id Post ID.
*/
function unstick_post( $post_id ) {
$post_id = (int) $post_id;
$post_id = (int) $post_id;
$post = get_post( $post_id );

// Check if the post type supports stickiness (only for existing posts).
if ( $post && ! post_type_supports( $post->post_type, 'sticky' ) ) {
return;
}
$stickies = get_option( 'sticky_posts' );

if ( ! is_array( $stickies ) ) {
Expand Down
165 changes: 165 additions & 0 deletions tests/phpunit/tests/post/sticky.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?php
/**
* Unit tests for stickiness and Post Type Support API integration.
*
* @package WordPress
* @group sticky
* @ticket 48954
*/

class Tests_Post_Stickiness extends WP_UnitTestCase {

/**
* Set up test environment before each test.
*/
public function setUp(): void {
parent::setUp();
update_option( 'sticky_posts', array() );
}

/**
* Clean up after each test.
*/
public function tearDown(): void {
$custom_types = array( 'custom_type', 'my_custom_type', 'another_type' );
foreach ( $custom_types as $type ) {
if ( post_type_exists( $type ) ) {
_unregister_post_type( $type );
}
}
update_option( 'sticky_posts', array() );
parent::tearDown();
}

/**
* Test that stickiness works for post types that support it.
*/
public function test_stickiness_for_supported_post_type() {
$post_id = self::factory()->post->create( array( 'post_type' => 'post' ) );

stick_post( $post_id );
$this->assertTrue( is_sticky( $post_id ) );

unstick_post( $post_id );
$this->assertFalse( is_sticky( $post_id ) );
}

/**
* Test that stickiness does not work for post types that do not support it.
*/
public function test_stickiness_for_unsupported_post_type() {
register_post_type(
'custom_type',
array(
'label' => 'Custom Type',
'public' => true,
'supports' => array( 'title', 'editor' ),
)
);

$post_id = self::factory()->post->create( array( 'post_type' => 'custom_type' ) );
stick_post( $post_id );
$this->assertFalse( is_sticky( $post_id ) );
}

/**
* Test that stickiness is removed when switching to a post type that does not support it.
*/
public function test_stickiness_removed_on_post_type_switch() {
$post_id = self::factory()->post->create( array( 'post_type' => 'post' ) );
stick_post( $post_id );
$this->assertTrue( is_sticky( $post_id ) );

wp_update_post(
array(
'ID' => $post_id,
'post_type' => 'page',
)
);
$this->assertFalse( is_sticky( $post_id ) );
}

/**
* Test that UI functions handle post type switches correctly.
*/
public function test_ui_functions_after_post_type_switch() {
$admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) );
wp_set_current_user( $admin_id );

$post_id = self::factory()->post->create(
array(
'post_type' => 'post',
'post_author' => $admin_id,
)
);
stick_post( $post_id );

$states = get_post_states( get_post( $post_id ) );
$this->assertArrayHasKey( 'sticky', $states );

wp_update_post(
array(
'ID' => $post_id,
'post_type' => 'page',
)
);

$states = get_post_states( get_post( $post_id ) );
$this->assertArrayNotHasKey( 'sticky', $states );
}

/**
* Test backward compatibility - existing sticky posts behavior.
*/
public function test_backward_compatibility() {
$this->assertTrue( post_type_supports( 'post', 'sticky' ) );
$this->assertFalse( post_type_supports( 'page', 'sticky' ) );

$post_id = self::factory()->post->create( array( 'post_type' => 'post' ) );
stick_post( $post_id );
$this->assertTrue( is_sticky( $post_id ) );
}

/**
* Test that developers can add sticky support to custom post types.
*/
public function test_custom_post_type_sticky_support() {
register_post_type(
'my_custom_type',
array(
'label' => 'My Custom Type',
'public' => true,
'supports' => array( 'title', 'editor', 'sticky' ),
)
);

$this->assertTrue( post_type_supports( 'my_custom_type', 'sticky' ) );

$custom_post_id = self::factory()->post->create( array( 'post_type' => 'my_custom_type' ) );
stick_post( $custom_post_id );
$this->assertTrue( is_sticky( $custom_post_id ) );

register_post_type(
'another_type',
array(
'label' => 'Another Type',
'public' => true,
'supports' => array( 'title' ),
)
);

$this->assertFalse( post_type_supports( 'another_type', 'sticky' ) );

add_post_type_support( 'another_type', 'sticky' );
$this->assertTrue( post_type_supports( 'another_type', 'sticky' ) );
}

/**
* Test that post_type_supports() checks work correctly for stickiness.
*/
public function test_post_type_supports_sticky() {
$this->assertTrue( post_type_supports( 'post', 'sticky' ) );
$this->assertFalse( post_type_supports( 'page', 'sticky' ) );
$this->assertFalse( post_type_supports( 'attachment', 'sticky' ) );
}
}
Loading