Reading and the Orbitstem settings page. */ class GuardianPage { const SLUG = 'orbitstem-seo-guardian'; const ACTION = 'orbitstem_seo_guardian'; /** * Plugin services. * * @var Plugin */ private $plugin; /** * Constructor. * * @param Plugin $plugin Plugin services. */ public function __construct( Plugin $plugin ) { $this->plugin = $plugin; } /** * Registers hooks. */ public function register() { add_action( 'admin_menu', array( $this, 'menu' ), 17 ); add_action( 'admin_post_' . self::ACTION, array( $this, 'handle' ) ); add_filter( 'post_row_actions', array( $this, 'row_action' ), 10, 2 ); add_filter( 'page_row_actions', array( $this, 'row_action' ), 10, 2 ); add_action( 'admin_footer-options-reading.php', array( $this, 'reading_confirm' ) ); add_action( 'admin_footer', array( $this, 'settings_confirm' ) ); } /** * Adds the submenu page. */ public function menu() { add_submenu_page( Admin::SLUG, __( 'Guardian', 'orbitstem-seo' ), __( 'Guardian', 'orbitstem-seo' ), 'edit_posts', self::SLUG, array( $this, 'render' ) ); } /** * "Trash (check impact)" for published items. * * @param array $actions Row actions. * @param \WP_Post $post Post. * @return array */ public function row_action( $actions, $post ) { if ( 'publish' === $post->post_status && current_user_can( 'delete_post', $post->ID ) && $this->plugin->types->supports_post_type( $post->post_type ) ) { $actions['orbitstem_impact'] = sprintf( '%s', esc_url( add_query_arg( array( 'page' => self::SLUG, 'post' => $post->ID, ), admin_url( 'admin.php' ) ) ), esc_html__( 'Trash (check impact)', 'orbitstem-seo' ) ); } return $actions; } /** * Handles trash-with-redirect, redirect-after-trash and undo. */ public function handle() { check_admin_referer( self::ACTION ); $task = isset( $_POST['task'] ) ? sanitize_key( wp_unslash( $_POST['task'] ) ) : ''; $back = array( 'page' => self::SLUG ); if ( 'trash' === $task ) { $post_id = isset( $_POST['post'] ) ? absint( $_POST['post'] ) : 0; $target = $this->chosen_target(); $result = $this->trash_with_redirect( $post_id, $target ); RedirectsPage::notice( is_wp_error( $result ) ? 'error' : 'success', is_wp_error( $result ) ? $result->get_error_message() : __( 'Moved to the trash. You can undo this below.', 'orbitstem-seo' ) ); if ( is_wp_error( $result ) ) { $back['post'] = $post_id; } } elseif ( 'redirect' === $task ) { $change = $this->plugin->changes->get( isset( $_POST['change'] ) ? absint( $_POST['change'] ) : 0 ); if ( ! $change || ! current_user_can( Admin::capability() ) ) { wp_die( esc_html__( 'You do not have permission to do this.', 'orbitstem-seo' ), 403 ); } // Only a trash entry has the old address to redirect; any other change ID is refused cleanly. if ( ! is_array( $change['state_before'] ) || empty( $change['state_before']['url'] ) ) { wp_die( esc_html__( 'This change has no old address to redirect.', 'orbitstem-seo' ), 400 ); } $created = $this->plugin->redirects->create( array( 'source' => $change['state_before']['url'], 'target' => $this->chosen_target(), 'code' => 301, ) ); RedirectsPage::notice( is_wp_error( $created ) ? 'error' : 'success', is_wp_error( $created ) ? $created->get_error_message() : __( 'Redirect added.', 'orbitstem-seo' ) ); } elseif ( 'undo' === $task ) { $result = $this->plugin->repair->rollback( isset( $_POST['change'] ) ? absint( $_POST['change'] ) : 0 ); RedirectsPage::notice( is_wp_error( $result ) ? 'error' : 'success', is_wp_error( $result ) ? $result->get_error_message() : __( 'Change undone.', 'orbitstem-seo' ) ); } wp_safe_redirect( add_query_arg( $back, admin_url( 'admin.php' ) ) ); exit; } /** * Redirect target chosen in a form: a preset or a custom address. * * @return string */ private function chosen_target() { // phpcs:disable WordPress.Security.NonceVerification.Missing -- verified in handle(); the value is validated again by Repository::validate(). $choice = isset( $_POST['target'] ) ? trim( sanitize_text_field( wp_unslash( $_POST['target'] ) ) ) : ''; if ( 'custom' === $choice ) { $choice = isset( $_POST['custom'] ) ? trim( sanitize_text_field( wp_unslash( $_POST['custom'] ) ) ) : ''; } // phpcs:enable return $choice; } /** * Trashes a post, optionally adding a redirect from its address first. * * @param int $post_id Post ID. * @param string $target Redirect target, or '' for none. * @return int|\WP_Error Change ID. */ public function trash_with_redirect( $post_id, $target ) { $post = get_post( $post_id ); if ( ! $post || 'publish' !== $post->post_status ) { return new \WP_Error( 'state', __( 'Only published items can be trashed here.', 'orbitstem-seo' ) ); } if ( ! current_user_can( 'delete_post', $post->ID ) ) { return new \WP_Error( 'permission', __( 'You cannot trash this item.', 'orbitstem-seo' ) ); } $url = Url::normalize( (string) get_permalink( $post ), home_url( '/' ) ); $redirect_id = 0; if ( '' !== $target ) { if ( ! current_user_can( Admin::capability() ) ) { return new \WP_Error( 'permission', __( 'You cannot add redirects.', 'orbitstem-seo' ) ); } $created = $this->plugin->redirects->create( array( 'source' => $url, 'target' => $target, 'code' => 301, /* translators: %s: title */ 'note' => sprintf( __( 'Added when "%s" was trashed', 'orbitstem-seo' ), $post->post_title ), ), false ); if ( is_wp_error( $created ) ) { return $created; } $redirect_id = (int) $created; } Guardian::suspend(); $trashed = wp_trash_post( $post->ID ); Guardian::resume(); if ( ! $trashed ) { if ( $redirect_id ) { $this->plugin->redirects->delete( $redirect_id, false ); } return new \WP_Error( 'failed', __( 'WordPress could not move the item to the trash.', 'orbitstem-seo' ) ); } return $this->plugin->guardian->record_trash( get_post( $post->ID ), $url, $redirect_id ); } /** * Renders the page. */ public function render() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- view state only. $post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0; $change_id = isset( $_GET['change'] ) ? absint( $_GET['change'] ) : 0; // phpcs:enable Ui::open( self::SLUG, __( 'Guardian', 'orbitstem-seo' ), __( 'Risky changes are recorded here and can be undone while nothing has changed since.', 'orbitstem-seo' ) ); ?> render_impact( $post_id ); } elseif ( $change_id ) { $this->render_after_trash( $change_id ); } else { $this->render_changes(); } ?> ' . esc_html__( 'You cannot view this item.', 'orbitstem-seo' ) . '
'; return; } $impact = $this->plugin->impact->for_post( $post ); $lines = Impact::removal_consequences( $impact ); ?>' . esc_html( $source['title'] ) . ''; } echo wp_kses_post( implode( ', ', $links ) ); ?>
post_status && current_user_can( 'delete_post', $post_id ) ) : ?> plugin->changes->get( $change_id ); if ( ! $change || Guardian::LOG_ID !== $change['fixer'] || 'trash' !== $change['state_after']['kind'] || ( ! current_user_can( Admin::capability() ) && ! current_user_can( 'edit_post', $change['object_id'] ) ) ) { echo '' . esc_html__( 'That change was not found.', 'orbitstem-seo' ) . '
'; return; } $post = get_post( $change['object_id'] ); ?> plugin->impact->for_post( $post ) ); ?>| display_name : '—' ); ?> | undo_button( $change, __( 'Undo', 'orbitstem-seo' ) ); } else { esc_html_e( 'Undone', 'orbitstem-seo' ); } ?> | ||