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

Add functionality to reset page order #129

Merged
merged 10 commits into from
Feb 28, 2023
16 changes: 16 additions & 0 deletions assets/js/src/simple-page-ordering.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,19 @@ sortable_post_table.sortable({
}
}
});

jQuery( function() {
// set up click handler for order reset link
jQuery( '#simple-page-ordering-reset' ).on( 'click', function(e) {
e.preventDefault();
var post_type = jQuery( this ).data( 'posttype' );
if ( window.confirm( 'Are you sure you want to reset the ' + post_type + ' order?' ) ) {
jQuery.post( ajaxurl, {
action: 'reset_simple_page_ordering',
post_type: post_type,
_wpnonce: simple_page_ordering_localized_data._wpnonce,
screen_id: simple_page_ordering_localized_data.screen_id
}, function() { window.location.reload(); } );
}
} );
});
56 changes: 46 additions & 10 deletions simple-page-ordering.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public function __construct() {}
public static function add_actions() {
add_action( 'load-edit.php', array( __CLASS__, 'load_edit_screen' ) );
add_action( 'wp_ajax_simple_page_ordering', array( __CLASS__, 'ajax_simple_page_ordering' ) );
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' ) );
}
Expand Down Expand Up @@ -139,8 +140,7 @@ public static function wp() {
'simple-page-ordering',
'simple_page_ordering_localized_data',
array(
'_wpnonce' => wp_create_nonce( 'simple-page-ordering_' . $screen->id ),
'screen_id' => (string) $screen->id,
'_wpnonce' => wp_create_nonce( 'simple-page-ordering-nonce' ),
)
);

Expand All @@ -164,12 +164,18 @@ function () {
* Add page ordering help to the help tab
*/
public static function admin_head() {
$screen = get_current_screen();
$reset_order = sprintf( '<a href="#" id="simple-page-ordering-reset" data-posttype="%s">%s</a>', get_query_var( 'post_type' ), __( 'Reset post order', 'simple-page-ordering' ) );
$screen = get_current_screen();
$screen->add_help_tab(
array(
'id' => 'simple_page_ordering_help_tab',
'title' => 'Simple Page Ordering',
'content' => '<p>' . __( 'To reposition an item, simply drag and drop the row by "clicking and holding" it anywhere (outside of the links and form controls) and moving it to its new position.', 'simple-page-ordering' ) . '</p>',
'title' => esc_html__( 'Simple Page Ordering', 'simple-page-ordering' ),
'content' => sprintf(
'<p>%s</p><a href="#" id="simple-page-ordering-reset" data-posttype="%s">%s</a>',
esc_html__( 'To reposition an item, simply drag and drop the row by "clicking and holding" it anywhere (outside of the links and form controls) and moving it to its new position.', 'simple-page-ordering' ),
get_query_var( 'post_type' ),
esc_html__( 'Reset post order', 'simple-page-ordering' )
),
)
);
}
Expand All @@ -185,14 +191,12 @@ public static function ajax_simple_page_ordering() {
die( - 1 );
}

// do we have a nonce that verifies?
if ( empty( $_POST['_wpnonce'] ) || empty( $_POST['screen_id'] ) ) {
// no nonce to verify...
$nonce = isset( $_POST['_wpnonce'] ) ? sanitize_key( wp_unslash( $_POST['_wpnonce'] ) ) : '';

if ( ! wp_verify_nonce( $nonce, 'simple-page-ordering-nonce' ) ) {
die( -1 );
}

check_admin_referer( 'simple-page-ordering_' . sanitize_key( $_POST['screen_id'] ) );

$post_id = empty( $_POST['id'] ) ? false : (int) $_POST['id'];
$previd = empty( $_POST['previd'] ) ? false : (int) $_POST['previd'];
$nextid = empty( $_POST['nextid'] ) ? false : (int) $_POST['nextid'];
Expand All @@ -219,6 +223,38 @@ public static function ajax_simple_page_ordering() {
die( wp_json_encode( $result ) );
}

/**
* Page ordering reset ajax callback
*
* @return void
*/
public static function ajax_reset_simple_page_ordering() {
global $wpdb;

$nonce = isset( $_POST['_wpnonce'] ) ? sanitize_key( wp_unslash( $_POST['_wpnonce'] ) ) : '';

if ( ! wp_verify_nonce( $nonce, 'simple-page-ordering-nonce' ) ) {
die( -1 );
}

// check and make sure we have what we need
$post_type = isset( $_POST['post_type'] ) ? sanitize_text_field( wp_unslash( $_POST['post_type'] ) ) : '';

if ( empty( $post_type ) ) {
die( -1 );
}

// does user have the right to manage these post objects?
if ( ! self::check_edit_others_caps( $post_type ) ) {
die( -1 );
}

// reset the order of all posts of given post type
$wpdb->update( 'wp_posts', array( 'menu_order' => 0 ), array( 'post_type' => $post_type ) );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkotter This PR works well. The only area that I am concerned about is the performance impact due to this line.
Would this be an issue for an extremely large database? If so, should we look into performing the updates in batches?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the code wpdb::update runs, seems like the final query will be something like:

UPDATE 'wp_posts' SET 'menu_order' = 0 WHERE 'post_type' = 'page'

I think that should scale fairly well and we won't need to worry about doing any sort of batching here. That said, @ruscoe if you'd be able to do some quick testing on various sizes to see the performance impact, that would help us be more confident about merging this in. Something like profiling this code when run on a site that has 10 pages, 100 pages and 1000 pages, that would be helpful.

In addition, I think best practice here is to utilize the format properties of the update method. Something like

Suggested change
$wpdb->update( 'wp_posts', array( 'menu_order' => 0 ), array( 'post_type' => $post_type ) );
$wpdb->update( 'wp_posts', array( 'menu_order' => 0 ), array( 'post_type' => $post_type ), array( '%d' ), array( '%s' ) );

Copy link
Contributor Author

@ruscoe ruscoe Feb 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkotter No problem at all! I profiled by measuring execution time.

Just to show my process, first I used a quick shell script to create pages:

#!/bin/bash

for i in {1..10}
do
  wp post create --post_type=page --post_title="Page $i"
done

Then used the following to test:

$start_time = microtime( true );
$wpdb->update( 'wp_posts', array( 'menu_order' => 0 ), array( 'post_type' => 'page' ), array( '%d' ), array( '%s' ) );
$end_time = microtime( true );
$total_time = ( $end_time - $start_time );
echo $total_time;

Taking the average of five test runs each:

10 posts: 0.0041 microseconds

100 posts: 0.0042 microseconds

1000 posts: 0.0116 microseconds

5000 posts: 0.0299 microseconds

How does that look?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ruscoe! Definitely see an increase in time as the number of posts increase but the overall amount of time is so low that this doesn't seem like it will cause any problems. This looks good to go on my end


die( 0 );
}

/**
* Page ordering function
*
Expand Down
11 changes: 11 additions & 0 deletions tests/cypress/integration/page-ordering.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,15 @@ describe('Test Page Order Change', () => {
firstText.should('have.text', $el.text());
});
});

// Reset page ordering state.
after( () => {
cy.login();
cy.visit('/wp-admin/edit.php?post_type=page');

const firstRow = '.wp-list-table tbody tr:nth-child(1)';
const secondRow = '.wp-list-table tbody tr:nth-child(2)';

cy.get( firstRow ).drag( secondRow );
} );
});
45 changes: 45 additions & 0 deletions tests/cypress/integration/reset-page-ordering.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
describe( 'Test Reset Page Order Change', () => {
it( 'Can reset pages order', () => {
cy.login();
cy.visit('/wp-admin/edit.php?post_type=page');

const firstRow = '.wp-list-table tbody tr:nth-child(1)';
const secondRow = '.wp-list-table tbody tr:nth-child(2)';

// Alias titles as `firstRowText` and `secondRowText` for convenience.
cy.get( firstRow ).find( '.row-title' ).invoke( 'text' ).as( 'firstRowText' );
cy.get( secondRow ).find( '.row-title' ).invoke( 'text' ).as( 'secondRowText' );

// Swap position of `Page 1` with `Page 2`.
cy.get( firstRow ).drag( secondRow );

// Verifies if 1st row has title `Page 2`.
cy.get( firstRow ).find( '.row-title' ).invoke( 'text' ).then( function( text ) {
expect( text ).to.eq( this.secondRowText );
} );

// Verifies if 2nd row has title `Page 1`.
cy.get( secondRow ).find( '.row-title' ).invoke( 'text' ).then( function( text ) {
expect( text ).to.eq( this.firstRowText );
} );

// Now reset the page order and verify original values are back.
cy.get( '#contextual-help-link' ).click();
cy.get( '#tab-link-simple_page_ordering_help_tab' ).click();
cy.get( '#simple-page-ordering-reset' ).click();
cy.on( 'window:confirm', () => true );

// Perform a reload as Cypress won't after window:confirm.
cy.reload();

// Verifies if 1st row has title `Page 1`.
cy.get( firstRow ).find( '.row-title' ).invoke( 'text' ).then( function( text ) {
expect( text ).to.eq( this.firstRowText );
} );

// Verifies if 2nd row has title `Page 2`.
cy.get( secondRow ).find( '.row-title' ).invoke( 'text' ).then( function( text ) {
expect( text ).to.eq( this.secondRowText );
} );
} );
} );