Skip to content

Commit

Permalink
first take on safe mode #4
Browse files Browse the repository at this point in the history
  • Loading branch information
markkap committed Sep 23, 2021
1 parent c6420f8 commit e490904
Show file tree
Hide file tree
Showing 4 changed files with 348 additions and 1 deletion.
9 changes: 8 additions & 1 deletion src/wp-admin/menu.php
Expand Up @@ -308,7 +308,14 @@
$submenu['backups.php'][15] = array( __( 'Create New' ), 'backup', 'backup-new.php' );
}

$_wp_last_utility_menu = 85; // The index of the last top-level menu in the utility menu group.
if ( ! is_multisite() ) {
$menu[90] = array( __( 'Debug tools' ), 'safe_mode', 'safe-mode.php', '', 'menu-top menu-icon-settings', 'menu-settings', 'dashicons-database' );
if ( \calmpress\calmpress\Safe_Mode::current_user_in_safe_mode() ) {
$submenu['safe-mode.php'][10] = array( __( 'Safe Mode' ), 'safe_mode', 'safe-mode.php' );
}
}

$_wp_last_utility_menu = 90; // The index of the last top-level menu in the utility menu group.

$menu[999] = array( '', 'read', 'separator-last', '', 'wp-menu-separator' );

Expand Down
154 changes: 154 additions & 0 deletions src/wp-admin/safe-mode.php
@@ -0,0 +1,154 @@
<?php
/**
* Safe mode tools Screen.
*
* @package calmPress
*/

declare(strict_types=1);

namespace calmpress\calmpress;

/** WordPress Administration Bootstrap */
require_once dirname( __FILE__ ) . '/admin.php';

if ( ! current_user_can( 'safe_mode' ) ) {
wp_die( esc_html__( 'Sorry, you are not allowed to manage safe mode for this site.' ) );
}

// Use same styling as maintenance mode.
wp_enqueue_style( 'maintenance-mode' );

$title = __( 'Safe Mode' );
$parent_file = 'tools.php';

get_current_screen()->add_help_tab(
array(
'id' => 'overview',
'title' => __( 'Overview' ),
'content' =>
'<p>' . esc_html__( 'The safe mode enables you to investigate why your normal login fails while ptentially keeping the site running.' ) . '</p>',
)
);

require ABSPATH . 'wp-admin/admin-header.php';

?>
<div class="wrap metabox-holder">
<h1><?php echo esc_html( $title ); ?></h1>

<div id="status" class="postbox">
<h3 class="hndle"><?php esc_html_e( 'Status' ); ?></h3>
<div class="inside">
<form action="admin-post.php" method="post">
<input name='action' type="hidden" value='maintenance_mode_status'>
<?php
wp_nonce_field( 'maintenance_mode_status' );
if ( ! Maintenance_Mode::is_active() ) {
echo '<p class="status_line inactive">' . esc_html__( 'Not in maintenance mode' ) . '</p>';
echo '<p>';
esc_html_e( 'If activated, it is expected to last for:' );
?>
<br>
<input class="hours" type="number" min="0" max="999" name="hours" value="0"> :
<input class="minutes" type="number" min="0" max="59" name="minutes" value="00">
<?php esc_html_e( 'Hours' ); ?>
<?php
echo '</p>';
echo '<p class="description">' . esc_html__( 'If no value is given, a 10 minutes value will be assumed. If maintenance mode is still active after the expected time it will be prolonged by 10 minutes untile it is deactivated.' ) . '</p>';
echo '<p><b>' . esc_html__( 'Before activating maintenance mode you might want to consider changing the setting of your analytics or any other plugin that assume "live" content as plugins keep operating fully.' ) . '</b></p>';
submit_button( __( 'Activate maintenance mode' ), 'primary', 'enter' );
} else {
echo '<p class="status_line active">' . esc_html__( 'In maintenance mode' ) . '</p>';
$bypass_url = site_url() . '?' . Maintenance_Mode::BYPASS_NAME . '=' . Maintenance_Mode::bypass_code();
?>
<p>
<?php
printf(
/* translators: 1: shortcode */
esc_html__( 'Bypass maintenance mode with the fillowing URL: %1$s' ),
'<code>' . esc_html( $bypass_url ) . '</code>'
);
?>
<br />
<span class="description">
<?php
esc_html_e( 'Once the URL is used, a cookie is set and the user will automatically bypass the maintenance mode for the rest of the session. The URL changes with every maintenance mode activation.' );
?>
</span>
</p>
<?php
$lasts_for = Maintenance_Mode::projected_time_till_end();

$hours = '0';
$minutes = '00';
if ( $lasts_for <= 10 * MINUTE_IN_SECONDS ) {
echo '<p>' . esc_html__( 'Seems like the initialy configured time had passed, try to estimate again.' ) . '<p>';
}
echo '<p>' . esc_html__( 'Configured to last for another :' );
$hours = intdiv( $lasts_for, 60 * MINUTE_IN_SECONDS );
$minutes = sprintf( '%02d', intdiv( $lasts_for % ( 60 * MINUTE_IN_SECONDS ), 60 ) );
?>
<br>
<input class="hours" type="number" min="0" max="999" name="hours" value="<?php echo esc_attr( $hours ); ?>"> :
<input class="minutes" type="number" min="0" max="59" name="minutes" value="<?php echo esc_attr( $minutes ); ?>">
<?php esc_html_e( 'Hours' ); ?>
<?php
echo '</p>';
echo '<p>';
echo '<p><b>' . esc_html__( 'Before deactivating maintenance mode you might want to change back the settings that were changed before activation.' ) . '</b><p>';
echo get_submit_button( esc_html__( 'Deactivate maintenance mode' ), 'primary', 'exit', false );
echo get_submit_button( esc_html__( 'Change remaining time' ), 'large', 'change_time', false );
echo '</p>';
}
?>
</form>
</div>
</div>
<div id="message" class="postbox">
<h3 class="hndle"><?php esc_html_e( 'Page content' ); ?></h3>
<div class="inside">
<p><a target="_blank" href="<?php echo esc_url( wp_nonce_url( site_url(), Maintenance_Mode::PREVIEW_PARAM, Maintenance_Mode::PREVIEW_PARAM ) ); ?>"><?php esc_html_e( 'Preview page' ); ?></a></p>
<form action="admin-post.php" method="post">
<input name='action' type="hidden" value='maintenance_mode_content'>
<?php wp_nonce_field( 'maintenance_mode_content' ); ?>
<table class="form-table">
<tr><th><label for="page_title"><?php esc_html_e( 'Page title' ); ?></label></th><td><input id="page_title" name="page_title" value="<?php echo esc_attr( Maintenance_Mode::page_title() ); ?>"></td></tr>
<tr><th><label for="text_title"><?php esc_html_e( 'Text title' ); ?></label></th><td><input id="text_title" name="text_title" value="<?php echo esc_attr( Maintenance_Mode::text_title() ); ?>"></td></tr>
<tr><th><label for="theme_page"><?php esc_html_e( 'Use normal header and footer' ); ?></label></th><td><input id="theme_page" name="theme_page" type="checkbox" <?php checked( Maintenance_Mode::theme_frame_used() ); ?>></td></tr>
<tr><th><label><?php esc_html_e( 'Message text' ); ?></label></th></tr>
<tr><td colspan="2">
<?php
$content = Maintenance_Mode::content();
$editor_id = 'message_text';
$settings = [
'media_buttons' => true,
'wpautop' => true,
'quicktags' => true,
'textarea_rows' => 5,
];
wp_editor( $content, $editor_id, $settings );
?>
<p class="description">
<?php
printf(
/* translators: 1: shortcode */
esc_html__( 'Use the %1$s shortcode to insert the approximate time left' ),
'<code>[maintenance_left]</code>'
);
?>
<br/>
<?php
esc_html_e( 'Other shortcodes can be used as well but you will need to check if they work as expected.' );
?>
</p>
</td></tr>
</table>
<?php submit_button(); ?>
</form>
</div>
</div>
</div>

<?php
require ABSPATH . 'wp-admin/admin-footer.php';
1 change: 1 addition & 0 deletions src/wp-includes/calmpress/autoloader.php
Expand Up @@ -60,6 +60,7 @@
'calmpress\object_cache\File' => __DIR__ . '/object-cache/class-file.php',
'calmpress\object_cache\Null_Cache' => __DIR__ . '/object-cache/class-null-cache.php',
'calmpress\calmpress\Maintenance_Mode' => __DIR__ . '/calmpress/class-maintenance-mode.php',
'calmpress\calmpress\Safe_Mode' => __DIR__ . '/calmpress/class-safe-mode.php',
'Psr\SimpleCache\CacheInterface' => ABSPATH . 'wp-includes/Psr/SimpleCache/CacheInterface.php',
'Psr\SimpleCache\CacheException' => ABSPATH . 'wp-includes/Psr/SimpleCache/CacheException.php',
'Psr\SimpleCache\InvalidArgumentException' => ABSPATH . 'wp-includes/Psr/SimpleCache/InvalidArgumentException.php',
Expand Down
185 changes: 185 additions & 0 deletions src/wp-includes/calmpress/calmpress/class-safe-mode.php
@@ -0,0 +1,185 @@
<?php
/**
* Implementation of safe mode related utils.
*
* @package calmPress
* @since 1.0.0
*/

declare(strict_types=1);

namespace calmpress\calmpress;

/**
* Safe mode utils packaged as a class.
*
* @since 1.0.0
*/
class Safe_Mode {

/**
* The name of the cookie and URL parameter that might include the bypass code.
*
* @since 1.0.0
*/
const ACTIVATION_NAME = 'safe_mode';

/**
* deactivate the maintenance mode..
*
* @since 1.0.0
*/
public static function deactivate() {
update_option( self::OPTION_NAME, '' );
}

/**
* Helper function to set the safe mode cookie, exists mainly to be able to avoid headers
* already sent type of errors during testing.
*
* @since 1.0.0
*/
protected static function set_safe_mode_cookie() {
setcookie( self::ACTIVATION_NAME, 1, 0, COOKIEPATH, COOKIE_DOMAIN, is_ssl() );
}

/**
* Helper function to clear the safe mode cookie, exists mainly to be able to avoid headers
* already sent type of errors during testing.
*
* @since 1.0.0
*/
protected static function clear_safe_mode_cookie() {
setcookie( self::ACTIVATION_NAME, 1, time() - 1000, COOKIEPATH, COOKIE_DOMAIN, is_ssl() );
}

/**
* Indicate if safe mode is active. for this request. To be active the user needs a safe_mode
* capability and a safe_mode parameter specified as a URL parameter or in a cookie.
*
* @since 1.0.0
*
* @return bool true if safe mode is active for the user, false otherwise.
*/
public static function current_user_in_safe_mode():bool {

// safe mode via URL paremeter.
if ( isset( $_GET[ self::ACTIVATION_NAME ] ) &&
current_user_can( 'safe_mode' ) ) {
// Set the cookie only for the session.
static::set_safe_mode_cookie();
return true;
}

// safe mode via cookie.
if ( isset( $_COOKIE[ self::ACTIVATION_NAME ] ) &&
current_user_can( 'safe_mode' ) ) {
return true;
}

return false;
}

/**
* Exit the current user from safe mode.
*
* Bsically removes the safe mode cookie. Usage of URL parameter can still put the user
* into safe mode.
*
* @since 1.0.0
*/
public static function exit_safe_mode():bool {

static::clear_safe_mode_cookie();
}

/**
* Verify capability, nonce, and validitty of referer data a POST request. Die if the
* user is not allowed to changed safe mode related data, or nonce/referer include
* bad data.
*
* @since 1.0.0
*
* @param string $action The name of the action expected to be used for generating the nonce
* and admin referer fields in the request.
*/
private static function verify_post_request( string $action ) {
if ( ! current_user_can( 'safe_mode' ) ) {
wp_die(
'<h1>' . __( 'You need additional permission.' ) . '</h1>' .
'<p>' . __( 'Sorry, you are not allowed to manage safe mode for this site.' ) . '</p>',
403
);
}
check_admin_referer( $action );
}

/**
* Handles the form post regarding content related maintenance page changes. Updates the
* post holding the content data.
*
* Used as a hook on admin-post.
*
* @since 1.0.0
*/
public static function handle_content_change_post() {
$errors = [];
static::verify_post_request( 'maintenance_mode_content' );

if ( ! isset( $_POST['page_title'] ) || ! isset( $_POST['text_title'] ) || ! isset( $_POST['message_text'] ) ) {
$errors[] = esc_html__( 'Something went wrong, please try again' );
} else {
static::set_page_title( wp_unslash( $_POST['page_title'] ) );
static::set_text_title( wp_unslash( $_POST['text_title'] ) );
static::set_content( wp_unslash( $_POST['message_text'] ) );
static::set_use_theme_frame( isset( $_POST['theme_page'] ) );
}

set_transient( 'maintenance_mode_errors', $errors, 30 );

// Redirect back to the settings page that was submitted.
$goback = add_query_arg( 'settings-updated', 'true', wp_get_referer() );
wp_redirect( $goback );
exit;
}

/**
* Handle the form post regarding maintenance mode (de)activation. Updates the activation state
* and/or the interval till expected end of it.
*
* Used as a hook on admin-post.
*
* @since 1.0.0
*/
public static function handle_status_change_post() {
$errors = [];
static::verify_post_request( 'maintenance_mode_status' );

// Check basic validity.
if ( ! isset( $_POST['hours'] ) || ! isset( $_POST['minutes'] ) ) {
$errors[] = esc_html__( 'Something went wrong, please try again' );
} else {
// Not putting much effort in validating the values as out of expected range
// values can not do any harm.
$hours = (int) wp_unslash( $_POST['hours'] );
$minutes = (int) wp_unslash( $_POST['minutes'] );
$end_time = time() + ( 60 * $hours + $minutes ) * 60;
static::set_projected_end_time( $end_time );

if ( isset( $_POST['enter'] ) ) {
static::activate();
}

if ( isset( $_POST['exit'] ) ) {
static::deactivate();
}
}

set_transient( 'maintenance_mode_errors', $errors, 30 );

// Redirect back to the settings page that was submitted.
$goback = add_query_arg( 'settings-updated', 'true', wp_get_referer() );
wp_redirect( $goback );
exit;
}
}

0 comments on commit e490904

Please sign in to comment.