Plugin Directory

Changeset 3454890


Ignore:
Timestamp:
02/05/2026 07:07:10 PM (7 weeks ago)
Author:
areziaal
Message:

Version 1.1.0

Location:
planned-outage
Files:
3 added
2 edited

Legend:

Unmodified
Added
Removed
  • planned-outage/trunk/planned-outage.php

    r3449795 r3454890  
    55 * Requires at least: 6.3
    66 * Requires PHP:      7.0
    7  * Version:           1.0.0
     7 * Version:           1.1.0
    88 * Author:            Troy Chaplin
    99 * License:           GPL-2.0-or-later
     
    7777            add_settings_error( 'pobt_settings', 'tracking_reset', 'Duration tracking has been reset.', 'success' );
    7878        }
     79
     80        // Handle regenerate bypass link action.
     81        if ( isset( $_POST['pobt_regenerate_bypass'] ) && check_admin_referer( 'pobt_regenerate_bypass_action' ) ) {
     82            update_option( 'pobt_bypass_key', wp_generate_password( 32, false ) );
     83            add_settings_error( 'pobt_settings', 'bypass_regenerated', 'Bypass link has been regenerated. The previous link will no longer work.', 'success' );
     84        }
    7985        register_setting(
    8086            'pobt_settings',
     
    112118            )
    113119        );
     120        register_setting(
     121            'pobt_settings',
     122            'pobt_bypass_enabled',
     123            array(
     124                'type'              => 'boolean',
     125                'default'           => false,
     126                'sanitize_callback' => 'rest_sanitize_boolean',
     127            )
     128        );
    114129    }
    115130
     
    118133     */
    119134    public function settings_page() {
    120         $enabled     = get_option( 'pobt_enabled', false );
    121         $retry_after = get_option( 'pobt_retry_after', 3600 );
    122         $allow_bots  = get_option( 'pobt_allow_bots', false );
    123         $template    = $this->get_maintenance_template();
    124 
    125         // Track when maintenance was enabled.
     135        $enabled        = get_option( 'pobt_enabled', false );
     136        $retry_after    = absint( get_option( 'pobt_retry_after', 3600 ) );
     137        $allow_bots     = get_option( 'pobt_allow_bots', false );
     138        $bypass_enabled = get_option( 'pobt_bypass_enabled', false );
     139        $template       = $this->get_maintenance_template();
     140
     141        // Track when maintenance was enabled (skip for pre-launch mode).
    126142        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only checking if settings were updated, not processing form data.
    127         if ( isset( $_GET['settings-updated'] ) && $enabled && ! get_option( 'pobt_enabled_at' ) ) {
     143        if ( isset( $_GET['settings-updated'] ) && $enabled && 0 !== $retry_after && ! get_option( 'pobt_enabled_at' ) ) {
    128144            update_option( 'pobt_enabled_at', time() );
    129145            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only checking if settings were updated, not processing form data.
    130         } elseif ( isset( $_GET['settings-updated'] ) && ! $enabled ) {
     146        } elseif ( isset( $_GET['settings-updated'] ) && ( ! $enabled || 0 === $retry_after ) ) {
    131147            delete_option( 'pobt_enabled_at' );
     148        }
     149
     150        // Generate or remove bypass key based on setting.
     151        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only checking if settings were updated, not processing form data.
     152        if ( isset( $_GET['settings-updated'] ) && $bypass_enabled && ! get_option( 'pobt_bypass_key' ) ) {
     153            update_option( 'pobt_bypass_key', wp_generate_password( 32, false ) );
     154            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Only checking if settings were updated, not processing form data.
     155        } elseif ( isset( $_GET['settings-updated'] ) && ! $bypass_enabled ) {
     156            delete_option( 'pobt_bypass_key' );
    132157        }
    133158        ?>
     
    165190                        <td>
    166191                            <select name="pobt_retry_after">
     192                                <option value="0" <?php selected( $retry_after, 0 ); ?>>Pre-Launch (indefinite)</option>
    167193                                <option value="1800" <?php selected( $retry_after, 1800 ); ?>>30 minutes</option>
    168194                                <option value="3600" <?php selected( $retry_after, 3600 ); ?>>1 hour</option>
     
    173199                                <option value="86400" <?php selected( $retry_after, 86400 ); ?>>1 day (maximum recommended)</option>
    174200                            </select>
    175                             <p class="description">Tells search engines when to check back. For maintenance longer than 1 day, enable search engine access below.</p>
     201                            <p class="description">Tells search engines when to check back. Select Pre-Launch for sites that aren't live yet. For maintenance longer than 1 day, enable search engine access below.</p>
    176202                        </td>
    177203                    </tr>
     
    186212                        </td>
    187213                    </tr>
     214                    <tr>
     215                        <th scope="row">Bypass Link</th>
     216                        <td>
     217                            <label>
     218                                <input type="checkbox" name="pobt_bypass_enabled" value="1" <?php checked( $bypass_enabled, 1 ); ?>>
     219                                Allow non-logged-in users to bypass maintenance mode via a secret link
     220                            </label>
     221                            <p class="description">When enabled, a unique URL is generated that lets anyone with the link browse the site normally during maintenance.</p>
     222                            <?php
     223                            $bypass_key = get_option( 'pobt_bypass_key' );
     224                            if ( $bypass_enabled && $bypass_key ) :
     225                                $bypass_url = add_query_arg( 'pobt_bypass', $bypass_key, home_url( '/' ) );
     226                                ?>
     227                                <div style="margin-top: 10px; padding: 10px; background: #f0f0f1; border-left: 4px solid #2271b1;">
     228                                    <p style="margin: 0 0 6px 0;"><strong>Bypass URL:</strong></p>
     229                                    <code style="display: block; padding: 6px 8px; background: #fff; word-break: break-all;"><?php echo esc_url( $bypass_url ); ?></code>
     230                                    <p class="description" style="margin-top: 6px;">Share this link with anyone who needs to view the site during maintenance. The link sets a cookie so they can navigate freely for 12 hours.</p>
     231                                </div>
     232                            <?php endif; ?>
     233                        </td>
     234                    </tr>
    188235                </table>
    189236
     
    191238            </form>
    192239
    193             <?php $enabled_at = get_option( 'pobt_enabled_at', 0 ); ?>
    194             <?php if ( $enabled_at ) : ?>
    195                 <hr style="margin: 30px 0;">
    196                 <h2>Duration Tracking</h2>
    197                 <p>Maintenance mode was enabled on: <strong><?php echo esc_html( wp_date( 'F j, Y \a\t g:i a', $enabled_at ) ); ?></strong></p>
    198                 <p class="description">If this date is incorrect (e.g., from a previous maintenance period), you can reset it.</p>
     240            <?php if ( $bypass_enabled && get_option( 'pobt_bypass_key' ) ) : ?>
    199241                <form method="post" style="margin-top: 10px;">
    200                     <?php wp_nonce_field( 'pobt_reset_tracking_action' ); ?>
    201                     <input type="hidden" name="pobt_reset_tracking" value="1">
    202                     <?php submit_button( 'Reset Duration Tracking', 'secondary', 'submit', false ); ?>
     242                    <?php wp_nonce_field( 'pobt_regenerate_bypass_action' ); ?>
     243                    <input type="hidden" name="pobt_regenerate_bypass" value="1">
     244                    <?php submit_button( 'Regenerate Bypass Link', 'secondary', 'submit', false ); ?>
     245                    <p class="description" style="margin-top: 6px;">Generate a new bypass link. The previous link will stop working immediately.</p>
    203246                </form>
    204247            <?php endif; ?>
    205248
    206             <div class="card" style="max-width: 600px; margin-top: 20px; padding: 16px 20px;">
    207                 <h3 style="margin: 0 0 12px 0; font-size: 14px; font-weight: 600;">SEO Recommendations</h3>
    208                 <ul style="list-style: disc; margin: 0 0 0 20px; padding: 0; line-height: 1.8;">
    209                     <li><strong>Under 2 hours:</strong> Default settings are fine.</li>
    210                     <li><strong>2-24 hours:</strong> Consider enabling search engine access.</li>
    211                     <li><strong>Over 1 day:</strong> Always enable search engine access. Extended 503 responses can cause pages to be removed from search indexes.</li>
    212                 </ul>
    213             </div>
     249            <?php if ( 0 !== $retry_after ) : ?>
     250                <?php $enabled_at = get_option( 'pobt_enabled_at', 0 ); ?>
     251                <?php if ( $enabled_at ) : ?>
     252                    <hr style="margin: 30px 0;">
     253                    <h2>Duration Tracking</h2>
     254                    <p>Maintenance mode was enabled on: <strong><?php echo esc_html( wp_date( 'F j, Y \a\t g:i a', $enabled_at ) ); ?></strong></p>
     255                    <p class="description">If this date is incorrect (e.g., from a previous maintenance period), you can reset it.</p>
     256                    <form method="post" style="margin-top: 10px;">
     257                        <?php wp_nonce_field( 'pobt_reset_tracking_action' ); ?>
     258                        <input type="hidden" name="pobt_reset_tracking" value="1">
     259                        <?php submit_button( 'Reset Duration Tracking', 'secondary', 'submit', false ); ?>
     260                    </form>
     261                <?php endif; ?>
     262
     263                <div class="card" style="max-width: 600px; margin-top: 20px; padding: 16px 20px;">
     264                    <h3 style="margin: 0 0 12px 0; font-size: 14px; font-weight: 600;">SEO Recommendations</h3>
     265                    <ul style="list-style: disc; margin: 0 0 0 20px; padding: 0; line-height: 1.8;">
     266                        <li><strong>Under 2 hours:</strong> Default settings are fine.</li>
     267                        <li><strong>2-24 hours:</strong> Consider enabling search engine access.</li>
     268                        <li><strong>Over 1 day:</strong> Always enable search engine access. Extended 503 responses can cause pages to be removed from search indexes.</li>
     269                    </ul>
     270                </div>
     271            <?php endif; ?>
    214272        </div>
    215273        <?php
     
    249307
    250308    /**
     309     * Checks if the current visitor has bypass access via query param or cookie.
     310     *
     311     * Sets a cookie on first valid access so subsequent page loads don't need the query param.
     312     *
     313     * @return bool True if the visitor has a valid bypass token.
     314     */
     315    private function has_bypass_access() {
     316        $bypass_key = get_option( 'pobt_bypass_key' );
     317
     318        if ( ! $bypass_key ) {
     319            return false;
     320        }
     321
     322        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a public-facing bypass token, not a form submission.
     323        $query_token  = isset( $_GET['pobt_bypass'] ) ? sanitize_text_field( wp_unslash( $_GET['pobt_bypass'] ) ) : '';
     324        $cookie_token = isset( $_COOKIE['pobt_bypass'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['pobt_bypass'] ) ) : '';
     325
     326        if ( hash_equals( $bypass_key, $query_token ) || hash_equals( $bypass_key, $cookie_token ) ) {
     327            // Set cookie if not already present or if arriving via query param.
     328            if ( $query_token && ( ! $cookie_token || ! hash_equals( $bypass_key, $cookie_token ) ) ) {
     329                setcookie(
     330                    'pobt_bypass',
     331                    $bypass_key,
     332                    array(
     333                        'expires'  => time() + ( 12 * HOUR_IN_SECONDS ),
     334                        'path'     => '/',
     335                        'secure'   => is_ssl(),
     336                        'httponly' => true,
     337                        'samesite' => 'Lax',
     338                    )
     339                );
     340            }
     341
     342            return true;
     343        }
     344
     345        return false;
     346    }
     347
     348    /**
    251349     * Checks if the current request is for the homepage.
    252350     *
     
    282380        }
    283381
     382        // Allow bypass via secret link if enabled.
     383        if ( get_option( 'pobt_bypass_enabled', false ) && $this->has_bypass_access() ) {
     384            return $template;
     385        }
     386
    284387        // Allow search engine bots through if enabled.
    285388        if ( get_option( 'pobt_allow_bots', false ) && $this->is_search_engine_bot() ) {
     
    293396        }
    294397
     398        $retry_after = absint( get_option( 'pobt_retry_after', 3600 ) );
     399
    295400        nocache_headers();
    296401        status_header( 503 );
    297         header( 'Retry-After: ' . absint( get_option( 'pobt_retry_after', 3600 ) ) );
     402
     403        if ( $retry_after > 0 ) {
     404            header( 'Retry-After: ' . $retry_after );
     405        }
    298406
    299407        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- WordPress core global.
     
    329437    public function duration_warning() {
    330438        if ( ! get_option( 'pobt_enabled', false ) ) {
     439            return;
     440        }
     441
     442        // Skip duration warnings in pre-launch mode.
     443        if ( 0 === absint( get_option( 'pobt_retry_after', 3600 ) ) ) {
    331444            return;
    332445        }
     
    364477        delete_option( 'pobt_enabled' );
    365478        delete_option( 'pobt_enabled_at' );
     479        delete_option( 'pobt_bypass_key' );
    366480    }
    367481}
  • planned-outage/trunk/readme.txt

    r3449795 r3454890  
    55Requires at least: 6.3
    66Tested up to: 6.9
    7 Stable tag: 1.0.0
     7Stable tag: 1.1.0
    88Requires PHP: 7.0
    99License: GPLv2 or later
     
    2222* Logged-in users bypass maintenance mode
    2323* Configurable expected duration (Retry-After header)
     24* Pre-launch mode for sites that aren't live yet
    2425* Optional search engine bot access during maintenance
     26* Bypass link to let non-logged-in users preview the site during maintenance
    2527* Admin bar indicator when maintenance mode is active
    26 * Duration warning after 3 days of maintenance
     28* Duration warning after 3 days of maintenance (except in pre-launch mode)
    2729* Returns proper 503 status code for SEO
    2830
     
    5052= Who can see the site when maintenance mode is enabled? =
    5153
    52 All logged-in users can browse the site normally. Only logged-out visitors see the maintenance template. You can also enable search engine bots to bypass maintenance mode.
     54All logged-in users can browse the site normally. Only logged-out visitors see the maintenance template. You can also enable search engine bots to bypass maintenance mode, or generate a bypass link for non-logged-in users.
    5355
    5456= What is the Expected Duration setting? =
    5557
    56 This sets the Retry-After HTTP header, which tells search engines how long to wait before checking your site again. Options range from 30 minutes to 1 day.
     58This sets the Retry-After HTTP header, which tells search engines how long to wait before checking your site again. Options range from 30 minutes to 1 day. You can also select "Pre-Launch (indefinite)" for sites that aren't live yet, which disables duration tracking and admin warnings.
     59
     60= What is the Bypass Link? =
     61
     62When enabled, the plugin generates a secret URL you can share with anyone who needs to view the site during maintenance without logging in. A 12-hour cookie is set on first visit so they can navigate freely. You can regenerate the link at any time to invalidate the previous one.
    5763
    5864= Should I enable Search Engine Access? =
     
    7076== Changelog ==
    7177
     78= 1.1.0 =
     79* Added bypass link feature for sharing preview access with non-logged-in users
     80* Added pre-launch mode (indefinite duration) that disables time tracking and admin warnings
     81* Bypass link sets a 12-hour cookie for seamless navigation
     82* Regenerate bypass link to invalidate previous links
     83
    7284= 1.0.0 =
    7385* Initial release
Note: See TracChangeset for help on using the changeset viewer.