Table of Contents
- 1 Building a Customer Portal in 3 Easy Steps
- 2 What to Use Your Client Portal For?
- 3 Building a WordPress Client Portal Plugin
- 3.1 Create a New Plugin
- 3.2 Build a PHP Class That will Contain Our Plugin Functions
- 3.3 Initialize the Default Variables Parameters
- 3.4 Register the Private Page Custom Post Type
- 3.5 Creating and Deleting the Private Pages on User Creation and Deletion
- 3.6 Restrict the Content of the Private Page if the User Doesn’t Have Access To It
- 3.7 Adding Links to the User Private Pages in WordPress Dashboard
- 3.8 Adding a Logout Button on the Private Page for Users to Use
- 3.9 Creating the [client-portal] Shortcode
- 3.10 Create Settings Page for Our Plugin
- 3.11 A Few More Tweaks for a Better Client Portal Plugin
- 3.12 Create a New Object
- 3.13 Entire Plugin
- 4 Moving Forward
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.
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.
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:
- 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.
- 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.
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->display_name) ? ($user->display_name) : ($user->user_login); } $private_page = <a href="http://www.php.net/array">array</a>( 'post_title' => $display_name, 'post_status' => 'publish', 'post_type' => 'private-page', 'post_author' => $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->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' => $user_id, 'posts_per_page' => 1, 'post_type' => '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->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->post_type == 'private-page' ){ if( !<a href="http://www.php.net/empty">empty</a>( $this->options['restricted-message'] ) ) $message = $this->options['restricted-message']; else $message = $this->defaults['restricted-message']; if( is_user_logged_in() ){ if( ( get_current_user_id() == $post->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->cp_get_private_page_for_user( $user_object->ID ); if( !<a href="http://www.php.net/empty">empty</a>( $private_page_id ) ){ $actions['private_page_link'] = "<a class='cp_private_page' href='" . admin_url( "post.php?post=$private_page_id&action=edit") . "'>" . __( 'Private Page', 'client-portal' ) . "</a>"; } 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') && 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->post_author; $user = get_user_by('id', $author_id); $display_name = ''; if( $user ){ $display_name = ($user->display_name) ? ($user->display_name) : ($user->user_login); } $extra_info = "<p class='cp-logout' style='border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; padding: 0.5rem 0; text-align: right'> $logout_link - $display_name </p>"; 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->options['portal-log-in-message'] ) ) $message = $this->options['portal-log-in-message']; else $message = $this->defaults['portal-log-in-message']; return $message; } else{ $user_id = get_current_user_id(); $private_page_id = $this->cp_get_private_page_for_user( $user_id ); $private_page_link = get_permalink( $private_page_id ); ?> <script> window.location.replace("<?php echo $private_page_link ?>"); </script> <?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' ] ) && $_GET[ 'cp_generate_for_all' ] == true ){ $this->cp_create_private_pages_for_all_users(); } ?> <div class="wrap form-wrap"> <h2><?php _e( 'Client Portal Settings', 'client-portal'); ?></h2> <?php settings_errors(); ?> <form method="POST" action="options.php"> <?php settings_fields( $this->slug ); ?> <div class="scp-form-field-wrapper"> <label class="scp-form-field-label" for="page-slug"><?php echo __( 'Page Slug' , 'client-portal' ) ?></label> <input type="text" class="widefat" id="page-slug" name="cp-options[page-slug]" value="<?php echo ( isset( $this->options['page-slug'] ) ? $this->options['page-slug'] : 'private-page' ); ?>" /> <p class="description"><?php echo __( 'The slug of the pages.', 'client-portal' ); ?></p> </div> <div class="scp-form-field-wrapper"> <label class="scp-form-field-label"><?php echo __( 'Generate pages' , 'client-portal' ) ?></label> <a class="button" href="<?php echo add_query_arg( 'cp_generate_for_all', 'true', admin_url("/users.php?page=client_portal_settings") ) ?>"><?php _e( 'Generate pages for existing users' ); ?></a> <p class="description"><?php echo __( 'Generate pages for already existing users.', 'client-portal' ); ?></p> </div> <div class="scp-form-field-wrapper"> <label class="scp-form-field-label" for="restricted-message"><?php echo __( 'Restricted Message' , 'client-portal' ) ?></label> <textarea name="cp-options[restricted-message]" id="restricted-message" class="widefat"><?php echo ( <a href="http://www.php.net/isset">isset</a>( $this->options['restricted-message'] ) ? $this->options['restricted-message'] : $this->defaults['restricted-message'] ); ?></textarea> <p class="description"><?php echo __( 'The default message showed on pages that are restricted.', 'client-portal' ); ?></p> </div> <div class="scp-form-field-wrapper"> <label class="scp-form-field-label" for="portal-log-in-message"><?php echo __( 'Portal Log In Message' , 'client-portal' ) ?></label> <textarea name="cp-options[portal-log-in-message]" id="portal-log-in-message" class="widefat"><?php echo ( <a href="http://www.php.net/isset">isset</a>( $this->options['portal-log-in-message'] ) ? $this->options['portal-log-in-message'] : $this->defaults['portal-log-in-message'] ); ?></textarea> <p class="description"><?php echo __( 'The default message showed on pages that are restricted.', 'client-portal' ); ?></p> </div> <?php submit_button( __( 'Save Settings', 'client_portal_settings' ) ); ?> </form> </div> <?php } /** * Function that registers the settings for the settings page with the Settings API */ public function cp_register_settings() { register_setting( $this->slug, $this->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' => <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' => $user->ID, // I could also use $user_ID, right? 'posts_per_page' => 1, 'post_type' => 'private-page', ); $users_private_pages = get_posts( $args ); if( <a href="http://www.php.net/empty">empty</a>( $users_private_pages ) ) { $this->cp_create_private_page( $user->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'] ) && $_GET['page'] == 'client_portal_settings' ) { if( !<a href="http://www.php.net/empty">empty</a>( $_GET['cp_generate_for_all'] ) && $_GET['cp_generate_for_all'] == true ) { ?> <div class="notice notice-success is-dismissible"> <p><?php _e( 'Successfully generated private pages for existing users.', 'client-portal'); ?></p> </div> <?php if( !<a href="http://www.php.net/empty">empty</a>( $_REQUEST['settings-updated'] ) && $_GET['settings-updated'] == 'true' ) { ?> <div class="notice notice-success is-dismissible"> <p><?php _e( 'Settings saved.', 'client-portal'); ?></p> </div> <?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'] ) && $_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'] ) && $_GET['page'] == 'client_portal_settings' && <a href="http://www.php.net/isset">isset</a>( $_REQUEST['settings-updated'] ) && $_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->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 | <?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->slug = 'cp-options'; $this->options = get_option( $this->slug ); $this->defaults = <a href="http://www.php.net/array">array</a>( '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 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' => _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 = <a href="http://www.php.net/array">array</a>( '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' => <a href="http://www.php.net/array">array</a>( 'title', 'editor', 'thumbnail' ) ); if( !<a href="http://www.php.net/empty">empty</a>( $this->options['page-slug'] ) ){ $args['rewrite'] = <a href="http://www.php.net/array">array</a>( 'slug' => $this->options['page-slug'] ); } else{ $args['rewrite'] = <a href="http://www.php.net/array">array</a>( 'slug' => $this->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->display_name) ? ($user->display_name) : ($user->user_login); } $private_page = <a href="http://www.php.net/array">array</a>( 'post_title' => $display_name, 'post_status' => 'publish', 'post_type' => 'private-page', 'post_author' => $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->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->post_type == 'private-page' ){ if( !<a href="http://www.php.net/empty">empty</a>( $this->options['restricted-message'] ) ) $message = $this->options['restricted-message']; else $message = $this->defaults['restricted-message']; if( is_user_logged_in() ){ if( ( get_current_user_id() == $post->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->cp_get_private_page_for_user( $user_object->ID ); if( !<a href="http://www.php.net/empty">empty</a>( $private_page_id ) ){ $actions['private_page_link'] = "<a class='cp_private_page' href='" . admin_url( "post.php?post=$private_page_id&action=edit") . "'>" . __( 'Private Page', 'client-portal' ) . "</a>"; } 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') && 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->post_author; $user = get_user_by('id', $author_id); $display_name = ''; if( $user ){ $display_name = ($user->display_name) ? ($user->display_name) : ($user->user_login); } $extra_info = "<p class='cp-logout' style='border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; padding: 0.5rem 0; text-align: right'> $logout_link - $display_name </p>"; 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->options['portal-log-in-message'] ) ) $message = $this->options['portal-log-in-message']; else $message = $this->defaults['portal-log-in-message']; return $message; } else{ $user_id = get_current_user_id(); $private_page_id = $this->cp_get_private_page_for_user( $user_id ); $private_page_link = get_permalink( $private_page_id ); ?> <script> window.location.replace("<?php echo $private_page_link ?>"); </script> <?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' ] ) && $_GET[ 'cp_generate_for_all' ] == true ){ $this->cp_create_private_pages_for_all_users(); } ?> <div class="wrap form-wrap"> <h2><?php _e( 'Client Portal Settings', 'client-portal'); ?></h2> <?php settings_errors(); ?> <form method="POST" action="options.php"> <?php settings_fields( $this->slug ); ?> <div class="scp-form-field-wrapper"> <label class="scp-form-field-label" for="page-slug"><?php echo __( 'Page Slug' , 'client-portal' ) ?></label> <input type="text" class="widefat" id="page-slug" name="cp-options[page-slug]" value="<?php echo ( isset( $this->options['page-slug'] ) ? $this->options['page-slug'] : 'private-page' ); ?>" /> <p class="description"><?php echo __( 'The slug of the pages.', 'client-portal' ); ?></p> </div> <div class="scp-form-field-wrapper"> <label class="scp-form-field-label"><?php echo __( 'Generate pages' , 'client-portal' ) ?></label> <a class="button" href="<?php echo add_query_arg( 'cp_generate_for_all', 'true', admin_url("/users.php?page=client_portal_settings") ) ?>"><?php _e( 'Generate pages for existing users' ); ?></a> <p class="description"><?php echo __( 'Generate pages for already existing users.', 'client-portal' ); ?></p> </div> <div class="scp-form-field-wrapper"> <label class="scp-form-field-label" for="restricted-message"><?php echo __( 'Restricted Message' , 'client-portal' ) ?></label> <textarea name="cp-options[restricted-message]" id="restricted-message" class="widefat"><?php echo ( <a href="http://www.php.net/isset">isset</a>( $this->options['restricted-message'] ) ? $this->options['restricted-message'] : $this->defaults['restricted-message'] ); ?></textarea> <p class="description"><?php echo __( 'The default message showed on pages that are restricted.', 'client-portal' ); ?></p> </div> <div class="scp-form-field-wrapper"> <label class="scp-form-field-label" for="portal-log-in-message"><?php echo __( 'Portal Log In Message' , 'client-portal' ) ?></label> <textarea name="cp-options[portal-log-in-message]" id="portal-log-in-message" class="widefat"><?php echo ( <a href="http://www.php.net/isset">isset</a>( $this->options['portal-log-in-message'] ) ? $this->options['portal-log-in-message'] : $this->defaults['portal-log-in-message'] ); ?></textarea> <p class="description"><?php echo __( 'The default message showed on pages that are restricted.', 'client-portal' ); ?></p> </div> <?php submit_button( __( 'Save Settings', 'client_portal_settings' ) ); ?> </form> </div> <?php } /** * Function that registers the settings for the settings page with the Settings API */ public function cp_register_settings() { register_setting( $this->slug, $this->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'] ) && $_GET['page'] == 'client_portal_settings' ) { if( !<a href="http://www.php.net/empty">empty</a>( $_GET['cp_generate_for_all'] ) && $_GET['cp_generate_for_all'] == true ) { ?> <div class="notice notice-success is-dismissible"> <p><?php _e( 'Successfully generated private pages for existing users.', 'client-portal'); ?></p> </div> <?php if( !<a href="http://www.php.net/empty">empty</a>( $_REQUEST['settings-updated'] ) && $_GET['settings-updated'] == 'true' ) { ?> <div class="notice notice-success is-dismissible"> <p><?php _e( 'Settings saved.', 'client-portal'); ?></p> </div> <?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'] ) && $_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'] ) && $_GET['page'] == 'client_portal_settings' && <a href="http://www.php.net/isset">isset</a>( $_REQUEST['settings-updated'] ) && $_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->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' => $user_id, 'posts_per_page' => 1, 'post_type' => '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->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' => -1, 'numberposts' => -1, 'post_type' => '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' => <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' => $user->ID, // I could also use $user_ID, right? 'posts_per_page' => 1, 'post_type' => 'private-page', ); $users_private_pages = get_posts( $args ); if( <a href="http://www.php.net/empty">empty</a>( $users_private_pages ) ) { $this->cp_create_private_page( $user->ID ); } } } } } $CP_Object = new CL_Client_Portal(); |
You can download the entire plugin from WordPress.org.
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:
- You can use login and registration from Profile Builder
- If you want paid users you can also use Paid Member Subscriptions
- Adding extra fields to the private page custom post type is possible with WordPress Creation Kit
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.
Do you have any more questions about client portal plugins? Let us know in the comments below.
Related Articles
Beginner’s Guide to: What Is WordPress?
Ever now and again the question arises with new clients that aren't really tech savvy: "What Is WordPress?" What I'm hoping to achieve with this post is to drop the technical jargon for a minute and explain in down to earth words what is WordPress, how can it help you, what is WordPress.com, what's a […]
Continue ReadingBest WordPress Monetization Plugins & Tactics to Grow Your Revenue
You will agree that finding the best WordPress monetization plugins (and tactics) to grow your revenue is challenging. If that describes your situation, you're in the right place, and we have your back as always. Like you, I had big dreams when I started my first WordPress site. Also, like you, I was skeptical about […]
Continue ReadingA Guide to WordPress Forum Plugins: The Best Plugins & How To Manage Them
If your WordPress site is designed to serve and engage with a community, you’ll need to install a WordPress forum plugin. Forum plugins add a platform where users can ask questions, provide answers, and join in discussions. It promotes engagement and gives users a sense of being part of a community. You can use it […]
Continue Reading
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
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.
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?
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.
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
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.
so i set this up as a widget on the side of my site and every time i log in using my “test user” it redirects to a wordpress log in and not to their private page. I can’t get it to work
Hey Dalton,
Please open a ticket and describe your issue in detail: https://www.cozmoslabs.com/support/open-ticket/
Regards.
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.
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.
Please note there isn’t a pro version of Client Portal.
Both Profile Builder Pro and Paid Member Subscriptions solve other problems.
how to get every user’s private page link?
if the page slug is private, can I use wp.com/private/$user_id?
Is there anyway to edit the private page settings? Such as make it full width?
How can we make the Private Page full width? It always has the right sidebar attached.