This tutorial explains the process behind writing a Maintenance Mode plugin for WordPress such as the one I released a few days ago: CGC Maintenance Mode. The final result of this tutorial will give you an advanced plugin capable of putting your site into maintenance mode, while allowing authorized users to view the site normally, so that you can perform updates on your theme, plugins, content, or whatever you need to do.

As the video describes everything in detail, I will be a bit more spare on the details in the written portion, so I advise you to watch the video, then proceed to copying/writing all of the code below.

The Main Plugin File

The main file of our plugin, which I have called “maintenance-mode.php”, is placed inside of the “maintenance-mode” folder and has the following contents:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
/*
Plugin Name: CGC Maintenance Mode
Plugin URI: https://pippinsplugins.com/cgc-maintenance-mode-plugin-for-wordpress/
Description: Allows you to put your site in maintenance mode and restrict address by IP addresses
Version: 1.0
Author: Pippin Williamson
Author URI: https://pippinsplugins.com
*/
 
/******************************
* global vars
******************************/
 
// load plugin settings
$cmm_options = get_option('cmm_settings');
 
/******************************
* load the include files
******************************/
include('includes/admin-page.php');
include('includes/restrict-access.php');

First is just the normal WordPress plugin header comments. These define the file as a plugin and sets up the author and plugin information. Next, we setup our global variable “$cmm_options” and load the plugin settings into it.

After we have defined the global settings variable, we include our additional files into the main plugin file. This is done in order to keep our plugin code cleaner and more organized.

That’s it for this file.

The Plugin Admin Page File

Now, inside of the “admin-page.php” file, which we have placed inside of the “includes” folder, we have three functions. The first is the function that outputs all of the HTML for our admin page, including the settings forms.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
function cmm_settings_page()
{
	global $cmm_options;
 
	?>
	<div class="wrap">
		<div id="upb-wrap" class="upb-help">
			<h2>Maintenance Mode</h2>
			<?php
			if ( ! isset( $_REQUEST['updated'] ) )
				$_REQUEST['updated'] = false;
			?>
			<?php if ( false !== $_REQUEST['updated'] ) : ?>
			<div class="updated fade"><p><strong><?php _e( 'Options saved' ); ?></strong></p></div>
			<?php endif; ?>
			<form method="post" action="options.php">
 
				<?php settings_fields( 'cmm_settings_group' ); ?>
 
				<h4>Enable</h4>
				<p>
					<input id="cmm_settings[enable]" name="cmm_settings[enable]" type="checkbox" value="1" <?php checked( '1', $cmm_options['enable'] ); ?>/>
					<label class="description" for="cmm_settings[enable]"><?php _e( 'Enable Maintenance Mode?' ); ?></label>
				</p>
 
				<h4>IP Addresses</h4>
				<p>
					<label class="description" for="cmm_settings[ips]"><?php _e( 'Enter the IP addresses that should have access to the site, one per line' ); ?></label><br/>					
					<textarea id="cmm_settings[ips]" style="width: 400px; height: 150px;" name="cmm_settings[ips]" type="text"><?php echo $cmm_options['ips'];?></textarea>
				</p>
 
				<h4>Page or URL</h4>
				<p>
					<input type="radio" name="cmm_settings[page_or_url]" value="page" <?php checked( 'page', $cmm_options['page_or_url'] ); ?>/> Page
					<input type="radio" name="cmm_settings[page_or_url]" value="url"<?php checked( 'url', $cmm_options['page_or_url'] ); ?>/> URL 
					<label class="description" for="cmm_settings[page_or_url]"><?php _e( 'Send unauthorized visitors to a WordPress Page or external URL?' ); ?></label><br/>
				</p>
 
				<h4>Redirect Page</h4>
				<p>
					<?php $pages = get_pages(); ?>
					<select id="cmm_settings[page]" name="cmm_settings[page]">
						<?php foreach($pages as $page) { ?>
							<option value="<?php echo $page->ID; ?>" <?php if($cmm_options['page'] == $page->ID) { echo 'selected="selected"'; } ?>><?php echo $page->post_title; ?></option>
						<?php } ?>
					</select>
					<label class="description" for="cmm_settings[page]"><?php _e( 'Page to send users without IP access' ); ?></label><br/>
				</p>
 
				<h4>Redirect URL</h4>
				<p>
					<input id="cmm_settings[redirect_url]" name="cmm_settings[redirect_url]" type="text" value="<?php echo $cmm_options['redirect_url'];?>" />
					<label class="description" for="cmm_settings[redirect_url]"><?php _e( 'URL to send users without IP access' ); ?></label><br/>
				</p>
 
				<!-- save the options -->
				<p class="submit">
					<input type="submit" class="button-primary" value="<?php _e( 'Save Options' ); ?>" />
				</p>
 
 
			</form>
		</div><!--end sf-wrap-->
	</div><!--end wrap-->
 
	<?php
}

This function is using the WordPress Settings API. I’m not going to explain how the settings API works (it’s rather involved), so I suggest you read about it at the link I provided if you are not familiar with it.

The second function in the “admin-page.php” file is the one that will register our settings with WordPress so that they can be used (and modified) in the function above.

1
2
3
4
5
6
// register the plugin settings
function cmm_register_settings() {
	register_setting('cmm_settings_group', 'cmm_settings');
}
// call register settings function on load
add_action('admin_init', 'cmm_register_settings');

Now we have just one more function for our admin page. This next function is the one that will add a menu link to the Settings Menu and link it to our first function (that outputs the HTML).

1
2
3
4
5
function cmm_settings_menu() {
	// add settings page
	add_submenu_function('options-general.php', 'Maintenance Mode', 'Maintenance Mode', 'manage_options', 'maintenance-mode', 'cmm_settings_page');
}
add_action('admin_menu', 'cmm_settings_menu');

That’s it for the “admin-page.php” file.

The Restrict Access File

This is our final file in our plugin and it’s called “restrict-access.php”, and it’s inside the “includes” folder.

There are two functions in this file, one that takes care of redirecting users who are not authorized (based on their IP address), and one that checks the current visitor’s IP address against and array of authorized IPs.

First is our function that takes care of the redirects:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Performs an IP check on the current user when the page is loaded and redirect user if not in the array
function cmm_restrict_access() {
	global $cmm_options;
 
	if($cmm_options['enable']) {
 
		$ips = array_map('trim', explode("\n", $cmm_options['ips']));
 
		if($cmm_options['page_or_url'] == 'page') {
			if(cmm_ip_test($ips) == false && !is_page(intval($cmm_options['page']))) {
				wp_redirect( get_permalink($cmm_options['page'])); exit;
			}
		} elseif($cmm_options['page_or_url'] == 'url') {
			if(cmm_ip_test($ips) == false && $cmm_options['redirect_url'] != '') {
				wp_redirect( $cmm_options['redirect_url']); exit; 
			}
		}
	}
}
add_action('template_redirect', 'cmm_restrict_access', 0);

This function does a few of things. First, it checks to see if maintenance mode is enabled and only proceeds if it is. Second, it grabs all of the IP addresses entered on the settings page and puts them into an array. Third, it checks to see whether unauthorized users should be redirected to a WordPress page or an external URL. Next, it checks to see whether the current visitor’s IP address is allowed, and then, lastly, redirects non-authorized users to either the specified page or external URL.

I’ve connected this function to the “template_redirect” hook because it makes it so the function runs every time any post or page is loaded in WordPress. This ensures that users are never able to get past it if they’re not supposed to be allowed in.

The second function is the one that does the actual IP comparison test. It’s very simple and merely checks whether the current visitor’s IP matches any of those in the provided array.

1
2
3
4
5
6
7
8
9
// checks the current user's IP address
// @return - true if the user's IP is in the array, false otherwise
function cmm_ip_test($ips){
 
	if(in_array($_SERVER['REMOTE_ADDR'], $ips)) {
		return true;
	}
	return false;
}

Wrap Up

That’s it, your plugin is now complete. While simple, it’s very powerful and can be extremely useful for times you need to perform updates on your website without risking your users seeing things they’re not supposed to. It’s also a great tool to use if you ever update a plugin or theme and have something suddenly break. You can throw this plugin up there really quick to let your users know you are undergoing maintenance and that you’ll be right back; a much better solution than those nasty internal server errors.

I hope you take this, expand on it, and make something even cooler. Please let me know what you think in the comments, and feel free to ask if you have any questions.

You can view the plugin this tutorial is based on here, and even download it for free!

    • Pippin

      Thanks for the feedback, and you are absolutely right.

  1. Zach Grimm

    add_submenu_function should be add_submenu_page, yes?
    Another great Tutorial!

    • Pippin

      Correct, thanks for catching that.

Leave a Reply