diff --git a/bulk-actions-cron-offload.php b/bulk-actions-cron-offload.php index f3d05fae4508850319666fc6922f182ebe3d02f4..3431ffc4018ccd93fa16049198f606d4f01d7212 100644 --- a/bulk-actions-cron-offload.php +++ b/bulk-actions-cron-offload.php @@ -20,6 +20,7 @@ require __DIR__ . '/includes/utils.php'; // Plugin functionality. require __DIR__ . '/includes/class-main.php'; +require __DIR__ . '/includes/class-custom-action.php'; require __DIR__ . '/includes/class-delete-all.php'; require __DIR__ . '/includes/class-delete-permanently.php'; require __DIR__ . '/includes/class-edit.php'; diff --git a/includes/class-custom-action.php b/includes/class-custom-action.php new file mode 100644 index 0000000000000000000000000000000000000000..7f37d6dcc4be1b4286c2bbf694fa3369455e4f68 --- /dev/null +++ b/includes/class-custom-action.php @@ -0,0 +1,83 @@ +<?php +/** + * Offload custom actions + * + * @package Bulk_Actions_Cron_Offload + */ + +namespace Automattic\WP\Bulk_Actions_Cron_Offload; + +/** + * Class Custom_Action + */ +class Custom_Action { + /** + * Common hooks and such + */ + use Bulk_Actions; + + /** + * Class constants + */ + const ACTION = 'custom'; + + const ADMIN_NOTICE_KEY = 'bulk_actions_cron_offload_custom'; + + /** + * Cron callback to run a custom bulk action + * + * Because bulk actions work off of a redirect by default, custom actions are + * processed in the filter for the redirect destination, normally allowing + * for customized messaging. + * + * @param object $vars Bulk-request variables. + */ + public static function process_via_cron( $vars ) { + // Provide for capabilities checks. + wp_set_current_user( $vars->user_id ); + + // TODO: capture and repopulate $_REQUEST? + // Rebuild something akin to the URL this would normally be filtering. + $return_url = sprintf( '/wp-admin/%1$s.php', $vars->current_screen->base ); + $return_url = add_query_arg( array( + 'post_type' => $vars->post_type, + 'post_status' => $vars->post_status, + ), $return_url ); + + // Run the custom action as Core does. See note above. + $return_url = apply_filters( 'handle_bulk_actions-' . $vars->current_screen->id, $return_url, $vars->action, $vars->posts ); // Core violates its own standard by using a hyphen in the hook name. @codingStandardsIgnoreLine + + // Can't get much more than this in terms of success or failure. + $results = compact( 'return_url', 'vars' ); + do_action( 'bulk_actions_cron_offload_custom_request_completed', $results, $vars ); + } + + /** + * Provide post-redirect success message + * + * @retun string + */ + public static function admin_notice_success_message() { + return __( 'Success! The selected posts will be processed shortly.', 'bulk-actions-cron-offload' ); + } + + /** + * Provide post-redirect error message + * + * @retun string + */ + public static function admin_notice_error_message() { + return __( 'The requested processing is already pending for the chosen posts.', 'bulk-actions-cron-offload' ); + } + + /** + * Provide message when posts are hidden pending processing + * + * @return string + */ + public static function admin_notice_hidden_pending_processing() { + return __( 'Some items that would normally be shown here are waiting to be processed. These items are hidden until processing completes.', 'bulk-actions-cron-offload' ); + } +} + +Custom_Action::register_hooks(); diff --git a/includes/class-delete-all.php b/includes/class-delete-all.php index a7d15c765f08225acf36c446eacb438816b121d8..ec51f75536063ba495e6e86ec4da2cc11a94f4e7 100644 --- a/includes/class-delete-all.php +++ b/includes/class-delete-all.php @@ -64,8 +64,6 @@ class Delete_All { $count = 0; if ( is_array( $post_ids ) && ! empty( $post_ids ) ) { - require_once ABSPATH . '/wp-admin/includes/post.php'; - $deleted = array(); $locked = array(); $auth_error = array(); @@ -120,7 +118,7 @@ class Delete_All { if ( 'edit' === $screen->base && isset( $_REQUEST['post_status'] ) && 'trash' === $_REQUEST['post_status'] ) { if ( Main::get_action_next_scheduled( self::ACTION, $screen->post_type ) ) { $type = 'warning'; - $message = __( 'A pending request to empty the trash will be processed soon.', 'bulk-actions-cron-offload' ); + $message = self::admin_notice_hidden_pending_processing(); } } @@ -145,6 +143,15 @@ class Delete_All { return __( 'A request to empty the trash is already pending for this post type.', 'bulk-actions-cron-offload' ); } + /** + * Provide translated message when posts are hidden pending processing + * + * @return string + */ + public static function admin_notice_hidden_pending_processing() { + return __( 'A pending request to empty the trash will be processed soon.', 'bulk-actions-cron-offload' ); + } + /** * When a delete is pending for a given post type, hide those posts in the admin * diff --git a/includes/class-delete-permanently.php b/includes/class-delete-permanently.php index 74005932a2c9c912ff3d907cd77f49a617f6a979..6b0419a156a42ae3a7a5f8cac64ba20b67362cc1 100644 --- a/includes/class-delete-permanently.php +++ b/includes/class-delete-permanently.php @@ -32,8 +32,6 @@ class Delete_Permanently { $count = 0; if ( is_array( $vars->posts ) && ! empty( $vars->posts ) ) { - require_once ABSPATH . '/wp-admin/includes/post.php'; - $deleted = array(); $locked = array(); $auth_error = array(); @@ -88,7 +86,7 @@ class Delete_Permanently { if ( 'edit' === $screen->base && isset( $_REQUEST['post_status'] ) && 'trash' === $_REQUEST['post_status'] ) { if ( Main::get_post_ids_for_pending_events( self::ACTION, $screen->post_type, 'trash' ) ) { $type = 'warning'; - $message = __( 'Some items that would normally be shown here are waiting to be deleted permanently. These items are hidden until then.', 'bulk-actions-cron-offload' ); + $message = self::admin_notice_hidden_pending_processing(); } } @@ -113,6 +111,15 @@ class Delete_Permanently { return __( 'The selected posts are already scheduled to be deleted.', 'bulk-actions-cron-offload' ); } + /** + * Provide translated message when posts are hidden pending processing + * + * @return string + */ + public static function admin_notice_hidden_pending_processing() { + return __( 'Some items that would normally be shown here are waiting to be deleted permanently. These items are hidden until then.', 'bulk-actions-cron-offload' ); + } + /** * When a delete is pending for a given post type, hide those posts in the admin * diff --git a/includes/class-edit.php b/includes/class-edit.php index 5361b30bec6a4cd1f98da36c7b1bcbb73d0f656b..595f984fc9cdfa2c2f468ba67fae152aa5bc6afb 100644 --- a/includes/class-edit.php +++ b/includes/class-edit.php @@ -35,9 +35,6 @@ class Edit { return; } - // We want to use `bulk_edit_posts()`. - require_once ABSPATH . '/wp-admin/includes/post.php'; - // `bulk_edit_posts()` takes an array, normally `$_REQUEST`, so we convert back. $request_array = get_object_vars( $vars ); unset( $request_array['action'] ); @@ -75,34 +72,6 @@ class Edit { do_action( 'bulk_actions_cron_offload_edit_request_completed', $results, $vars ); } - /** - * Let the user know what's going on - * - * Not used for post-request redirect - */ - public static function admin_notices() { - $screen = get_current_screen(); - - $type = ''; - $message = ''; - - if ( 'edit' === $screen->base ) { - if ( isset( $_REQUEST['post_status'] ) && 'trash' === $_REQUEST['post_status'] ) { - return; - } - - $status = isset( $_REQUEST['post_status'] ) ? $_REQUEST['post_status'] : 'all'; - $pending = Main::get_post_ids_for_pending_events( self::ACTION, $screen->post_type, $status ); - - if ( ! empty( $pending ) ) { - $type = 'warning'; - $message = __( 'Some items that would normally be shown here are waiting to be edited. These items are hidden until they are processed.', 'bulk-actions-cron-offload' ); - } - } - - Main::render_admin_notice( $type, $message ); - } - /** * Provide post-redirect success message * @@ -122,25 +91,12 @@ class Edit { } /** - * When an edit is pending for a given post type, hide those posts in the admin + * Provide notice when posts are hidden pending edits * - * @param string $where Posts' WHERE clause. - * @param object $q WP_Query object. * @return string */ - public static function hide_posts( $where, $q ) { - if ( 'trash' === $q->get( 'post_status' ) ) { - return $where; - } - - $post__not_in = Main::get_post_ids_for_pending_events( self::ACTION, $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; + public static function admin_notice_hidden_pending_processing() { + return __( 'Some items that would normally be shown here are waiting to be edited. These items are hidden until they are processed.', 'bulk-actions-cron-offload' ); } } diff --git a/includes/class-main.php b/includes/class-main.php index 2705149ca224fd789f4ad7a74d96c509d0f76553..e0c6a2fa011d2d9ce4639b95189b684906af42a5 100644 --- a/includes/class-main.php +++ b/includes/class-main.php @@ -32,6 +32,7 @@ class Main { public static function load() { add_action( self::CRON_EVENT, array( __CLASS__, 'do_cron' ) ); + // TODO: add for upload.php and edit-comments.php. Anything else? add_action( 'load-edit.php', array( __CLASS__, 'intercept' ) ); add_action( 'admin_notices', array( __CLASS__, 'admin_notices' ) ); @@ -62,13 +63,12 @@ class Main { $vars = self::capture_vars(); $action = self::build_hook( $vars->action ); - if ( ! self::bulk_action_allowed( $vars->action ) ) { - return; - } - - // Nothing to do, unless we're emptying the trash. - if ( empty( $vars->posts ) && 'delete_all' !== $vars->action ) { - self::do_admin_redirect( self::ADMIN_NOTICE_KEY, false ); + // What kind of action is this? + if ( self::is_core_action( $vars->action ) ) { + // Nothing to do, unless we're emptying the trash. + if ( empty( $vars->posts ) && 'delete_all' !== $vars->action ) { + self::do_admin_redirect( self::ADMIN_NOTICE_KEY, false ); + } } // Pass request to a class to handle offloading to cron, UX, etc. @@ -99,11 +99,24 @@ class Main { * Capture relevant variables */ private static function capture_vars() { - $vars = array_merge( array( 'action', 'user_id' ), self::get_supported_vars() ); + $vars = array( 'action', 'custom_action', 'user_id', 'current_screen' ); // Extra data that normally would be available from the context. + $vars = array_merge( $vars, self::get_supported_vars() ); $vars = (object) array_fill_keys( $vars, null ); + // All permissions checks must be re-implemented! $vars->user_id = get_current_user_id(); + // Some dynamic hooks need screen data, but we don't need help and other private data. + // Fortunately, Core's private convention is used in the \WP_Screen class. + $screen = get_current_screen(); + $screen = get_object_vars( $screen ); + $screen = array_filter( $screen, function( $key ) { + return 0 !== strpos( $key, '_' ); + }, ARRAY_FILTER_USE_KEY ); + $vars->current_screen = (object) $screen; + unset( $screen ); + + // Remainder of data comes from $_REQUEST. if ( isset( $_REQUEST['delete_all'] ) || isset( $_REQUEST['delete_all2'] ) ) { $vars->action = 'delete_all'; } elseif ( isset( $_REQUEST['action'] ) && '-1' !== $_REQUEST['action'] ) { @@ -174,6 +187,12 @@ class Main { $vars->keep_private = true; } + // Standardize custom actions. + if ( ! self::is_core_action( $vars->action ) ) { + $vars->custom_action = $vars->action; + $vars->action = 'custom'; + } + return $vars; } @@ -202,13 +221,13 @@ class Main { } /** - * Validate action + * Is this one of Core's default actions, or a custom action * * @param string $action Action parsed from request vars. * @return bool */ - public static function bulk_action_allowed( $action ) { - $allowed_actions = array( + public static function is_core_action( $action ) { + $core_actions = array( 'delete', // class Delete_Permanently. 'delete_all', // class Delete_All. 'edit', // class Edit. @@ -216,7 +235,7 @@ class Main { 'untrash', // class Restore_From_Trash. ); - return in_array( $action, $allowed_actions, true ); + return in_array( $action, $core_actions, true ); } /** @@ -235,6 +254,10 @@ class Main { * @return string */ public static function build_hook( $action ) { + if ( ! self::is_core_action( $action ) ) { + $action = 'custom'; + } + return self::ACTION . $action; } diff --git a/includes/class-move-to-trash.php b/includes/class-move-to-trash.php index 74588f750f8d4fcc2ea9258fe0a4810f6aa56185..8ccf2f0d587bb010e919ba3cb1906a6ca738dc58 100644 --- a/includes/class-move-to-trash.php +++ b/includes/class-move-to-trash.php @@ -32,8 +32,6 @@ class Move_To_Trash { $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(); @@ -74,34 +72,6 @@ class Move_To_Trash { } } - /** - * Let the user know what's going on - * - * Not used for post-request redirect - */ - public static function admin_notices() { - $screen = get_current_screen(); - - $type = ''; - $message = ''; - - if ( 'edit' === $screen->base ) { - if ( isset( $_REQUEST['post_status'] ) && 'trash' === $_REQUEST['post_status'] ) { - return; - } - - $status = isset( $_REQUEST['post_status'] ) ? $_REQUEST['post_status'] : 'all'; - $pending = Main::get_post_ids_for_pending_events( self::ACTION, $screen->post_type, $status ); - - if ( ! empty( $pending ) ) { - $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-actions-cron-offload' ); - } - } - - Main::render_admin_notice( $type, $message ); - } - /** * Provide post-redirect success message * @@ -121,25 +91,12 @@ class Move_To_Trash { } /** - * When a move is pending for a given post type, hide those posts in the admin + * Provide translated message when posts are hidden pending move * - * @param string $where Posts' WHERE clause. - * @param object $q WP_Query object. * @return string */ - public static function hide_posts( $where, $q ) { - if ( 'trash' === $q->get( 'post_status' ) ) { - return $where; - } - - $post__not_in = Main::get_post_ids_for_pending_events( self::ACTION, $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; + public static function admin_notice_hidden_pending_processing() { + return __( '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-actions-cron-offload' ); } } diff --git a/includes/class-restore-from-trash.php b/includes/class-restore-from-trash.php index c3bc23459d392864795a1278e9399697aadbb23b..ff1a764885bdc0e90a4881fff14e0086e4b40b0f 100644 --- a/includes/class-restore-from-trash.php +++ b/includes/class-restore-from-trash.php @@ -32,8 +32,6 @@ class Restore_From_Trash { $count = 0; if ( is_array( $vars->posts ) && ! empty( $vars->posts ) ) { - require_once ABSPATH . '/wp-admin/includes/post.php'; - $restored = array(); $locked = array(); $auth_error = array(); @@ -88,7 +86,7 @@ class Restore_From_Trash { if ( 'edit' === $screen->base && isset( $_REQUEST['post_status'] ) && 'trash' === $_REQUEST['post_status'] ) { if ( Main::get_post_ids_for_pending_events( self::ACTION, $screen->post_type, 'trash' ) ) { $type = 'warning'; - $message = __( 'Some items that would normally be shown here are waiting to be restored from the trash. These items are hidden until they are restored.', 'bulk-actions-cron-offload' ); + $message = self::admin_notice_hidden_pending_processing(); } } @@ -113,6 +111,15 @@ class Restore_From_Trash { return __( 'The selected posts are already scheduled to be restored.', 'bulk-actions-cron-offload' ); } + /** + * Provide translated message when posts are hidden pending restoration + * + * @return string + */ + public static function admin_notice_hidden_pending_processing() { + return __( 'Some items that would normally be shown here are waiting to be restored from the trash. These items are hidden until they are restored.', 'bulk-actions-cron-offload' ); + } + /** * When a restore is pending for a given post type, hide those posts in the admin * diff --git a/includes/trait-bulk-actions.php b/includes/trait-bulk-actions.php index 4580755d8cde157294f8e4533da4ee10050b0a3a..a504c2bfe4ec5fb9b249743ef2495a7cbf3280f5 100644 --- a/includes/trait-bulk-actions.php +++ b/includes/trait-bulk-actions.php @@ -19,7 +19,7 @@ trait Bulk_Actions { add_action( Main::build_cron_hook( self::ACTION ), array( __CLASS__, 'process_via_cron' ) ); add_action( 'admin_notices', array( __CLASS__, 'render_admin_notices' ) ); - add_filter( 'posts_where', array( __CLASS__, 'hide_posts' ), 999, 2 ); + add_filter( 'posts_where', array( __CLASS__, 'hide_posts_common' ), 999, 2 ); add_filter( 'removable_query_args', array( __CLASS__, 'remove_notice_arg' ) ); @@ -47,6 +47,18 @@ trait Bulk_Actions { } } + /** + * Prepare environment for individual actions + * + * @param object $vars Bulk-request variables. + */ + public static function process_via_cron( $vars ) { + // Normally processed in the admin context. + require_once( ABSPATH . 'wp-admin/includes/admin.php' ); + + parent::process_via_cron( $vars ); + } + /** * Render the post-redirect notice, or hand off to class for other notices */ @@ -67,6 +79,34 @@ trait Bulk_Actions { self::admin_notices(); } + /** + * Let the user know what's going on + * + * Not used for post-request redirect + */ + public static function admin_notices() { + $screen = get_current_screen(); + + $type = ''; + $message = ''; + + if ( 'edit' === $screen->base ) { + if ( isset( $_REQUEST['post_status'] ) && 'trash' === $_REQUEST['post_status'] ) { + return; + } + + $status = isset( $_REQUEST['post_status'] ) ? $_REQUEST['post_status'] : 'all'; + $pending = Main::get_post_ids_for_pending_events( self::ACTION, $screen->post_type, $status ); + + if ( ! empty( $pending ) ) { + $type = 'warning'; + $message = self::admin_notice_hidden_pending_processing(); + } + } + + Main::render_admin_notice( $type, $message ); + } + /** * Provide translated success message for bulk action * @@ -86,13 +126,22 @@ trait Bulk_Actions { } /** - * When an edit is pending for a given post type, hide those posts in the admin + * Provide translated message when posts are hidden pending processing + * + * @return string + */ + public static function admin_notice_hidden_pending_processing() { + return ''; + } + + /** + * When a process 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( $where, $q ) { + public static function hide_posts_common( $where, $q ) { if ( ! is_admin() || ! $q->is_main_query() ) { return $where; } @@ -101,7 +150,29 @@ trait Bulk_Actions { return $where; } - return parent::hide_posts( $where, $q ); + return self::hide_posts( $where, $q ); + } + + /** + * Hide posts pending processing + * + * @param string $where Posts' WHERE clause. + * @param object $q WP_Query object. + * @return string + */ + public static function hide_posts( $where, $q ) { + if ( 'trash' === $q->get( 'post_status' ) ) { + return $where; + } + + $post__not_in = Main::get_post_ids_for_pending_events( self::ACTION, $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; } /**