diff --git a/bulk-actions-cron-offload.php b/bulk-actions-cron-offload.php index 630b03a78226bbec8d062b031567f99b542312ae..bea57ae64b065c754f6cbc76b68bbd2c571b6c4f 100644 --- a/bulk-actions-cron-offload.php +++ b/bulk-actions-cron-offload.php @@ -21,5 +21,6 @@ require __DIR__ . '/includes/utils.php'; require __DIR__ . '/includes/class-main.php'; require __DIR__ . '/includes/class-delete-all.php'; require __DIR__ . '/includes/class-delete-permanently.php'; +require __DIR__ . '/includes/class-edit.php'; require __DIR__ . '/includes/class-move-to-trash.php'; require __DIR__ . '/includes/class-restore-from-trash.php'; diff --git a/includes/class-edit.php b/includes/class-edit.php new file mode 100644 index 0000000000000000000000000000000000000000..c802041cfe62b94cd5e93a95a787a059fd496e4a --- /dev/null +++ b/includes/class-edit.php @@ -0,0 +1,165 @@ +<?php +/** + * Offload "Edit" + * + * @package Bulk_Actions_Cron_Offload + */ + +namespace Automattic\WP\Bulk_Actions_Cron_Offload; + +/** + * Class Edit + */ +class Edit { + /** + * Class constants + */ + const ACTION = 'edit'; + + const ADMIN_NOTICE_KEY = 'bulk_actions_cron_offload_edit'; + + /** + * 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_move' ), 999, 2 ); + } + + /** + * Handle a request to edit some posts + * + * @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 edit requested items + * + * @param object $vars Bulk-request variables. + */ + public static function process_via_cron( $vars ) { + // Nothing to edit. + if ( ! is_array( $vars->posts ) || empty( $vars->posts ) ) { + do_action( 'bulk_actions_cron_offload_edit_request_no_posts', $vars->posts, $vars ); + 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'] ); + unset( $request_array['user_id'] ); + + // Modify some keys to match `bulk_edit_post()`'s expectations. + $request_array['post'] = $request_array['posts']; + unset( $request_array['posts'] ); + + if ( ! is_null( $request_array['post_sticky'] ) ) { + $request_array['sticky'] = $request_array['post_sticky']; + } + unset( $request_array['post_sticky'] ); + + if ( is_null( $request_array['post_status'] ) || 'all' === $request_array['post_status'] ) { + $request_array['_status'] = -1; + } else { + $request_array['_status'] = $request_array['post_status']; + } + unset( $request_array['post_status'] ); + + // `bulk_edit_posts()` calls `current_user_can()`, so we make sure it can. + wp_set_current_user( $vars->user_id ); + + // Perform bulk edit. + $results = bulk_edit_posts( $request_array ); + $edited = $results['updated']; + $error = $results['skipped']; + $locked = $results['locked']; + + // `bulk_edit_posts()` mixes these without indicating which it was. + $auth_error = $error; + + $results = compact( 'edited', 'locked', 'auth_error', 'error' ); + do_action( 'bulk_actions_cron_offload_edit_request_completed', $results, $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 edited shortly.', 'bulk-actions-cron-offload' ); + } else { + $type = 'error'; + $message = __( 'The requested edits are already pending for the chosen posts.', 'bulk-actions-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'; + $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 ); + } + + /** + * When an edit 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 = 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; + } +} + +Edit::register_hooks(); diff --git a/includes/class-main.php b/includes/class-main.php index 750887f86bcbd1f249d6a77742e34f489337e438..f29b3373282c099ba90b5d6d6d375e8bef49b7fb 100644 --- a/includes/class-main.php +++ b/includes/class-main.php @@ -87,7 +87,8 @@ class Main { * Capture relevant variables */ private static function capture_vars() { - $vars = (object) array_fill_keys( array( 'user_id', 'action', 'post_type', 'posts', 'tax_input', 'post_author', 'comment_status', 'ping_status', 'post_status', 'post_sticky', 'post_format' ), null ); + $vars = array_merge( array( 'action', 'user_id' ), self::get_supported_vars() ); + $vars = (object) array_fill_keys( $vars, null ); $vars->user_id = get_current_user_id(); @@ -111,6 +112,10 @@ class Main { $vars->tax_input = $_REQUEST['tax_input']; } + if ( isset( $_REQUEST['post_category'] ) && is_array( $_REQUEST['post_category'] ) ) { + $vars->post_category = $_REQUEST['post_category']; + } + if ( isset( $_REQUEST['post_author'] ) && -1 !== (int) $_REQUEST['post_author'] ) { $vars->post_author = (int) $_REQUEST['post_author']; } @@ -135,14 +140,55 @@ class Main { $vars->post_format = $_REQUEST['post_format']; } + if ( isset( $_REQUEST['post_parent'] ) && '-1' !== $_REQUEST['post_parent'] ) { + $vars->post_parent = (int) $_REQUEST['post_parent']; + } + + if ( isset( $_REQUEST['page_template'] ) && '-1' !== $_REQUEST['page_template'] ) { + $vars->page_template = $_REQUEST['page_template']; + } + + if ( isset( $_REQUEST['post_password'] ) && ! empty( $_REQUEST['post_password'] ) ) { + $vars->post_password = $_REQUEST['post_password']; + } + // Post status is special. if ( is_null( $vars->post_status ) && isset( $_REQUEST['post_status'] ) && ! empty( $_REQUEST['post_status'] ) ) { $vars->post_status = $_REQUEST['post_status']; } + // Another special case, dependent on post status. + if ( isset( $_REQUEST['keep_private'] ) && 'private' === $vars->post_status ) { + $vars->keep_private = true; + } + return $vars; } + /** + * List allowed $_REQUEST variables + * + * @return array + */ + private static function get_supported_vars() { + return array( + 'comment_status', + 'keep_private', + 'page_template', + 'ping_status', + 'post_author', + 'post_category', + 'post_format', + 'post_parent', + 'post_password', + 'post_status', + 'post_sticky', + 'post_type', + 'posts', + 'tax_input', + ); + } + /** * Validate action * @@ -153,7 +199,7 @@ class Main { $allowed_actions = array( 'delete', // class Delete_Permanently. 'delete_all', // class Delete_All. - 'edit', + 'edit', // class Edit. 'trash', // class Move_To_Trash. 'untrash', // class Restore_From_Trash. ); diff --git a/languages/bulk-actions-cron-offload.pot b/languages/bulk-actions-cron-offload.pot index 860c4d37f7cbbf73e6a11fdd0ac2d78ce3561701..1900db3b0dd004d532d4d1cbe0b477129310fcff 100644 --- a/languages/bulk-actions-cron-offload.pot +++ b/languages/bulk-actions-cron-offload.pot @@ -5,7 +5,7 @@ msgstr "" "Project-Id-Version: Bulk Actions Cron Offload 1.0\n" "Report-Msgid-Bugs-To: " "https://wordpress.org/support/plugin/bulk-actions-cron-offload\n" -"POT-Creation-Date: 2017-09-14 18:55:07+00:00\n" +"POT-Creation-Date: 2017-09-14 22:18:30+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -51,6 +51,20 @@ msgid "" "permanently. These items are hidden until then." msgstr "" +#: includes/class-edit.php:112 +msgid "Success! The selected posts will be edited shortly." +msgstr "" + +#: includes/class-edit.php:115 +msgid "The requested edits are already pending for the chosen posts." +msgstr "" + +#: includes/class-edit.php:127 +msgid "" +"Some items that would normally be shown here are waiting to be edited. " +"These items are hidden until they are processed." +msgstr "" + #: includes/class-move-to-trash.php:111 msgid "Success! The selected posts will be moved to the trash shortly." msgstr ""