Skip to content
Snippets Groups Projects
Commit 7315cf8a authored by Erick Hitter's avatar Erick Hitter Committed by GitHub
Browse files

Merge pull request #2 from Automattic/add/move-to-trash

Add support for "Move to trash"
parents d4d060ca 3f98b389
No related branches found
No related tags found
No related merge requests found
...@@ -20,3 +20,4 @@ require __DIR__ . '/includes/utils.php'; ...@@ -20,3 +20,4 @@ require __DIR__ . '/includes/utils.php';
// Plugin functionality. // Plugin functionality.
require __DIR__ . '/includes/class-main.php'; require __DIR__ . '/includes/class-main.php';
require __DIR__ . '/includes/class-delete-all.php'; require __DIR__ . '/includes/class-delete-all.php';
require __DIR__ . '/includes/class-move-to-trash.php';
...@@ -14,16 +14,14 @@ class Delete_All { ...@@ -14,16 +14,14 @@ class Delete_All {
/** /**
* Class constants * Class constants
*/ */
const CRON_EVENT = 'a8c_bulk_edit_delete_all'; const ADMIN_NOTICE_KEY = 'bulk_edit_cron_offload_deleted_all';
const ADMIN_NOTICE_KEY = 'a8c_bulk_edit_deleted_all';
/** /**
* Register this bulk process' hooks * Register this bulk process' hooks
*/ */
public static function register_hooks() { public static function register_hooks() {
add_action( Main::build_hook( 'delete_all' ), array( __CLASS__, 'process' ) ); add_action( Main::build_hook( 'delete_all' ), array( __CLASS__, 'process' ) );
add_action( self::CRON_EVENT, array( __CLASS__, 'process_via_cron' ) ); add_action( Main::build_cron_hook( 'delete_all' ), array( __CLASS__, 'process_via_cron' ) );
add_action( 'admin_notices', array( __CLASS__, 'admin_notices' ) ); add_action( 'admin_notices', array( __CLASS__, 'admin_notices' ) );
add_filter( 'posts_where', array( __CLASS__, 'hide_posts_pending_delete' ), 999, 2 ); add_filter( 'posts_where', array( __CLASS__, 'hide_posts_pending_delete' ), 999, 2 );
...@@ -43,11 +41,10 @@ class Delete_All { ...@@ -43,11 +41,10 @@ class Delete_All {
// Special keys are used to trigger this request, and we need to remove them on redirect. // Special keys are used to trigger this request, and we need to remove them on redirect.
$extra_keys = array( 'delete_all', 'delete_all2' ); $extra_keys = array( 'delete_all', 'delete_all2' );
$action_scheduled = self::action_next_scheduled( self::CRON_EVENT, $vars->post_type ); $action_scheduled = self::action_next_scheduled( $vars->post_type );
if ( empty( $action_scheduled ) ) { if ( empty( $action_scheduled ) ) {
wp_schedule_single_event( time(), self::CRON_EVENT, array( $vars ) ); Main::schedule_processing( $vars );
Main::do_admin_redirect( self::ADMIN_NOTICE_KEY, true, $extra_keys ); Main::do_admin_redirect( self::ADMIN_NOTICE_KEY, true, $extra_keys );
} else { } else {
Main::do_admin_redirect( self::ADMIN_NOTICE_KEY, false, $extra_keys ); Main::do_admin_redirect( self::ADMIN_NOTICE_KEY, false, $extra_keys );
...@@ -115,31 +112,25 @@ class Delete_All { ...@@ -115,31 +112,25 @@ class Delete_All {
public static function admin_notices() { public static function admin_notices() {
$screen = get_current_screen(); $screen = get_current_screen();
$type = '';
$message = '';
if ( isset( $_REQUEST[ self::ADMIN_NOTICE_KEY ] ) ) { if ( isset( $_REQUEST[ self::ADMIN_NOTICE_KEY ] ) ) {
if ( 1 === (int) $_REQUEST[ self::ADMIN_NOTICE_KEY ] ) { if ( 1 === (int) $_REQUEST[ self::ADMIN_NOTICE_KEY ] ) {
$class = 'notice-success'; $type = 'success';
$message = __( 'Success! The trash will be emptied soon.', 'bulk-edit-cron-offload' ); $message = __( 'Success! The trash will be emptied shortly.', 'bulk-edit-cron-offload' );
} else { } else {
$class = 'notice-error'; $type = 'error';
$message = __( 'A request to empty the trash is already pending for this post type.', 'bulk-edit-cron-offload' ); $message = __( 'A request to empty the trash is already pending for this post type.', 'bulk-edit-cron-offload' );
} }
} elseif ( 'edit' === $screen->base && isset( $_REQUEST['post_status'] ) && 'trash' === $_REQUEST['post_status'] ) { } elseif ( 'edit' === $screen->base && isset( $_REQUEST['post_status'] ) && 'trash' === $_REQUEST['post_status'] ) {
if ( self::action_next_scheduled( self::CRON_EVENT, $screen->post_type ) ) { if ( self::action_next_scheduled( $screen->post_type ) ) {
$class = 'notice-warning'; $type = 'warning';
$message = __( 'A pending request to empty the trash will be processed soon.', 'bulk-edit-cron-offload' ); $message = __( 'A pending request to empty the trash will be processed soon.', 'bulk-edit-cron-offload' );
} }
} }
// Nothing to display. Main::render_admin_notice( $type, $message );
if ( ! isset( $class ) || ! isset( $message ) ) {
return;
}
?>
<div class="notice <?php echo esc_attr( $class ); ?>">
<p><?php echo esc_html( $message ); ?></p>
</div>
<?php
} }
/** /**
...@@ -162,7 +153,7 @@ class Delete_All { ...@@ -162,7 +153,7 @@ class Delete_All {
return $where; return $where;
} }
if ( self::action_next_scheduled( self::CRON_EVENT, $q->get( 'post_type' ) ) ) { if ( self::action_next_scheduled( $q->get( 'post_type' ) ) ) {
$where .= ' AND 0=1'; $where .= ' AND 0=1';
} }
...@@ -196,7 +187,7 @@ class Delete_All { ...@@ -196,7 +187,7 @@ class Delete_All {
} }
// There isn't a pending purge, so one should be permitted. // There isn't a pending purge, so one should be permitted.
if ( ! self::action_next_scheduled( self::CRON_EVENT, $screen->post_type ) ) { if ( ! self::action_next_scheduled( $screen->post_type ) ) {
return $caps; return $caps;
} }
...@@ -209,11 +200,10 @@ class Delete_All { ...@@ -209,11 +200,10 @@ class Delete_All {
/** /**
* Find the next scheduled instance of a given action, regardless of arguments * Find the next scheduled instance of a given action, regardless of arguments
* *
* @param string $action_to_check Hook to search for. * @param string $post_type Post type hook is scheduled for.
* @param string $post_type Post type hook is scheduled for.
* @return array * @return array
*/ */
private static function action_next_scheduled( $action_to_check, $post_type ) { private static function action_next_scheduled( $post_type ) {
$events = get_option( 'cron' ); $events = get_option( 'cron' );
if ( ! is_array( $events ) ) { if ( ! is_array( $events ) ) {
...@@ -227,14 +217,14 @@ class Delete_All { ...@@ -227,14 +217,14 @@ class Delete_All {
} }
foreach ( $timestamp_events as $action => $action_instances ) { foreach ( $timestamp_events as $action => $action_instances ) {
if ( $action !== $action_to_check ) { if ( Main::CRON_EVENT !== $action ) {
continue; continue;
} }
foreach ( $action_instances as $instance => $instance_args ) { foreach ( $action_instances as $instance => $instance_args ) {
$vars = array_shift( $instance_args['args'] ); $vars = array_shift( $instance_args['args'] );
if ( $post_type === $vars->post_type ) { if ( 'delete_all' === $vars->action && $post_type === $vars->post_type ) {
return array( return array(
'timestamp' => $timestamp, 'timestamp' => $timestamp,
'args' => $vars, 'args' => $vars,
......
...@@ -17,12 +17,28 @@ class Main { ...@@ -17,12 +17,28 @@ class Main {
const ACTION = 'a8c_bulk_edit_cron_'; const ACTION = 'a8c_bulk_edit_cron_';
/** /**
* Register action * Common cron action
*/
const CRON_EVENT = 'bulk_edit_cron_offload';
/**
* Register actions
*/ */
public static function load() { public static function load() {
add_action( self::CRON_EVENT, array( __CLASS__, 'do_cron' ) );
add_action( 'load-edit.php', array( __CLASS__, 'intercept' ) ); add_action( 'load-edit.php', array( __CLASS__, 'intercept' ) );
} }
/**
* Run appropriate cron callback
*
* @param object $vars Bulk-request variables.
*/
public static function do_cron( $vars ) {
do_action( self::build_cron_hook( $vars->action ), $vars );
}
/** /**
* Call appropriate handler * Call appropriate handler
*/ */
...@@ -77,8 +93,6 @@ class Main { ...@@ -77,8 +93,6 @@ class Main {
if ( isset( $_REQUEST['delete_all'] ) || isset( $_REQUEST['delete_all2'] ) ) { if ( isset( $_REQUEST['delete_all'] ) || isset( $_REQUEST['delete_all2'] ) ) {
$vars->action = 'delete_all'; $vars->action = 'delete_all';
$vars->post_status = $_REQUEST['post_status'];
} elseif ( isset( $_REQUEST['action'] ) && '-1' !== $_REQUEST['action'] ) { } elseif ( isset( $_REQUEST['action'] ) && '-1' !== $_REQUEST['action'] ) {
$vars->action = $_REQUEST['action']; $vars->action = $_REQUEST['action'];
} elseif ( isset( $_REQUEST['action2'] ) && '-1' !== $_REQUEST['action2'] ) { } elseif ( isset( $_REQUEST['action2'] ) && '-1' !== $_REQUEST['action2'] ) {
...@@ -121,6 +135,11 @@ class Main { ...@@ -121,6 +135,11 @@ class Main {
$vars->post_format = $_REQUEST['post_format']; $vars->post_format = $_REQUEST['post_format'];
} }
// Post status is special.
if ( is_null( $vars->post_status ) && isset( $_REQUEST['post_status'] ) && ! empty( $_REQUEST['post_status'] ) ) {
$vars->post_status = $_REQUEST['post_status'];
}
return $vars; return $vars;
} }
...@@ -152,6 +171,16 @@ class Main { ...@@ -152,6 +171,16 @@ class Main {
return self::ACTION . $action; return self::ACTION . $action;
} }
/**
* Build a cron hook specific to a bulk request
*
* @param string $action Bulk action to register cron callback for.
* @return string
*/
public static function build_cron_hook( $action ) {
return self::ACTION . $action . '_callback';
}
/** /**
* Unset flags Core uses to trigger bulk processing * Unset flags Core uses to trigger bulk processing
*/ */
...@@ -162,6 +191,26 @@ class Main { ...@@ -162,6 +191,26 @@ class Main {
unset( $_REQUEST['delete_all2'] ); unset( $_REQUEST['delete_all2'] );
} }
/**
* Create cron event
*
* @param object $vars Bulk-request variables.
* @return bool
*/
public static function schedule_processing( $vars ) {
return false !== wp_schedule_single_event( time(), self::CRON_EVENT, array( $vars ) );
}
/**
* Retrieve timestamp for next scheduled event with given vars
*
* @param object $vars Bulk-request variables.
* @return int
*/
public static function next_scheduled( $vars ) {
return (int) wp_next_scheduled( self::CRON_EVENT, array( $vars ) );
}
/** /**
* Redirect, including a flag to indicate if the bulk process was scheduled successfully * Redirect, including a flag to indicate if the bulk process was scheduled successfully
* *
...@@ -184,6 +233,26 @@ class Main { ...@@ -184,6 +233,26 @@ class Main {
wp_safe_redirect( $redirect ); wp_safe_redirect( $redirect );
exit; exit;
} }
/**
* Render an admin message of a given type
*
* @param string $type Message type.
* @param string $message Message to output.
* @return void
*/
public static function render_admin_notice( $type, $message ) {
// Lacking what's required.
if ( empty( $type ) || empty( $message ) ) {
return;
}
?>
<div class="notice <?php echo esc_attr( 'notice-' . $type ); ?>">
<p><?php echo esc_html( $message ); ?></p>
</div>
<?php
}
} }
Main::load(); Main::load();
<?php
/**
* Offload "Move to Trash"
*
* @package Bulk_Edit_Cron_Offload
*/
namespace Automattic\WP\Bulk_Edit_Cron_Offload;
/**
* Class Move_To_Trash
*/
class Move_To_Trash {
/**
* Class constants
*/
const ADMIN_NOTICE_KEY = 'bulk_edit_cron_offload_move_to_trash';
/**
* Register this bulk process' hooks
*/
public static function register_hooks() {
add_action( Main::build_hook( 'trash' ), array( __CLASS__, 'process' ) );
add_action( Main::build_cron_hook( 'trash' ), array( __CLASS__, 'process_via_cron' ) );
add_action( 'admin_notices', array( __CLASS__, 'admin_notices' ) );
add_filter( 'posts_where', array( __CLASS__, 'hide_posts_pending_move' ), 999, 2 );
}
/**
* Handle a request to move some posts to the trash
*
* @param object $vars Bulk-request variables.
*/
public static function process( $vars ) {
$action_scheduled = Main::next_scheduled( $vars );
if ( empty( $action_scheduled ) ) {
Main::schedule_processing( $vars );
Main::do_admin_redirect( self::ADMIN_NOTICE_KEY, true );
} else {
Main::do_admin_redirect( self::ADMIN_NOTICE_KEY, false );
}
}
/**
* Cron callback to move requested items to trash
*
* @param object $vars Bulk-request variables.
*/
public static function process_via_cron( $vars ) {
$count = 0;
if ( is_array( $vars->posts ) && ! empty( $vars->posts ) ) {
require_once ABSPATH . '/wp-admin/includes/post.php';
$trashed = array();
$locked = array();
$auth_error = array();
$error = array();
foreach ( $vars->posts as $post_id ) {
// Can the user trash this post?
if ( ! user_can( $vars->user_id, 'delete_post', $post_id ) ) {
$auth_error[] = $post_id;
continue;
}
// Post is locked by someone, so leave it alone.
if ( false !== wp_check_post_lock( $post_id ) ) {
$locked[] = $post_id;
continue;
}
// Try trashing.
$post_trashed = wp_trash_post( $post_id );
if ( $post_trashed ) {
$trashed[] = $post_id;
} else {
$error[] = $post_id;
}
// Take a break periodically.
if ( 0 === $count++ % 50 ) {
stop_the_insanity();
sleep( 3 );
}
}
$results = compact( 'trashed', 'locked', 'auth_error', 'error' );
do_action( 'bulk_edit_cron_offload_move_to_trash_request_completed', $results, $vars );
} else {
do_action( 'bulk_edit_cron_offload_move_to_trash_request_no_posts', $vars->posts, $vars );
}
}
/**
* Let the user know what's going on
*/
public static function admin_notices() {
$screen = get_current_screen();
$type = '';
$message = '';
if ( isset( $_REQUEST[ self::ADMIN_NOTICE_KEY ] ) ) {
if ( 1 === (int) $_REQUEST[ self::ADMIN_NOTICE_KEY ] ) {
$type = 'success';
$message = __( 'Success! The selected posts will be moved to the trash shortly.', 'bulk-edit-cron-offload' );
} else {
$type = 'error';
$message = __( 'The selected posts are already scheduled to be moved to the trash.', 'bulk-edit-cron-offload' );
}
} elseif ( 'edit' === $screen->base ) {
if ( isset( $_REQUEST['post_status'] ) && 'trash' === $_REQUEST['post_status'] ) {
return;
}
$status = isset( $_REQUEST['post_status'] ) ? $_REQUEST['post_status'] : 'all';
if ( self::get_all_pending_actions( $screen->post_type, $status ) ) {
$type = 'warning';
$message = __( 'Some items that would normally be shown here are waiting to be moved to the trash. These items are hidden until they are moved.', 'bulk-edit-cron-offload' );
}
}
Main::render_admin_notice( $type, $message );
}
/**
* When a move is pending for a given post type, hide those posts in the admin
*
* @param string $where Posts' WHERE clause.
* @param object $q WP_Query object.
* @return string
*/
public static function hide_posts_pending_move( $where, $q ) {
if ( ! is_admin() || ! $q->is_main_query() ) {
return $where;
}
if ( 'edit' !== get_current_screen()->base ) {
return $where;
}
if ( 'trash' === $q->get( 'post_status' ) ) {
return $where;
}
$post__not_in = self::get_post_ids_pending_move( $q->get( 'post_type' ), $q->get( 'post_status' ) );
if ( ! empty( $post__not_in ) ) {
$post__not_in = implode( ',', $post__not_in );
$where .= ' AND ID NOT IN(' . $post__not_in . ')';
}
return $where;
}
/**
* Gather all pending events for a given post type
*
* @param string $post_type Post type needing exclusion.
* @param string $post_status Post status to filter by.
* @return array
*/
private static function get_all_pending_actions( $post_type, $post_status ) {
$events = get_option( 'cron' );
if ( ! is_array( $events ) ) {
return array();
}
$ids = array();
foreach ( $events as $timestamp => $timestamp_events ) {
// Skip non-event data that Core includes in the option.
if ( ! is_numeric( $timestamp ) ) {
continue;
}
foreach ( $timestamp_events as $action => $action_instances ) {
if ( Main::CRON_EVENT !== $action ) {
continue;
}
foreach ( $action_instances as $instance => $instance_args ) {
$vars = array_shift( $instance_args['args'] );
if ( 'trash' === $vars->action && $post_type === $vars->post_type ) {
if ( $post_status === $vars->post_status || 'all' === $vars->post_status || 'all' === $post_status ) {
$ids[] = array(
'timestamp' => $timestamp,
'args' => $vars,
);
}
}
}
}
}
return $ids;
}
/**
* Gather IDs of objects pending move to trash, with given post type
*
* @param string $post_type Post type needing exclusion.
* @param string $post_status Post status to filter by.
* @return array
*/
private static function get_post_ids_pending_move( $post_type, $post_status ) {
$events = wp_list_pluck( self::get_all_pending_actions( $post_type, $post_status ), 'args' );
$events = wp_list_pluck( $events, 'posts' );
$ids = array();
foreach ( $events as $ids_to_merge ) {
$ids = array_merge( $ids, $ids_to_merge );
}
if ( ! empty( $ids ) ) {
$ids = array_map( 'absint', $ids );
$ids = array_unique( $ids );
}
return $ids;
}
}
Move_To_Trash::register_hooks();
...@@ -5,7 +5,7 @@ msgstr "" ...@@ -5,7 +5,7 @@ msgstr ""
"Project-Id-Version: Bulk Edit Cron Offload 1.0\n" "Project-Id-Version: Bulk Edit Cron Offload 1.0\n"
"Report-Msgid-Bugs-To: " "Report-Msgid-Bugs-To: "
"https://wordpress.org/support/plugin/bulk-edit-cron-offload\n" "https://wordpress.org/support/plugin/bulk-edit-cron-offload\n"
"POT-Creation-Date: 2017-09-13 01:24:54+00:00\n" "POT-Creation-Date: 2017-09-13 05:28:23+00:00\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
...@@ -25,18 +25,32 @@ msgstr "" ...@@ -25,18 +25,32 @@ msgstr ""
"X-Poedit-Bookmarks: \n" "X-Poedit-Bookmarks: \n"
"X-Textdomain-Support: yes\n" "X-Textdomain-Support: yes\n"
#: includes/class-delete-all.php:124 #: includes/class-delete-all.php:121
msgid "Success! The trash will be emptied soon." msgid "Success! The trash will be emptied shortly."
msgstr "" msgstr ""
#: includes/class-delete-all.php:127 #: includes/class-delete-all.php:124
msgid "A request to empty the trash is already pending for this post type." msgid "A request to empty the trash is already pending for this post type."
msgstr "" msgstr ""
#: includes/class-delete-all.php:132 #: includes/class-delete-all.php:129
msgid "A pending request to empty the trash will be processed soon." msgid "A pending request to empty the trash will be processed soon."
msgstr "" msgstr ""
#: includes/class-move-to-trash.php:109
msgid "Success! The selected posts will be moved to the trash shortly."
msgstr ""
#: includes/class-move-to-trash.php:112
msgid "The selected posts are already scheduled to be moved to the trash."
msgstr ""
#: includes/class-move-to-trash.php:123
msgid ""
"Some items that would normally be shown here are waiting to be moved to the "
"trash. These items are hidden until they are moved."
msgstr ""
#. Plugin Name of the plugin/theme #. Plugin Name of the plugin/theme
msgid "Bulk Edit Cron Offload" msgid "Bulk Edit Cron Offload"
msgstr "" msgstr ""
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment