Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce feature allowing modifying parent/child relationships. #172

Merged
merged 23 commits into from
Jan 22, 2024
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1784729
Add Move In/Out actions to the table row.
peterwilsoncc Oct 19, 2023
1e1ec87
Hide move out on first row of table.
peterwilsoncc Oct 20, 2023
8ed6ff8
Handle moving a page inwards.
peterwilsoncc Oct 20, 2023
8c02f5e
Handle moving posts inwards.
peterwilsoncc Oct 20, 2023
2a89b43
Remove test code.
peterwilsoncc Oct 20, 2023
453d24e
Docblock the moving funcitons.
peterwilsoncc Oct 20, 2023
1c7f2fa
Use same ordering as list table.
peterwilsoncc Oct 20, 2023
8d13574
Ensure user has permission before displaying actions.
peterwilsoncc Oct 20, 2023
e9c734c
Merge branch 'develop' into try/25-modify-parenting
github-actions[bot] Oct 26, 2023
d8d70ea
Merge branch 'develop' into try/25-modify-parenting
github-actions[bot] Nov 8, 2023
27f498a
Merge branch 'develop' into try/25-modify-parenting
github-actions[bot] Nov 8, 2023
19d3074
Merge branch 'develop' into try/25-modify-parenting
github-actions[bot] Nov 9, 2023
eda3003
Merge branch 'develop' into try/25-modify-parenting
github-actions[bot] Nov 9, 2023
954d144
Merge branch 'develop' into try/25-modify-parenting
github-actions[bot] Dec 18, 2023
6d22675
Merge branch 'develop' into try/25-modify-parenting
github-actions[bot] Dec 18, 2023
2bbf550
Merge branch 'develop' into try/25-modify-parenting
github-actions[bot] Dec 18, 2023
f53b29d
Merge branch 'develop' into try/25-modify-parenting
github-actions[bot] Jan 2, 2024
8100380
Merge branch 'develop' into try/25-modify-parenting
github-actions[bot] Jan 2, 2024
1a5901a
Merge branch 'develop' into try/25-modify-parenting
github-actions[bot] Jan 4, 2024
b40c13c
Merge branch 'develop' into try/25-modify-parenting
github-actions[bot] Jan 4, 2024
b8d74d5
Modify copy to match copy on WP menu pages.
peterwilsoncc Jan 18, 2024
dba9bf0
Rename methods following copy changes.
peterwilsoncc Jan 18, 2024
2f857b3
Coding standards fixes.
peterwilsoncc Jan 18, 2024
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
265 changes: 265 additions & 0 deletions class-simple-page-ordering.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use stdClass;
use WP_Error;
use WP_Post;
use WP_REST_Response;
use WP_Query;

Expand Down Expand Up @@ -50,6 +51,180 @@ public static function add_actions() {
add_action( 'wp_ajax_reset_simple_page_ordering', array( __CLASS__, 'ajax_reset_simple_page_ordering' ) );
add_action( 'plugins_loaded', array( __CLASS__, 'load_textdomain' ) );
add_action( 'rest_api_init', array( __CLASS__, 'rest_api_init' ) );

// Custom edit page actions.
add_action( 'post_action_spo-move-under-grandparent', array( __CLASS__, 'handle_move_under_grandparent' ) );
add_action( 'post_action_spo-move-under-sibling', array( __CLASS__, 'handle_move_under_sibling' ) );
}

/**
* Move a post in/up the post parent tree.
*
* This is a custom action on the edit page to modify the post parent
* to be the child it's current grandparent post. If no grandparent
* exists, the post becomes a top level page.
*
* @param int $post_id The post ID.
*/
public static function handle_move_under_grandparent( $post_id ) {
$post = get_post( $post_id );
if ( ! $post ) {
self::redirect_to_referer();
}

check_admin_referer( "simple-page-ordering-nonce-move-{$post->ID}", 'spo_nonce' );

if ( ! current_user_can( 'edit_post', $post->ID ) ) {
wp_die( esc_html__( 'You are not allowed to edit this item.', 'simple-page-ordering' ) );
}

if ( 0 === $post->post_parent ) {
// Top level. Politely continue without doing anything.
self::redirect_to_referer();
}

$ancestors = get_post_ancestors( $post );

// If only one ancestor, set to top level page.
if ( 1 === count( $ancestors ) ) {
$parent_id = 0;
} else {
$parent_id = $ancestors[1];
}

// Update the post.
wp_update_post(
array(
'ID' => $post->ID,
'post_parent' => $parent_id,
)
);

self::redirect_to_referer();
}

/**
* Move a post out/down the post parent tree.
*
* This is a custom action on the edit page to modify the post parent
* to be the child of it's previous sibling post on the current post
* tree.
*
* @param int $post_id The post ID.
*/
public static function handle_move_under_sibling( $post_id ) {
$post = get_post( $post_id );
if ( ! $post ) {
self::redirect_to_referer();
}

check_admin_referer( "simple-page-ordering-nonce-move-{$post->ID}", 'spo_nonce' );

if ( ! current_user_can( 'edit_post', $post->ID ) ) {
wp_die( esc_html__( 'You are not allowed to edit this item.', 'simple-page-ordering' ) );
}

list( 'top_level_pages' => $top_level_pages, 'children_pages' => $children_pages ) = self::get_walked_pages();

// Get the relevant siblings.
if ( 0 === $post->post_parent ) {
$siblings = $top_level_pages;
} else {
$siblings = $children_pages[ $post->post_parent ];
}

// Check if the post being moved is a top level page.
$filtered_siblings = wp_list_filter( $siblings, array( 'ID' => $post->ID ) );
if ( empty( $filtered_siblings ) ) {
// Something went wrong. Do nothing.
self::redirect_to_referer();
}

// Find the previous page in the sibling tree
$key = array_key_first( $filtered_siblings );
if ( 0 === $key ) {
// It's the first page. Do nothing.
self::redirect_to_referer();
}

$previous_page = $siblings[ $key - 1 ];
$previous_page_id = $previous_page->ID;

// Update the post with the previous page as the parent.
wp_update_post(
array(
'ID' => $post->ID,
'post_parent' => $previous_page_id,
)
);

self::redirect_to_referer();
}

/**
* Redirect the user after modifying the post parent.
*/
public static function redirect_to_referer() {
global $post_type;

$send_back = wp_get_referer();
if ( ! $send_back ||
str_contains( $send_back, 'post.php' ) ||
str_contains( $send_back, 'post-new.php' ) ) {
if ( 'attachment' === $post_type ) {
$send_back = admin_url( 'upload.php' );
} else {
$send_back = admin_url( 'edit.php' );
if ( ! empty( $post_type ) ) {
$send_back = add_query_arg( 'post_type', $post_type, $send_back );
}
}
} else {
$send_back = remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'ids' ), $send_back );
}

wp_safe_redirect( $send_back );
exit;
}

/**
* Walk the pages and return top level and children pages.
*
* @return array {
* @type WP_Post[] $top_level_pages Top level pages.
* @type WP_Post[] $children_pages Children pages.
* }
*/
public static function get_walked_pages() {
global $wpdb;
$pages = get_pages( array( 'sort_column' => 'menu_order title' ) );

$top_level_pages = array();
$children_pages = array();
$bad_parents = array();

foreach ( $pages as $page ) {
// Catch and repair bad pages.
if ( $page->post_parent === $page->ID ) {
$page->post_parent = 0;
$wpdb->update( $wpdb->posts, array( 'post_parent' => 0 ), array( 'ID' => $page->ID ) );
clean_post_cache( $page );
$bad_parents[] = $page->ID;
}

if ( $page->post_parent > 0 ) {
$children_pages[ $page->post_parent ][] = $page;
} else {
$top_level_pages[] = $page;
}
}
// Reprime post cache for bad parents.
_prime_post_caches( $bad_parents, false, false );

return array(
'top_level_pages' => $top_level_pages,
'children_pages' => $children_pages,
);
}

/**
Expand Down Expand Up @@ -109,6 +284,7 @@ public static function load_edit_screen() {
add_action( 'pre_get_posts', array( __CLASS__, 'filter_query' ) );
add_action( 'wp', array( __CLASS__, 'wp' ) );
add_action( 'admin_head', array( __CLASS__, 'admin_head' ) );
add_action( 'page_row_actions', array( __CLASS__, 'page_row_actions' ), 10, 2 );
}

/**
Expand Down Expand Up @@ -199,6 +375,95 @@ public static function admin_head() {
);
}

/**
* Modify the row actions for hierarchical post types.
*
* This adds the actions to change the parent/child relationships.
*
* @param array $actions An array of row action links.
* @param WP_Post $post The post object.
*/
public static function page_row_actions( $actions, $post ) {
$post = get_post( $post );
if ( ! $post ) {
return $actions;
}

if ( ! current_user_can( 'edit_post', $post->ID ) ) {
return $actions;
}

list( 'top_level_pages' => $top_level_pages, 'children_pages' => $children_pages ) = self::get_walked_pages();

$edit_link = get_edit_post_link( $post->ID, 'raw' );
$move_under_grandparent_link = add_query_arg(
array(
'action' => 'spo-move-under-grandparent',
'spo_nonce' => wp_create_nonce( "simple-page-ordering-nonce-move-{$post->ID}" ),
'post_type' => $post->post_type,
),
$edit_link
);
$move_under_sibling_link = add_query_arg(
array(
'action' => 'spo-move-under-sibling',
'spo_nonce' => wp_create_nonce( "simple-page-ordering-nonce-move-{$post->ID}" ),
'post_type' => $post->post_type,
),
$edit_link
);

$parent_id = $post->post_parent;
if ( $parent_id ) {
$actions['spo-move-under-grandparent'] = sprintf(
'<a href="%s">%s</a>',
esc_url( $move_under_grandparent_link ),
sprintf(
/* translators: %s: parent page/post title */
__( 'Move out from under %s' ),
get_the_title( $parent_id )
)
);
}

// Get the relevant siblings.
if ( 0 === $post->post_parent ) {
$siblings = $top_level_pages;
} else {
$siblings = $children_pages[ $post->post_parent ];
}

// Assume no sibling.
$sibling = 0;
// Check if the post being moved is a top level page.
$filtered_siblings = wp_list_filter( $siblings, array( 'ID' => $post->ID ) );
if ( ! empty( $filtered_siblings ) ) {
// Find the previous page in the sibling tree
$key = array_key_first( $filtered_siblings );
if ( 0 === $key ) {
// It's the first page, can't do anything.
$sibling = 0;
} else {
$previous_page = $siblings[ $key - 1 ];
$sibling = $previous_page->ID;
}
}

if ( $sibling ) {
$actions['spo-move-under-sibling'] = sprintf(
'<a href="%s">%s</a>',
esc_url( $move_under_sibling_link ),
sprintf(
/* translators: %s: sibling page/post title */
__( 'Move under %s' ),
get_the_title( $sibling )
)
);
}

return $actions;
}

/**
* Page ordering ajax callback
*
Expand Down
Loading