1/**
2 * Plugin Name: Really Simple Maintenance Mode
3 * Plugin URI: https://www.christchurchwebsolutions.co.uk
4 * Description: This WordPress plugin enables a site to be switched into maintenance or coming soon mode.
5 * It allows the site administrator to display a custom page to visitors while logged-in users with
6 * sufficient permissions can still access the site for management and preview purposes.
7 * Author: Mark Harris
8 * Version: 1.0
9 *
10 * When the maintenance mode is active, the plugin sends a 503 Service Temporarily Unavailable header,
11 * which informs search engines that the site is down for a short time. This is beneficial for SEO as it
12 * indicates that the site should be re-visited by search engines after some time. The coming soon mode sends
13 * a standard 200 OK status to indicate that the site is under construction but still technically available.
14 *
15 * Administrators can customize the maintenance or coming soon page by setting an image and headline
16 * through the WordPress admin panel. This page is then displayed to all non-logged-in users or logged-in
17 * users who do not have theme editing capabilities.
18 *
19 * The plugin includes a settings page under the WordPress 'Settings' menu where the maintenance mode
20 * can be toggled, and the customization options can be set. It uses WordPress's built-in media uploader
21 * for image handling, ensuring a familiar interface for administrators as well as built-in security checks.
22 *
23 * Security features include capability checks, nonce verification for form submissions, sanitization of
24 * input data, and escaping of output data. These practices help to protect against common vulnerabilities
25 * such as cross-site scripting (XSS) and cross-site request forgery (CSRF).
26 *
27 * @version 1.0
28 * @author Mark Harris
29 * @link https://www.christchurchwebsolutions.co.uk
30 */
31
32
33if (!defined('ABSPATH')) exit; // Exit if accessed directly
34
35// Define constants for the maintenance modes.
36define('WPTURBO_MAINTENANCE_MODE', 'maintenance');
37define('WPTURBO_COMING_SOON_MODE', 'coming_soon');
38
39// Check if we're currently in maintenance or coming soon mode.
40function wpturbo_check_maintenance_mode() {
41 $options = get_option('wpturbo_maintenance_mode', []);
42 if (!current_user_can('edit_themes') || !is_user_logged_in()) {
43 $mode = $options['mode'] === WPTURBO_COMING_SOON_MODE ? WPTURBO_COMING_SOON_MODE : WPTURBO_MAINTENANCE_MODE;
44 if ($mode === WPTURBO_MAINTENANCE_MODE) {
45 header('HTTP/1.1 503 Service Temporarily Unavailable');
46 header('Status: 503 Service Temporarily Unavailable');
47 header('Retry-After: 3600'); // Tell search engines that the site will be back in an hour.
48 }
49
50 // Output custom maintenance page with centered content.
51 ?>
52 <!DOCTYPE html>
53 <html lang="en">
54 <head>
55 <meta charset="<?php echo get_bloginfo('charset'); ?>">
56 <title><?php echo esc_html(get_bloginfo('name')) . ' - Maintenance Mode'; ?></title>
57 <style>
58 body, html {
59 height: 100%;
60 margin: 0;
61 display: flex;
62 align-items: center;
63 justify-content: center;
64 text-align: center;
65 }
66 img {
67 max-width: 100%;
68 height: auto;
69 }
70 .maintenance-content {
71 padding: 20px;
72 }
73 </style>
74 </head>
75 <body>
76 <div class="maintenance-content">
77 <?php if (!empty($options['image_url'])): ?>
78 <img src="<?php echo esc_url($options['image_url']); ?>" alt="Maintenance Mode">
79 <?php endif; ?>
80 <?php if (!empty($options['headline'])): ?>
81 <div><?php echo wp_kses_post($options['headline']); ?></div>
82 <?php endif; ?>
83 </div>
84 </body>
85 </html>
86 <?php
87 exit();
88 }
89}
90
91add_action('template_redirect', 'wpturbo_check_maintenance_mode');
92
93// Enqueue the WordPress Media Uploader scripts.
94function wpturbo_enqueue_media_uploader() {
95 wp_enqueue_media();
96 wp_enqueue_script('wpturbo-media-uploader', plugin_dir_url(__FILE__) . 'media-uploader.js', array('jquery'), null, true);
97}
98add_action('admin_enqueue_scripts', 'wpturbo_enqueue_media_uploader');
99
100// Add admin menu item for settings if user is an admin.
101if (is_admin()) {
102 add_action('admin_menu', function () {
103 add_options_page(
104 'Maintenance Mode Settings',
105 'Maintenance Mode',
106 'manage_options',
107 'wpturbo-maintenance-mode',
108 'wpturbo_maintenance_mode_settings_page'
109 );
110 });
111
112 function wpturbo_maintenance_mode_settings_page() {
113 // Check user capability.
114 if (!current_user_can('manage_options')) {
115 return;
116 }
117
118 // Check if the form has been submitted.
119 if (isset($_POST['wpturbo_maintenance_mode_nonce']) && wp_verify_nonce($_POST['wpturbo_maintenance_mode_nonce'], 'wpturbo_maintenance_mode_action')) {
120 $mode = in_array($_POST['mode'], [WPTURBO_MAINTENANCE_MODE, WPTURBO_COMING_SOON_MODE], true) ? $_POST['mode'] : WPTURBO_MAINTENANCE_MODE;
121
122 // Update options with the posted values.
123 update_option('wpturbo_maintenance_mode', [
124 'mode' => sanitize_text_field($mode),
125 'image_url' => esc_url_raw($_POST['image_url']),
126 'headline' => wp_kses_post($_POST['headline']),
127 ]);
128 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('Settings updated.', 'wpturbo') . '</p></div>';
129 }
130
131 // Retrieve current options.
132 $options = get_option('wpturbo_maintenance_mode');
133 ?>
134 <div class="wrap">
135 <h1>Maintenance Mode Settings</h1>
136 <form method="post" action="">
137 <?php wp_nonce_field('wpturbo_maintenance_mode_action', 'wpturbo_maintenance_mode_nonce'); ?>
138 <table class="form-table">
139 <tr>
140 <th scope="row"><label for="mode">Mode</label></th>
141 <td>
142 <select name="mode" id="mode">
143 <option value="<?php echo WPTURBO_MAINTENANCE_MODE; ?>" <?php selected($options['mode'], WPTURBO_MAINTENANCE_MODE); ?>>Maintenance Mode (HTTP 503)</option>
144 <option value="<?php echo WPTURBO_COMING_SOON_MODE; ?>" <?php selected($options['mode'], WPTURBO_COMING_SOON_MODE); ?>>Coming Soon (HTTP 200)</option>
145 </select>
146 </td>
147 </tr>
148 <tr>
149 <th scope="row"><label for="image_url">Image</label></th>
150 <td>
151 <input type="hidden" id="image_url" name="image_url" value="<?php echo esc_url($options['image_url'] ?? ''); ?>" class="regular-text">
152 <button type="button" class="button" id="upload_image_button"><?php echo empty($options['image_url']) ? 'Upload Image' : 'Change Image'; ?></button>
153 <?php if (!empty($options['image_url'])): ?>
154 <p class="description"><img src="<?php echo esc_url($options['image_url']); ?>" style="max-width: 300px; max-height: 300px;" /></p>
155 <?php endif; ?>
156 </td>
157 </tr>
158 <tr>
159 <th scope="row"><label for="headline">Headline (HTML allowed)</label></th>
160 <td><textarea id="headline" name="headline" class="large-text" rows="3"><?php echo esc_textarea($options['headline'] ?? ''); ?></textarea></td>
161 </tr>
162 </table>
163 <?php submit_button(); ?>
164 </form>
165 </div>
166 <script>
167 jQuery(document).ready(function($){
168 $('#upload_image_button').click(function(e) {
169 e.preventDefault();
170 var image = wp.media({
171 title: 'Upload Image',
172 multiple: false
173 }).open()
174 .on('select', function(e){
175 var uploaded_image = image.state().get('selection').first();
176 var image_url = uploaded_image.toJSON().url;
177 $('#image_url').val(image_url);
178 if(uploaded_image.toJSON().sizes.thumbnail){
179 image_url = uploaded_image.toJSON().sizes.thumbnail.url;
180 }
181 $(this).html('Change Image');
182 $('.description').html('<img src="'+ image_url +'" style="max-width: 300px; max-height: 300px;" />');
183 });
184 });
185 });
186 </script>
187 <?php
188 }
189}