diff --git a/bulk-edit-cron-offload.php b/bulk-edit-cron-offload.php index ccfb942f93e8729e001dbb3b0ee1e64da7bb147d..4aedcab57da06cc71bc9f073b793768d16ade82b 100644 --- a/bulk-edit-cron-offload.php +++ b/bulk-edit-cron-offload.php @@ -21,3 +21,4 @@ require __DIR__ . '/includes/utils.php'; require __DIR__ . '/includes/class-main.php'; require __DIR__ . '/includes/class-delete-all.php'; require __DIR__ . '/includes/class-move-to-trash.php'; +require __DIR__ . '/includes/class-restore-from-trash.php'; diff --git a/includes/class-main.php b/includes/class-main.php index 1a477327800daa97a31fd669039702d4dcb2d617..e6a0d31e6170a2e80f450772f6625db0f7e2ced9 100644 --- a/includes/class-main.php +++ b/includes/class-main.php @@ -151,11 +151,11 @@ class Main { */ public static function bulk_action_allowed( $action ) { $allowed_actions = array( - 'delete', - 'delete_all', + 'delete', // TODO: "Delete permantently" in Trash. + 'delete_all', // class Delete_All. 'edit', - 'trash', - 'untrash', + 'trash', // class Move_To_trash. + 'untrash', // class Restore_From_Trash. ); return in_array( $action, $allowed_actions, true ); @@ -253,6 +253,78 @@ class Main { </div> <?php } + + /** + * Gather pending events for given conditions + * + * @param string $bulk_action Bulk action to filter by. + * @param string $post_type Post type needing exclusion. + * @param string $post_status Post status to filter by. + * @return array + */ + public static function get_all_pending_events_for_action( $bulk_action, $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 ( self::CRON_EVENT !== $action ) { + continue; + } + + foreach ( $action_instances as $instance => $instance_args ) { + $vars = array_shift( $instance_args['args'] ); + + if ( $bulk_action === $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 for given conditions + * + * @param string $bulk_action Bulk action to filter by. + * @param string $post_type Post type needing exclusion. + * @param string $post_status Post status to filter by. + * @return array + */ + public static function get_post_ids_for_pending_events( $bulk_action, $post_type, $post_status ) { + $events = wp_list_pluck( self::get_all_pending_events_for_action( $bulk_action, $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; + } } Main::load(); diff --git a/includes/class-move-to-trash.php b/includes/class-move-to-trash.php index 3350e81e2ac1ef0cb80125b03c6f1287068274c7..94fb84a3bbc9e751383db70c80b8b75a320921c2 100644 --- a/includes/class-move-to-trash.php +++ b/includes/class-move-to-trash.php @@ -118,9 +118,10 @@ class Move_To_Trash { return; } - $status = isset( $_REQUEST['post_status'] ) ? $_REQUEST['post_status'] : 'all'; + $status = isset( $_REQUEST['post_status'] ) ? $_REQUEST['post_status'] : 'all'; + $pending = Main::get_post_ids_for_pending_events( self::ACTION, $screen->post_type, $status ); - if ( self::get_all_pending_actions( $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-edit-cron-offload' ); } @@ -149,7 +150,7 @@ class Move_To_Trash { return $where; } - $post__not_in = self::get_post_ids_pending_move( $q->get( 'post_type' ), $q->get( 'post_status' ) ); + $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 ); @@ -158,76 +159,6 @@ class Move_To_Trash { 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 ( self::ACTION === $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(); diff --git a/includes/class-restore-from-trash.php b/includes/class-restore-from-trash.php new file mode 100644 index 0000000000000000000000000000000000000000..f1e79b54729d066a234d8790f0ca55e2f3cfe168 --- /dev/null +++ b/includes/class-restore-from-trash.php @@ -0,0 +1,157 @@ +<?php +/** + * Offload "Restore from Trash" + * + * @package Bulk_Edit_Cron_Offload + */ + +namespace Automattic\WP\Bulk_Edit_Cron_Offload; + +/** + * Class Restore_From_Trash + */ +class Restore_From_Trash { + /** + * Class constants + */ + const ACTION = 'untrash'; + + const ADMIN_NOTICE_KEY = 'bulk_edit_cron_offload_restore_from_trash'; + + /** + * Register this bulk process' hooks + */ + public static function register_hooks() { + add_action( Main::build_hook( self::ACTION ), array( __CLASS__, 'process' ) ); + add_action( Main::build_cron_hook( self::ACTION ), array( __CLASS__, 'process_via_cron' ) ); + + add_action( 'admin_notices', array( __CLASS__, 'admin_notices' ) ); + add_filter( 'posts_where', array( __CLASS__, 'hide_posts_pending_restore' ), 999, 2 ); + } + + /** + * Handle a request to restore some posts from 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 restore requested items from 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'; + + $restored = array(); + $locked = array(); + $auth_error = array(); + $error = array(); + + foreach ( $vars->posts as $post_id ) { + // Can the user restore 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 restoring. + $post_restored = wp_untrash_post( $post_id ); + if ( $post_restored ) { + $restored[] = $post_id; + } else { + $error[] = $post_id; + } + + // Take a break periodically. + if ( 0 === $count++ % 50 ) { + stop_the_insanity(); + sleep( 3 ); + } + } + + $results = compact( 'restored', 'locked', 'auth_error', 'error' ); + do_action( 'bulk_edit_cron_offload_restore_from_trash_request_completed', $results, $vars ); + } else { + do_action( 'bulk_edit_cron_offload_restore_from_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 restored shortly.', 'bulk-edit-cron-offload' ); + } else { + $type = 'error'; + $message = __( 'The selected posts are already scheduled to be restored.', 'bulk-edit-cron-offload' ); + } + } elseif ( '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-edit-cron-offload' ); + } + } + + Main::render_admin_notice( $type, $message ); + } + + /** + * When a restore 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_restore( $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 = 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; + } +} + +Restore_From_Trash::register_hooks(); diff --git a/languages/bulk-edit-cron-offload.pot b/languages/bulk-edit-cron-offload.pot index 00f39df29866b2f7d3de38def2161bca21a7ddcc..c90811ff746d5f35c74167c6571d4c8fe01e42a4 100644 --- a/languages/bulk-edit-cron-offload.pot +++ b/languages/bulk-edit-cron-offload.pot @@ -5,7 +5,7 @@ msgstr "" "Project-Id-Version: Bulk Edit Cron Offload 1.0\n" "Report-Msgid-Bugs-To: " "https://wordpress.org/support/plugin/bulk-edit-cron-offload\n" -"POT-Creation-Date: 2017-09-13 05:28:23+00:00\n" +"POT-Creation-Date: 2017-09-14 05:06:18+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -25,32 +25,46 @@ msgstr "" "X-Poedit-Bookmarks: \n" "X-Textdomain-Support: yes\n" -#: includes/class-delete-all.php:121 +#: includes/class-delete-all.php:123 msgid "Success! The trash will be emptied shortly." msgstr "" -#: includes/class-delete-all.php:124 +#: includes/class-delete-all.php:126 msgid "A request to empty the trash is already pending for this post type." msgstr "" -#: includes/class-delete-all.php:129 +#: includes/class-delete-all.php:131 msgid "A pending request to empty the trash will be processed soon." msgstr "" -#: includes/class-move-to-trash.php:109 +#: includes/class-move-to-trash.php:111 msgid "Success! The selected posts will be moved to the trash shortly." msgstr "" -#: includes/class-move-to-trash.php:112 +#: includes/class-move-to-trash.php:114 msgid "The selected posts are already scheduled to be moved to the trash." msgstr "" -#: includes/class-move-to-trash.php:123 +#: includes/class-move-to-trash.php:126 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 "" +#: includes/class-restore-from-trash.php:111 +msgid "Success! The selected posts will be restored shortly." +msgstr "" + +#: includes/class-restore-from-trash.php:114 +msgid "The selected posts are already scheduled to be restored." +msgstr "" + +#: includes/class-restore-from-trash.php:119 +msgid "" +"Some items that would normally be shown here are waiting to be restored " +"from the trash. These items are hidden until they are restored." +msgstr "" + #. Plugin Name of the plugin/theme msgid "Bulk Edit Cron Offload" msgstr ""