Blog / WordPress / How to Build a WordPress Client Portal Plugin

How to Build a WordPress Client Portal Plugin

Cristian Antohe
Last Updated: 08/03/23

When building a company website, chances are you’ll be looking for a WordPress client portal plugin where an administrator can upload files and information related to a particular client. And most of the time it doesn’t have to be something complicated.

There are several solutions like simply creating a password protected page or using an existing WordPress plugin. But in some cases, it’s just a bit too much.

In this tutorial, we’ll learn how to build a simple WordPress Client Portal plugin step by step. And if you aren’t a developer, don’t worry! We’ll also be showing you how to build a secure client portal on your WordPress website without using code! Either way, you’ll have the best client portal WordPress plugin for your needs.

There are two main things we’ll cover: creating the WordPress customer portal plugin and exploring what plugins we can integrate it with so we have a complete solution.

Building a Customer Portal in 3 Easy Steps

Step 1: Download the Client Portal WordPress Plugin

On your WordPress site go to your admin dashboard → Plugins Add new, search for “client portal” and then click on Install Now and then Activate.

Installing the Client Portal plugin

Step 2: Setting Up Your Client Portal

Once activated in your WordPress admin go to Users → Client Portal Settings. Here you’ll see all settings available for the plugin.

Client Portal plugin settings

The settings available are:

  • Page Slug — this is the slug of the private user pages such as example.com/private-page/username.
  • Support Comments — allows you to have communication on the front-end of your site with your clients.
  • Generate Pages — this setting allows you to automatically generate client areas for your existing users with one-click.
  • View all pages — shows the current private pages which can be useful for client management.
  • Restricted Message — The Client Portal plugin is fully white-label allowing you to set custom messages on the front-end of your site for better user experience.
  • Portal Log In Message — Same as the restricted message, with the difference being this is the message that a client sees when they try and log in.
  • Default Page Content — This is the content that is automatically displayed on the private page. You could include things such as invoices, an area to upload files, links to project management tools, and literally anything you want.

Once you’re happy with all your settings scroll down to the bottom of the page and click Save Settings.

Step 3: Onboarding Your Clients

Now you have your WordPress client portal configured you need to let your clients know about it! There are two ways you can do this:

  1. Use your favorite email marketing tool such as Mailchimp to send out a newsletter to your clients letting them know about the new client area.
  2. Personally email your clients with a unique email to introduce them to the new client portal, explain how it works, and assist them in getting to grips with it. This method helps cultivate your customer relationships, manage client projects,  and makes them feel more valued.

Congratulations! You’ve now successfully set up a brand-new client portal in just 3 steps.

What to Use Your Client Portal For?

There are loads of things you can use your brand new client portal for such as:

  • File uploads;
  • Sharing deliverables — such as completed content work, graphics, zip files, etc;
  • Invoicing — upload client invoices to make it easier for both of you to find;
  • Add a contact form — a quick and easy way for your client to reach out if for any reason they lose your contact details.

Building a WordPress Client Portal Plugin

While there seems to be a lot of functionality in this small plugin, most functions are quite small and easy to understand. We’ll go through:

  • creating a plugin;
  • building a PHP class that will hold our plugin;
  • registering a custom post type so private pages are not mixed with other content;
  • automatically create a new private page on new user creation;
  • automatically delete a private page on user deletion;
  • restrict the content if the user doesn’t have permissions to view it;
  • adding links to the user private pages in WordPress Dashboard Users;
  • adding a log out button on the private page for users to use;
  • creating the [client-portal] shortcode that redirects users to their private page;
  • create a settings page for our plugin where we’re editing the default messages;
  • add a couple of tweaks like admin notices, flush permalinks and exclude next and pre navigation from our custom post.

If you want to skip this, you can download it from WordPress.org.

Get Client Portal Plugin

Create a New Plugin

In WordPress creating a plugin is as simple as creating a .php file with a special header. Here’s how our plugin file will look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
/**
* Plugin Name: Client Portal
* Plugin URI: http://www.cozmoslabs.com/
* Description: Build a company site with a client portal where clients login and see a restricted-access, personalized page of content with links and downloads.
* Version: 1.0.0
* Author: Cozmoslabs, Madalin Ungureanu, Antohe Cristian
* Author URI: http://www.cozmoslabs.com
* License: GPL2
*/
/* Copyright 2019 Cozmoslabs (www.cozmoslabs.com)
 
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2, as
published by the Free Software Foundation.
 
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation

Build a PHP Class That will Contain Our Plugin Functions

The main reason for using a PHP class instead of functional programming is to keep everything nice and tidy and limit the risk of naming conflicts.

1
2
3
4
5
6
7
8
9
10
11
12
class CL_Client_Portal
{
private $slug;
private $defaults;
public $options;
 
 
function __construct()
 {
 }
 
}

The __construct() function will contain all our hooks and filters as well as initiate the $slug, $defaults, and $options variables that we’ll use throughout our client portal plugin.

Initialize the Default Variables Parameters

Any plugin that has options also needs some carefully chosen defaults that will work for the majority of users. We’re doing this inside the __construct() function.

  • $slug – the admin page slug. Also used for the plugin option name in the database
  • $options – if we have user defined options, we’re loading them from the database
  • $defaults – the default settings for the private page slug, restricted message and portal login message
1
2
3
4
5
6
7
$this->slug = 'cp-options';
$this->options = get_option( $this->slug );
$this->defaults = array(
'page-slug' => 'private-page',
'restricted-message' => __( 'You do not have permission to view this page.', 'client-portal' ),
'portal-log-in-message' => __( 'Please log in in order to access the client portal.', 'client-portal' )
);

Register the Private Page Custom Post Type

Using a custom post type instead of normal pages keeps everything clear and organized. The Custom Post Type gets added on the init hook.

The private page slug is dynamic and can be changed by the administrator if needed. We’re also setting it up so it doesn’t show in the WordPress menu since we’ll be adding it under each user in the WordPress → Users interface.

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
 /* register the post type */
add_action( 'init', array( $this, 'cp_create_post_type' ) );
/**
* Function that registers the post type
*/
function cp_create_post_type() {
 
$labels = array(
'name' => _x( 'Private Pages', 'post type general name', 'client-portal' ),
'singular_name' => _x( 'Private Page', 'post type singular name', 'client-portal' ),
'menu_name' => _x( 'Private Page', 'admin menu', 'client-portal' ),
'name_admin_bar' => _x( 'Private Page', 'add new on admin bar', 'client-portal' ),
'add_new' => _x( 'Add New', 'private Page', 'client-portal' ),
'add_new_item' => __( 'Add New Private Page', 'client-portal' ),
'new_item' => __( 'New Private Page', 'client-portal' ),
'edit_item' => __( 'Edit Private Page', 'client-portal' ),
'view_item' => __( 'View Private Page', 'client-portal' ),
'all_items' => __( 'All Private Pages', 'client-portal' ),
'search_items' => __( 'Search Private Pages', 'client-portal' ),
'parent_item_colon' => __( 'Parent Private Page:', 'client-portal' ),
'not_found' => __( 'No Private Pages found.', 'client-portal' ),
'not_found_in_trash' => __( 'No Private Pages found in Trash.', 'client-portal' )
);
 
$args = array(
'labels' => $labels,
'description' => __( 'Description.', 'client-portal' ),
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => false,
'query_var' => true,
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => true,
'supports' => array( 'title', 'editor', 'thumbnail' )
);
 
if( !empty( $this->options['page-slug'] ) ){
$args['rewrite'] = array( 'slug' => $this->options['page-slug'] );
}
else{
$args['rewrite'] = array( 'slug' => $this->defaults['page-slug'] );
}
 
register_post_type( 'private-page', $args );
}

Creating and Deleting the Private Pages on User Creation and Deletion

Every time a new user registers or is created by an administrator, we also need to create its private page. The same thing goes on post deletion.

The correlation between a user and a private page is the CPT’s author.

We’re also making use of cp_get_private_page_for_user() function that returns the ID for the private page for a particular user.

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
    /* action to create a private page when a user registers */
    add_action( 'user_register', <a href="http://www.php.net/array">array</a>( $this, 'cp_create_private_page' ) );
    /* remove the page when a user is deleted */
    add_action( 'deleted_user', <a href="http://www.php.net/array">array</a>( $this, 'cp_delete_private_page' ), 10, 2 );
 
    /**
     * Function that creates the private page for a user
     * @param $user_id the id of the user for which to create the page
     */
    function cp_create_private_page( $user_id ){
        /* make sure get_userdata() is available at this point */
        if(is_admin()) require_once( ABSPATH . 'wp-includes/pluggable.php' );
 
        $user = get_userdata( $user_id );
        $display_name = '';
        if( $user ){
            $display_name = ($user-&gt;display_name) ? ($user-&gt;display_name) : ($user-&gt;user_login);
        }
 
        $private_page = <a href="http://www.php.net/array">array</a>(
            'post_title'    =&gt; $display_name,
            'post_status'   =&gt; 'publish',
            'post_type'     =&gt; 'private-page',
            'post_author'   =&gt; $user_id
        );
 
        // Insert the post into the database
        wp_insert_post( $private_page );
    }
 
    /**
     * Function that deletes the private page when the user is deleted
     * @param $id the id of the user which page we are deleting
     * @param $reassign
     */
    function cp_delete_private_page( $id, $reassign ){
        $private_page_id = $this-&gt;cp_get_private_page_for_user( $id );
        if( !<a href="http://www.php.net/empty">empty</a>( $private_page_id ) ){
            wp_delete_post( $private_page_id, true );
        }
    }
 
    /**
     * Function that returns the id for the private page for the provided user
     * @param $user_id the user id for which we want to get teh private page for
     * @return mixed
     */
    function cp_get_private_page_for_user( $user_id ){
        $args = <a href="http://www.php.net/array">array</a>(
            'author'            =&gt;  $user_id,
            'posts_per_page'    =&gt;  1,
            'post_type'         =&gt; 'private-page',
        );
        $users_private_pages = get_posts( $args );
 
        if( !<a href="http://www.php.net/empty">empty</a>( $users_private_pages ) ){
            foreach( $users_private_pages as $users_private_page ){
                return $users_private_page-&gt;ID;
                break;
            }
        }
    }

Restrict the Content of the Private Page if the User Doesn’t Have Access To It

We need to make sure that logged-in users can only access their private page. The only exception is the administrator or users with the capability to delete_users.

We’re blocking access by simply filtering the_content and returning something else instead if the user doesn’t have access or is not logged in.

The error message displayed is taken from the plugin options that we’ll go through a bit down the line.

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
   /* restrict the content of the page only to the user */
    add_filter( 'the_content', <a href="http://www.php.net/array">array</a>( $this, 'cp_restrict_content' ) );
    /**
     * Function that restricts the content only to the author of the page
     * @param $content the content of the page
     * @return mixed
     */
    function cp_restrict_content( $content ){
        global $post;
        if( $post-&gt;post_type == 'private-page' ){
 
            if( !<a href="http://www.php.net/empty">empty</a>( $this-&gt;options['restricted-message'] ) )
                $message = $this-&gt;options['restricted-message'];
            else
                $message = $this-&gt;defaults['restricted-message'];
 
            if( is_user_logged_in() ){
                if( ( get_current_user_id() == $post-&gt;post_author ) || current_user_can('delete_user') ){
                    return $content;
                }
                else return $message;
            }
            else return $message;
 
        }
        return $content;
    }

Adding Links to the User Private Pages in WordPress Dashboard

Now that we’ve created the private pages for the client portal, we need to allow the administrator to access and edit them.

Since the private pages are user-related, we’ll simply list them in the User listing dashboard.

The filter we’re using is user_row_actions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    /* add a link in the Users List Table in admin area to access the page */
    add_filter( 'user_row_actions', <a href="http://www.php.net/array">array</a>( $this, 'cp_add_link_to_private_page' ), 10, 2);
    /**
     * Function that adds a link in the user listing in admin area to access the private page
     * @param $actions The actions available on the user listing in admin area
     * @param $user_object The user object
     * @return mixed
     */
    function cp_add_link_to_private_page( $actions, $user_object ){
        $private_page_id = $this-&gt;cp_get_private_page_for_user( $user_object-&gt;ID );
        if( !<a href="http://www.php.net/empty">empty</a>( $private_page_id ) ){
            $actions['private_page_link'] = "&lt;a class='cp_private_page' href='" . admin_url( "post.php?post=$private_page_id&amp;action=edit") . "'&gt;" . __( 'Private Page', 'client-portal' ) . "&lt;/a&gt;";
        }
 
        return $actions;
    }

Adding a Logout Button on the Private Page for Users to Use

Once a client accesses his private page, it’s important to allow him to logout.

So we’re adding a small logout link as well as his name in case the admin visits that page, so he know what private page he’s looking at.

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
    /* create client portal extra information */
    add_filter('the_content', <a href="http://www.php.net/array">array</a>( $this, 'cp_add_private_page_info'));
 
    /**
     * Function that creates a private page extra information div
     * @param $content the content of the private page
     * @return mixed
     */
    function cp_add_private_page_info( $content ){
        global $post;
        if ( is_singular('private-page') &amp;&amp; is_user_logged_in() ){
            // logout link
            $logout_link = wp_loginout( home_url(), false);
 
            // author display name. Fallback to username if no display name is set.
            $author_id=$post-&gt;post_author;
            $user = get_user_by('id', $author_id);
            $display_name = '';
            if( $user ){
                $display_name = ($user-&gt;display_name) ? ($user-&gt;display_name) : ($user-&gt;user_login);
            }
 
            $extra_info = "&lt;p class='cp-logout' style='border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; padding: 0.5rem 0; text-align: right'&gt; $logout_link - $display_name &lt;/p&gt;";
 
            return  $extra_info . $content;
        }
 
        return $content;
    }

Creating the [client-portal] Shortcode

This is probably the most important function of the entire plugin. Once the user is logged in he needs to be able to access his client portal. By default, there’s no way for the user or the administrator to create a menu for each individual user.

So instead we’re simply redirecting the user to his individual private page with the use of the [client-portal] shortcode.

Since the shortcode executes in the middle of the page, we can’t really do a PHP redirect without some sort of workaround. So instead we’ve opted for a simple Javascript redirect that should work just fine for the majority of use cases.

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
    /* create the shortcode for the main page */
    add_shortcode( 'client-portal', <a href="http://www.php.net/array">array</a>( $this, 'cp_shortcode' ) );
 
    /**
     * Function that creates a shortcode which redirects the user to its private page
     * @param $atts the shortcode attributes
     */
    function cp_shortcode( $atts ){
        if( !is_user_logged_in() ){
            if( !<a href="http://www.php.net/empty">empty</a>( $this-&gt;options['portal-log-in-message'] ) )
                $message = $this-&gt;options['portal-log-in-message'];
            else
                $message = $this-&gt;defaults['portal-log-in-message'];
 
            return $message;
        }
        else{
            $user_id = get_current_user_id();
            $private_page_id = $this-&gt;cp_get_private_page_for_user( $user_id );
            $private_page_link = get_permalink( $private_page_id );
            ?&gt;
            &lt;script&gt;
                window.location.replace("&lt;?php echo $private_page_link ?&gt;");
            &lt;/script&gt;
        &lt;?php
        }
    }

Create Settings Page for Our Plugin

There are just 3 settings and a button for generating private pages for existing clients. If you want to learn more about the WordPress Settings API this tutorial covers it all.

The function that generates a new private page for existing users is needed when you have clients already registered.

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
    /* create the settings page */
    add_action( 'admin_menu', <a href="http://www.php.net/array">array</a>( $this, 'cp_add_settings_page' ) );
    /* register the settings */
    add_action( 'admin_init', <a href="http://www.php.net/array">array</a>( $this, 'cp_register_settings' ) );
 
 
    /**
     * Function that creates the admin settings page under the Users menu
     */
    function cp_add_settings_page(){
        add_users_page( 'Client Portal Settings', 'Client Portal Settings', 'manage_options', 'client_portal_settings', <a href="http://www.php.net/array">array</a>( $this, 'cp_settings_page_content' ) );
    }
 
    /**
     * Function that outputs the content for the settings page
     */
    function cp_settings_page_content(){
        /* if the user pressed the generate button then generate pages for existing users */
        if( !<a href="http://www.php.net/empty">empty</a>( $_GET[ 'cp_generate_for_all' ] ) &amp;&amp; $_GET[ 'cp_generate_for_all' ] == true ){
            $this-&gt;cp_create_private_pages_for_all_users();
        }
 
        ?&gt;
        &lt;div class="wrap form-wrap"&gt;
 
            &lt;h2&gt;&lt;?php _e( 'Client Portal Settings', 'client-portal'); ?&gt;&lt;/h2&gt;
 
            &lt;?php settings_errors(); ?&gt;
 
            &lt;form method="POST" action="options.php"&gt;
 
                &lt;?php settings_fields( $this-&gt;slug ); ?&gt;
 
                &lt;div class="scp-form-field-wrapper"&gt;
                    &lt;label class="scp-form-field-label" for="page-slug"&gt;&lt;?php echo __( 'Page Slug' , 'client-portal' ) ?&gt;&lt;/label&gt;
                    &lt;input type="text" class="widefat" id="page-slug" name="cp-options[page-slug]" value="&lt;?php echo ( isset( $this-&gt;options['page-slug'] ) ? $this-&gt;options['page-slug'] : 'private-page' ); ?&gt;" /&gt;
                    &lt;p class="description"&gt;&lt;?php echo __( 'The slug of the pages.', 'client-portal' ); ?&gt;&lt;/p&gt;
                &lt;/div&gt;
 
                &lt;div class="scp-form-field-wrapper"&gt;
                    &lt;label class="scp-form-field-label"&gt;&lt;?php echo __( 'Generate pages' , 'client-portal' ) ?&gt;&lt;/label&gt;
                    &lt;a class="button" href="&lt;?php echo add_query_arg( 'cp_generate_for_all', 'true', admin_url("/users.php?page=client_portal_settings") ) ?&gt;"&gt;&lt;?php _e( 'Generate pages for existing users' ); ?&gt;&lt;/a&gt;
                    &lt;p class="description"&gt;&lt;?php echo __( 'Generate pages for already existing users.', 'client-portal' ); ?&gt;&lt;/p&gt;
                &lt;/div&gt;
 
                &lt;div class="scp-form-field-wrapper"&gt;
                    &lt;label class="scp-form-field-label" for="restricted-message"&gt;&lt;?php echo __( 'Restricted Message' , 'client-portal' ) ?&gt;&lt;/label&gt;
                    &lt;textarea name="cp-options[restricted-message]" id="restricted-message" class="widefat"&gt;&lt;?php echo ( <a href="http://www.php.net/isset">isset</a>( $this-&gt;options['restricted-message'] ) ? $this-&gt;options['restricted-message'] : $this-&gt;defaults['restricted-message'] ); ?&gt;&lt;/textarea&gt;
                    &lt;p class="description"&gt;&lt;?php echo __( 'The default message showed on pages that are restricted.', 'client-portal' ); ?&gt;&lt;/p&gt;
                &lt;/div&gt;
 
                &lt;div class="scp-form-field-wrapper"&gt;
                    &lt;label class="scp-form-field-label" for="portal-log-in-message"&gt;&lt;?php echo __( 'Portal Log In Message' , 'client-portal' ) ?&gt;&lt;/label&gt;
                    &lt;textarea name="cp-options[portal-log-in-message]" id="portal-log-in-message" class="widefat"&gt;&lt;?php echo ( <a href="http://www.php.net/isset">isset</a>( $this-&gt;options['portal-log-in-message'] ) ? $this-&gt;options['portal-log-in-message'] : $this-&gt;defaults['portal-log-in-message'] ); ?&gt;&lt;/textarea&gt;
                    &lt;p class="description"&gt;&lt;?php echo __( 'The default message showed on pages that are restricted.', 'client-portal' ); ?&gt;&lt;/p&gt;
                &lt;/div&gt;
 
                &lt;?php submit_button( __( 'Save Settings', 'client_portal_settings' ) ); ?&gt;
 
            &lt;/form&gt;
        &lt;/div&gt;
    &lt;?php
    }
 
    /**
     * Function that registers the settings for the settings page with the Settings API
     */
    public function cp_register_settings() {
        register_setting( $this-&gt;slug, $this-&gt;slug );
    }
 
    /**
     *  Function that creates private pages for all existing users
     */
    function cp_create_private_pages_for_all_users(){
        $all_users = get_users( <a href="http://www.php.net/array">array</a>(  'fields' =&gt; <a href="http://www.php.net/array">array</a>( 'ID' ) ) );
        if( !<a href="http://www.php.net/empty">empty</a>( $all_users ) ){
            foreach( $all_users as $user ){
                $args = <a href="http://www.php.net/array">array</a>(
                    'author'            =&gt;  $user-&gt;ID, // I could also use $user_ID, right?
                    'posts_per_page'    =&gt; 1,
                    'post_type'         =&gt; 'private-page',
                );
                $users_private_pages = get_posts( $args );
                if( <a href="http://www.php.net/empty">empty</a>( $users_private_pages ) ) {
                    $this-&gt;cp_create_private_page( $user-&gt;ID );
                }
 
            }
        }
    }

A Few More Tweaks for a Better Client Portal Plugin

In order to take this a bit further, we’ll have to style the settings page a bit, add some admin notices, regenerate the permalinks if we’re changing the private page slug, and make sure we don’t get next and previous posts navigation on our private pages.

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
68
69
70
71
72
    /* show notices on the admin settings page */
    add_action( 'admin_notices', <a href="http://www.php.net/array">array</a>( $this, 'cp_admin_notices' ) );
    // Enqueue scripts on the admin side
    add_action( 'admin_enqueue_scripts', <a href="http://www.php.net/array">array</a>( $this, 'cp_enqueue_admin_scripts' ) );
    /* flush the rewrite rules when settings saved in case page slug was changed */
    add_action('init', <a href="http://www.php.net/array">array</a>( $this, 'cp_flush_rules' ), 20 );
 
    /* make sure we don't have post navigation on the private pages */
    add_filter( "get_previous_post_where", <a href="http://www.php.net/array">array</a>( $this, 'cp_exclude_from_post_navigation' ), 10, 5 );
    add_filter( "get_next_post_where", <a href="http://www.php.net/array">array</a>( $this, 'cp_exclude_from_post_navigation' ), 10, 5 );
 
   /**
     * Function that creates the notice messages on the settings page
     */
    function cp_admin_notices(){
        if( !<a href="http://www.php.net/empty">empty</a>( $_GET['page'] ) &amp;&amp; $_GET['page'] == 'client_portal_settings' ) {
            if( !<a href="http://www.php.net/empty">empty</a>( $_GET['cp_generate_for_all'] ) &amp;&amp; $_GET['cp_generate_for_all'] == true ) {
                ?&gt;
                &lt;div class="notice notice-success is-dismissible"&gt;
                    &lt;p&gt;&lt;?php _e( 'Successfully generated private pages for existing users.', 'client-portal'); ?&gt;&lt;/p&gt;
                &lt;/div&gt;
                &lt;?php
                if( !<a href="http://www.php.net/empty">empty</a>( $_REQUEST['settings-updated'] ) &amp;&amp; $_GET['settings-updated'] == 'true' ) {
                    ?&gt;
                    &lt;div class="notice notice-success is-dismissible"&gt;
                        &lt;p&gt;&lt;?php _e( 'Settings saved.', 'client-portal'); ?&gt;&lt;/p&gt;
                    &lt;/div&gt;
                &lt;?php
                }
            }
        }
    }
 
    /**
     * Function that enqueues the scripts on the admin settings page
     */
    function cp_enqueue_admin_scripts() {
        if( !<a href="http://www.php.net/empty">empty</a>( $_GET['page'] ) &amp;&amp; $_GET['page'] == 'client_portal_settings' )
            wp_enqueue_style( 'cp_style-back-end', plugins_url( 'assets/style.css', __FILE__ ) );
    }
 
    /**
     * Function that flushes the rewrite rules when we save the settings page
     */
    function cp_flush_rules(){
        if( <a href="http://www.php.net/isset">isset</a>( $_GET['page'] ) &amp;&amp; $_GET['page'] == 'client_portal_settings' &amp;&amp; <a href="http://www.php.net/isset">isset</a>( $_REQUEST['settings-updated'] ) &amp;&amp; $_REQUEST['settings-updated'] == 'true' ) {
            flush_rewrite_rules(false);
        }
    }
 
 
    /**
     * Function that filters the WHERE clause in the select for adjacent posts so we exclude private pages
     * @param $where
     * @param $in_same_term
     * @param $excluded_terms
     * @param $taxonomy
     * @param $post
     * @return mixed
     */
    function cp_exclude_from_post_navigation( $where, $in_same_term, $excluded_terms, $taxonomy, $post ){
        if( $post-&gt;post_type == 'private-page' ){
            $where = <a href="http://www.php.net/str_replace">str_replace</a>( "'private-page'", "'do not show this'", $where );
        }
        return $where;
    }
 
    /**
     * Function that returns the id for the private page for the provided user
     * @param $user_id the user id for which we want to get teh private page for
     * @return mixed
     */

Create a New Object

The last thing we’re doing is create a new object with our CL_Client_Portal class.

1
$CP_Object = new CL_Client_Portal();

Entire Plugin

After all this, the entire plugin looks like this:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
&lt;?php
/**
 * Plugin Name: Client Portal
 * Plugin URI: http://www.cozmoslabs.com/
 * Description:  Build a company site with a client portal where clients login and see a restricted-access, personalized page of content with links and downloads.
 * Version: 1.0.0
 * Author: Cozmoslabs, Madalin Ungureanu, Antohe Cristian
 * Author URI: http://www.cozmoslabs.com
 * License: GPL2
 */
/*  Copyright 2015 Cozmoslabs (www.cozmoslabs.com)
 
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License, version 2, as
    published by the Free Software Foundation.
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
/*
* Define plugin path
*/
 
class CL_Client_Portal
{
    private $slug;
    private $defaults;
    public $options;
 
 
    function __construct()
    {
        $this-&gt;slug = 'cp-options';
        $this-&gt;options = get_option( $this-&gt;slug );
        $this-&gt;defaults = <a href="http://www.php.net/array">array</a>(
                                'page-slug' =&gt; 'private-page',
                                'restricted-message' =&gt; __( 'You do not have permission to view this page.', 'client-portal' ),
                                'portal-log-in-message' =&gt; __( 'Please log in in order to access the client portal.', 'client-portal' )
                                );
 
        /* register the post type */
        add_action( 'init', <a href="http://www.php.net/array">array</a>( $this, 'cp_create_post_type' ) );
        /* action to create a private page when a user registers */
        add_action( 'user_register', <a href="http://www.php.net/array">array</a>( $this, 'cp_create_private_page' ) );
        /* remove the page when a user is deleted */
        add_action( 'deleted_user', <a href="http://www.php.net/array">array</a>( $this, 'cp_delete_private_page' ), 10, 2 );
        /* restrict the content of the page only to the user */
        add_filter( 'the_content', <a href="http://www.php.net/array">array</a>( $this, 'cp_restrict_content' ) );
        /* add a link in the Users List Table in admin area to access the page */
        add_filter( 'user_row_actions', <a href="http://www.php.net/array">array</a>( $this, 'cp_add_link_to_private_page' ), 10, 2);
 
        /* create client portal extra information */
        add_filter('the_content', <a href="http://www.php.net/array">array</a>( $this, 'cp_add_private_page_info'));
 
        /* create the shortcode for the main page */
        add_shortcode( 'client-portal', <a href="http://www.php.net/array">array</a>( $this, 'cp_shortcode' ) );
 
        /* create the settings page */
        add_action( 'admin_menu', <a href="http://www.php.net/array">array</a>( $this, 'cp_add_settings_page' ) );
        /* register the settings */
        add_action( 'admin_init', <a href="http://www.php.net/array">array</a>( $this, 'cp_register_settings' ) );
        /* show notices on the admin settings page */
        add_action( 'admin_notices', <a href="http://www.php.net/array">array</a>( $this, 'cp_admin_notices' ) );
        // Enqueue scripts on the admin side
        add_action( 'admin_enqueue_scripts', <a href="http://www.php.net/array">array</a>( $this, 'cp_enqueue_admin_scripts' ) );
        /* flush the rewrite rules when settings saved in case page slug was changed */
        add_action('init', <a href="http://www.php.net/array">array</a>( $this, 'cp_flush_rules' ), 20 );
 
        /* make sure we don't have post navigation on the private pages */
        add_filter( "get_previous_post_where", <a href="http://www.php.net/array">array</a>( $this, 'cp_exclude_from_post_navigation' ), 10, 5 );
        add_filter( "get_next_post_where", <a href="http://www.php.net/array">array</a>( $this, 'cp_exclude_from_post_navigation' ), 10, 5 );
 
    }
 
    /**
     * Function that registers the post type
     */
    function cp_create_post_type() {
 
        $labels = <a href="http://www.php.net/array">array</a>(
            'name'               =&gt; _x( 'Private Pages', 'post type general name', 'client-portal' ),
            'singular_name'      =&gt; _x( 'Private Page', 'post type singular name', 'client-portal' ),
            'menu_name'          =&gt; _x( 'Private Page', 'admin menu', 'client-portal' ),
            'name_admin_bar'     =&gt; _x( 'Private Page', 'add new on admin bar', 'client-portal' ),
            'add_new'            =&gt; _x( 'Add New', 'private Page', 'client-portal' ),
            'add_new_item'       =&gt; __( 'Add New Private Page', 'client-portal' ),
            'new_item'           =&gt; __( 'New Private Page', 'client-portal' ),
            'edit_item'          =&gt; __( 'Edit Private Page', 'client-portal' ),
            'view_item'          =&gt; __( 'View Private Page', 'client-portal' ),
            'all_items'          =&gt; __( 'All Private Pages', 'client-portal' ),
            'search_items'       =&gt; __( 'Search Private Pages', 'client-portal' ),
            'parent_item_colon'  =&gt; __( 'Parent Private Page:', 'client-portal' ),
            'not_found'          =&gt; __( 'No Private Pages found.', 'client-portal' ),
            'not_found_in_trash' =&gt; __( 'No Private Pages found in Trash.', 'client-portal' )
        );
 
        $args = <a href="http://www.php.net/array">array</a>(
            'labels'             =&gt; $labels,
            'description'        =&gt; __( 'Description.', 'client-portal' ),
            'public'             =&gt; true,
            'publicly_queryable' =&gt; true,
            'show_ui'            =&gt; true,
            'show_in_menu'       =&gt; false,
            'query_var'          =&gt; true,
            'capability_type'    =&gt; 'post',
            'has_archive'        =&gt; false,
            'hierarchical'       =&gt; true,
            'supports'           =&gt; <a href="http://www.php.net/array">array</a>( 'title', 'editor', 'thumbnail' )
        );
 
        if( !<a href="http://www.php.net/empty">empty</a>( $this-&gt;options['page-slug'] ) ){
            $args['rewrite'] = <a href="http://www.php.net/array">array</a>( 'slug' =&gt; $this-&gt;options['page-slug'] );
        }
        else{
            $args['rewrite'] = <a href="http://www.php.net/array">array</a>( 'slug' =&gt; $this-&gt;defaults['page-slug'] );
        }
 
        register_post_type( 'private-page', $args );
    }
 
    /**
     * Function that creates the private page for a user
     * @param $user_id the id of the user for which to create the page
     */
    function cp_create_private_page( $user_id ){
        /* make sure get_userdata() is available at this point */
        if(is_admin()) require_once( ABSPATH . 'wp-includes/pluggable.php' );
 
        $user = get_userdata( $user_id );
        $display_name = '';
        if( $user ){
            $display_name = ($user-&gt;display_name) ? ($user-&gt;display_name) : ($user-&gt;user_login);
        }
 
        $private_page = <a href="http://www.php.net/array">array</a>(
            'post_title'    =&gt; $display_name,
            'post_status'   =&gt; 'publish',
            'post_type'     =&gt; 'private-page',
            'post_author'   =&gt; $user_id
        );
 
        // Insert the post into the database
        wp_insert_post( $private_page );
    }
 
    /**
     * Function that deletes the private page when the user is deleted
     * @param $id the id of the user which page we are deleting
     * @param $reassign
     */
    function cp_delete_private_page( $id, $reassign ){
        $private_page_id = $this-&gt;cp_get_private_page_for_user( $id );
        if( !<a href="http://www.php.net/empty">empty</a>( $private_page_id ) ){
            wp_delete_post( $private_page_id, true );
        }
    }
 
    /**
     * Function that restricts the content only to the author of the page
     * @param $content the content of the page
     * @return mixed
     */
    function cp_restrict_content( $content ){
        global $post;
        if( $post-&gt;post_type == 'private-page' ){
 
            if( !<a href="http://www.php.net/empty">empty</a>( $this-&gt;options['restricted-message'] ) )
                $message = $this-&gt;options['restricted-message'];
            else
                $message = $this-&gt;defaults['restricted-message'];
 
            if( is_user_logged_in() ){
                if( ( get_current_user_id() == $post-&gt;post_author ) || current_user_can('delete_user') ){
                    return $content;
                }
                else return $message;
            }
            else return $message;
 
        }
        return $content;
    }
 
    /**
     * Function that adds a link in the user listing in admin area to access the private page
     * @param $actions The actions available on the user listing in admin area
     * @param $user_object The user object
     * @return mixed
     */
    function cp_add_link_to_private_page( $actions, $user_object ){
        $private_page_id = $this-&gt;cp_get_private_page_for_user( $user_object-&gt;ID );
        if( !<a href="http://www.php.net/empty">empty</a>( $private_page_id ) ){
            $actions['private_page_link'] = "&lt;a class='cp_private_page' href='" . admin_url( "post.php?post=$private_page_id&amp;action=edit") . "'&gt;" . __( 'Private Page', 'client-portal' ) . "&lt;/a&gt;";
        }
 
        return $actions;
    }
 
    /**
     * Function that creates a private page extra information div
     * @param $content the content of the private page
     * @return mixed
     */
    function cp_add_private_page_info( $content ){
        global $post;
        if ( is_singular('private-page') &amp;&amp; is_user_logged_in() ){
            // logout link
            $logout_link = wp_loginout( home_url(), false);
 
            // author display name. Fallback to username if no display name is set.
            $author_id=$post-&gt;post_author;
            $user = get_user_by('id', $author_id);
            $display_name = '';
            if( $user ){
                $display_name = ($user-&gt;display_name) ? ($user-&gt;display_name) : ($user-&gt;user_login);
            }
 
            $extra_info = "&lt;p class='cp-logout' style='border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; padding: 0.5rem 0; text-align: right'&gt; $logout_link - $display_name &lt;/p&gt;";
 
            return  $extra_info . $content;
        }
 
        return $content;
    }
 
    /**
     * Function that creates a shortcode which redirects the user to its private page
     * @param $atts the shortcode attributes
     */
    function cp_shortcode( $atts ){
        if( !is_user_logged_in() ){
            if( !<a href="http://www.php.net/empty">empty</a>( $this-&gt;options['portal-log-in-message'] ) )
                $message = $this-&gt;options['portal-log-in-message'];
            else
                $message = $this-&gt;defaults['portal-log-in-message'];
 
            return $message;
        }
        else{
            $user_id = get_current_user_id();
            $private_page_id = $this-&gt;cp_get_private_page_for_user( $user_id );
            $private_page_link = get_permalink( $private_page_id );
            ?&gt;
            &lt;script&gt;
                window.location.replace("&lt;?php echo $private_page_link ?&gt;");
            &lt;/script&gt;
        &lt;?php
        }
    }
 
    /**
     * Function that creates the admin settings page under the Users menu
     */
    function cp_add_settings_page(){
        add_users_page( 'Client Portal Settings', 'Client Portal Settings', 'manage_options', 'client_portal_settings', <a href="http://www.php.net/array">array</a>( $this, 'cp_settings_page_content' ) );
    }
 
    /**
     * Function that outputs the content for the settings page
     */
    function cp_settings_page_content(){
        /* if the user pressed the generate button then generate pages for existing users */
        if( !<a href="http://www.php.net/empty">empty</a>( $_GET[ 'cp_generate_for_all' ] ) &amp;&amp; $_GET[ 'cp_generate_for_all' ] == true ){
            $this-&gt;cp_create_private_pages_for_all_users();
        }
 
        ?&gt;
        &lt;div class="wrap form-wrap"&gt;
 
            &lt;h2&gt;&lt;?php _e( 'Client Portal Settings', 'client-portal'); ?&gt;&lt;/h2&gt;
 
            &lt;?php settings_errors(); ?&gt;
 
            &lt;form method="POST" action="options.php"&gt;
 
                &lt;?php settings_fields( $this-&gt;slug ); ?&gt;
 
                &lt;div class="scp-form-field-wrapper"&gt;
                    &lt;label class="scp-form-field-label" for="page-slug"&gt;&lt;?php echo __( 'Page Slug' , 'client-portal' ) ?&gt;&lt;/label&gt;
                    &lt;input type="text" class="widefat" id="page-slug" name="cp-options[page-slug]" value="&lt;?php echo ( isset( $this-&gt;options['page-slug'] ) ? $this-&gt;options['page-slug'] : 'private-page' ); ?&gt;" /&gt;
                    &lt;p class="description"&gt;&lt;?php echo __( 'The slug of the pages.', 'client-portal' ); ?&gt;&lt;/p&gt;
                &lt;/div&gt;
 
                &lt;div class="scp-form-field-wrapper"&gt;
                    &lt;label class="scp-form-field-label"&gt;&lt;?php echo __( 'Generate pages' , 'client-portal' ) ?&gt;&lt;/label&gt;
                    &lt;a class="button" href="&lt;?php echo add_query_arg( 'cp_generate_for_all', 'true', admin_url("/users.php?page=client_portal_settings") ) ?&gt;"&gt;&lt;?php _e( 'Generate pages for existing users' ); ?&gt;&lt;/a&gt;
                    &lt;p class="description"&gt;&lt;?php echo __( 'Generate pages for already existing users.', 'client-portal' ); ?&gt;&lt;/p&gt;
                &lt;/div&gt;
 
                &lt;div class="scp-form-field-wrapper"&gt;
                    &lt;label class="scp-form-field-label" for="restricted-message"&gt;&lt;?php echo __( 'Restricted Message' , 'client-portal' ) ?&gt;&lt;/label&gt;
                    &lt;textarea name="cp-options[restricted-message]" id="restricted-message" class="widefat"&gt;&lt;?php echo ( <a href="http://www.php.net/isset">isset</a>( $this-&gt;options['restricted-message'] ) ? $this-&gt;options['restricted-message'] : $this-&gt;defaults['restricted-message'] ); ?&gt;&lt;/textarea&gt;
                    &lt;p class="description"&gt;&lt;?php echo __( 'The default message showed on pages that are restricted.', 'client-portal' ); ?&gt;&lt;/p&gt;
                &lt;/div&gt;
 
                &lt;div class="scp-form-field-wrapper"&gt;
                    &lt;label class="scp-form-field-label" for="portal-log-in-message"&gt;&lt;?php echo __( 'Portal Log In Message' , 'client-portal' ) ?&gt;&lt;/label&gt;
                    &lt;textarea name="cp-options[portal-log-in-message]" id="portal-log-in-message" class="widefat"&gt;&lt;?php echo ( <a href="http://www.php.net/isset">isset</a>( $this-&gt;options['portal-log-in-message'] ) ? $this-&gt;options['portal-log-in-message'] : $this-&gt;defaults['portal-log-in-message'] ); ?&gt;&lt;/textarea&gt;
                    &lt;p class="description"&gt;&lt;?php echo __( 'The default message showed on pages that are restricted.', 'client-portal' ); ?&gt;&lt;/p&gt;
                &lt;/div&gt;
 
                &lt;?php submit_button( __( 'Save Settings', 'client_portal_settings' ) ); ?&gt;
 
            &lt;/form&gt;
        &lt;/div&gt;
    &lt;?php
    }
 
    /**
     * Function that registers the settings for the settings page with the Settings API
     */
    public function cp_register_settings() {
        register_setting( $this-&gt;slug, $this-&gt;slug );
    }
 
    /**
     * Function that creates the notice messages on the settings page
     */
    function cp_admin_notices(){
        if( !<a href="http://www.php.net/empty">empty</a>( $_GET['page'] ) &amp;&amp; $_GET['page'] == 'client_portal_settings' ) {
            if( !<a href="http://www.php.net/empty">empty</a>( $_GET['cp_generate_for_all'] ) &amp;&amp; $_GET['cp_generate_for_all'] == true ) {
                ?&gt;
                &lt;div class="notice notice-success is-dismissible"&gt;
                    &lt;p&gt;&lt;?php _e( 'Successfully generated private pages for existing users.', 'client-portal'); ?&gt;&lt;/p&gt;
                &lt;/div&gt;
                &lt;?php
                if( !<a href="http://www.php.net/empty">empty</a>( $_REQUEST['settings-updated'] ) &amp;&amp; $_GET['settings-updated'] == 'true' ) {
                    ?&gt;
                    &lt;div class="notice notice-success is-dismissible"&gt;
                        &lt;p&gt;&lt;?php _e( 'Settings saved.', 'client-portal'); ?&gt;&lt;/p&gt;
                    &lt;/div&gt;
                &lt;?php
                }
            }
        }
    }
 
    /**
     * Function that enqueues the scripts on the admin settings page
     */
    function cp_enqueue_admin_scripts() {
        if( !<a href="http://www.php.net/empty">empty</a>( $_GET['page'] ) &amp;&amp; $_GET['page'] == 'client_portal_settings' )
            wp_enqueue_style( 'cp_style-back-end', plugins_url( 'assets/style.css', __FILE__ ) );
    }
 
    /**
     * Function that flushes the rewrite rules when we save the settings page
     */
    function cp_flush_rules(){
        if( <a href="http://www.php.net/isset">isset</a>( $_GET['page'] ) &amp;&amp; $_GET['page'] == 'client_portal_settings' &amp;&amp; <a href="http://www.php.net/isset">isset</a>( $_REQUEST['settings-updated'] ) &amp;&amp; $_REQUEST['settings-updated'] == 'true' ) {
            flush_rewrite_rules(false);
        }
    }
 
 
    /**
     * Function that filters the WHERE clause in the select for adjacent posts so we exclude private pages
     * @param $where
     * @param $in_same_term
     * @param $excluded_terms
     * @param $taxonomy
     * @param $post
     * @return mixed
     */
    function cp_exclude_from_post_navigation( $where, $in_same_term, $excluded_terms, $taxonomy, $post ){
        if( $post-&gt;post_type == 'private-page' ){
            $where = <a href="http://www.php.net/str_replace">str_replace</a>( "'private-page'", "'do not show this'", $where );
        }
        return $where;
    }
 
    /**
     * Function that returns the id for the private page for the provided user
     * @param $user_id the user id for which we want to get teh private page for
     * @return mixed
     */
    function cp_get_private_page_for_user( $user_id ){
        $args = <a href="http://www.php.net/array">array</a>(
            'author'            =&gt;  $user_id,
            'posts_per_page'    =&gt;  1,
            'post_type'         =&gt; 'private-page',
        );
        $users_private_pages = get_posts( $args );
 
        if( !<a href="http://www.php.net/empty">empty</a>( $users_private_pages ) ){
            foreach( $users_private_pages as $users_private_page ){
                return $users_private_page-&gt;ID;
                break;
            }
        }
    }
 
    /**
     * Function that returns all the private pages post objects
     * @return array
     */
    function cp_get_all_private_pages(){
        $args = <a href="http://www.php.net/array">array</a>(
            'posts_per_page'    =&gt;  -1,
            'numberposts'       =&gt;   -1,
            'post_type'         =&gt; 'private-page',
        );
 
        $users_private_pages = get_posts( $args );
        return $users_private_pages;
    }
 
    /**
     *  Function that creates private pages for all existing users
     */
    function cp_create_private_pages_for_all_users(){
        $all_users = get_users( <a href="http://www.php.net/array">array</a>(  'fields' =&gt; <a href="http://www.php.net/array">array</a>( 'ID' ) ) );
        if( !<a href="http://www.php.net/empty">empty</a>( $all_users ) ){
            foreach( $all_users as $user ){
                $args = <a href="http://www.php.net/array">array</a>(
                    'author'            =&gt;  $user-&gt;ID, // I could also use $user_ID, right?
                    'posts_per_page'    =&gt; 1,
                    'post_type'         =&gt; 'private-page',
                );
                $users_private_pages = get_posts( $args );
                if( <a href="http://www.php.net/empty">empty</a>( $users_private_pages ) ) {
                    $this-&gt;cp_create_private_page( $user-&gt;ID );
                }
 
            }
        }
    }
 
}
 
$CP_Object = new CL_Client_Portal();

You can download the entire plugin from WordPress.org.

Get Client Portal Plugin

Moving Forward

The reason we were able to make this work in ~430 lines of code (~30% comments) is simply that we can offload anything else we might need to existing free plugins, which we can use as add-ons:

Obviously, there are other plugins that do the above so any login, membership, or custom fields plugin will allow you to extend the Client Portal plugin to fit your needs exactly. And an SEO plugin will help you attract more visitors to sign up for your client portal.

Get Client Portal Plugin

Do you have any more questions about client portal plugins? Let us know in the comments below.

From the blog

Related Articles

Best User Registration Plugins for WordPress

8 Best WordPress User Registration Plugins

Author: Cristian Antohe
Last Updated: July 9th, 2024

Not sure what WordPress user registration plugin to use for your project? Going through all the WordPress user registration plugins can be disorienting. You may have spent hours and hours searching for plugins and tutorials to help you with your project, and you’re still as confused as you were when you started. Imagine finding the […]

Continue Reading
roundup wordpress ecosystem january2

Roundup of WordPress ecosystem #1 – January 2017

Author: Patricia Borlovan
Last Updated: February 9th, 2017

After writing the article "Overview of the WordPress Community in 2016" and getting feedback for the article on various platforms, I decided to continue writing them, but I changed its name into "Roundup of WordPress ecosystem". This is the first article from a monthly series that will showcase what happened around the whole ecosystem in […]

Continue Reading

5 Free And Easy to Use WordPress Membership Plugins

Author: Cristian Antohe
Last Updated: May 22nd, 2019

Free and easy to use WordPress membership plugins are not a myth. Finding and getting started with them is simpler then you might think. Often website owners require visitors to sign up and become members of their platform before allowing access to the content. The solutions for WordPress users are a variety of membership plugins […]

Continue Reading

14 thoughts on “How to Build a WordPress Client Portal Plugin

    Hi,

    Thanks so much for this tutorial and plugin. I use it on my website and I love it. I have one small question. Is there a way I can have pre-entered data on each private page when a new one is created for each user. I use my private pages as a transcript and there is a specific template that needs to be inserted into each page, how would I go about doing that?

    Thanks

    Reply

    Please I want to add a welcome note and other related content/data to be displayed immediately a user private page is created. Please guide me on how to do this. I love you for what you are doing, God will reward abundantly. More power to your elbow.

    Reply

    Good day! Nice addon, but the user can change the content of other pages. How can I restrict my rights so that the user can only edit their own page?

    Reply

    Hello Vasily,

    The idea of this plugin is not to let the user edit this page, but only the admin to place private content, specific to that user on it.

    Reply

    I too would like to be able to add a generic text that would automatically go on all newly created private pages. Is this possible?
    Thank you

    Reply

    Hello Charles,

    Yes, this is possible. After installing, go to Users -> Client Portal Settings and you will have a Content Box that lets you do this.

    Reply

    Please delete my reply to the post above, I didn’t mean to reply to the previous question, I was asking a new question:

    I’m wondering what the logic/thought process is behind the decision to hide the WP Admin menu for the custom post type?

    All this does is make it harder to reach the private pages in the admin since we need to open users, find the user, then click on the private page link.

    Surely it would be easier to simply have the private pages post type as a menu item so we can go there directly?

    I’m very interested in the pro version of the tools, but this is one of the decisions in the admin setup that is making me scratch my head a bit and hesitate.

    Reply

    Hi,

    That’s because you reach them from the backend user listing. They are tied to a particular user anyway. So listing them also as a top level menu item would just clutter the UI. At least that’s the reasoning behind it.

    Reply

    Please note there isn’t a pro version of Client Portal.

    Both Profile Builder Pro and Paid Member Subscriptions solve other problems.

    Reply

    how to get every user’s private page link?
    if the page slug is private, can I use wp.com/private/$user_id?

    Reply

    Is there anyway to edit the private page settings? Such as make it full width?

    Reply

    How can we make the Private Page full width? It always has the right sidebar attached.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.