https://markspeciale.com/wp-content/plugins
WordPress Logo

Custom WordPress Plugins

WordPress is a wonderful website development and management tool. It’s used by an estimated 1/3 to 1/2 of all websites. It’s widely supported and provides a great common framework for customization, administration and user content management.

Mark created the basic plugin template and tutorial video below to help other LAMP and PHP coders to more easily begin creating custom WordPress plugins.

EXAMPLE PLUGIN: ms-data-display

This demo plugin and tutorial are intended for PHP/JavaScript developers who want to quickly understand the basics for creating custom applications for deployment within the WordPress CMS environment.

The table shown below is filled with dummy data, which is queried from an external database and displayed by the ms-data-display plugin. The plugin formats the table data and markup, and tells WordPress to simply display that instead, wherever it comes across a specific “shortcode” phrase like this…

[ms-data-display-product-table].

The website’s content administrators simply type the shortcode into any webpage on the site, wherever they want to display the table. The table’s sort field and order can also be set in the plugin’s simple administration section.

This plugin is only around 300 lines of descriptively-commented code. It serves as a well-rounded teaching template for new plugin developers, touching on a number of common aspects of plugin development, such as…

  • Plugin basic creation and security
  • Interaction with the built-in WordPress Database and others
  • Creating a Plugin Administration Menu link and Settings Page
  • Using the built-in $wpdb object and options table to store and retrieve settings data
  • Creation of shortcodes for deployment of output
  • Registering an “Uninstall” hook to clean everything up upon removal

You’ll have to supply your own external database for your dummy data of course, but the plugin is free, as-is, under the GPL2 license, the code is below, and the tutorial video should explain it all. 🙂

Here’s the Dummy Data that replaces the shortcode…

IMAGE
PRODUCT
PRICE
TYPE
POWER OUTPUT (Watts)
MSD-2400
$ 3629.00
High-Res Filangee
12
MSD-2800
$ 3929.00
High-Res Filangee
14
MSD-3200
$ 4168.00
High-Res Filangee
14
MSD-4400
$ 5629.00
High-Res Filangee
10
MSD-4800
$ 6229.00
High-Res Filangee
16
MSD-5200
$ 6868.00
High-Res Filangee
18
SMC-1200
$ 1359.00
Low-Res Filangee
8
SMC-1450
$ 1469.00
Low-Res Filangee
9
SMC-2200
$ 1959.00
Low-Res Filangee
12
SMC-2450
$ 2069.00
Low-Res Filangee
12

…and here’s the code that makes that happen (download here)…

ms-data-display.php
<?php
/**
 * Plugin Name: Mark Speciale - Tutorial Plugin: Data Display
 * Plugin URI: https://markspeciale.com/custom-wordpress-plugins/
 * Description: Basic plugin example that displays data with shortcode and has a simple admin section.
 * Version: 1.0.0
 * Author: Mark Speciale
 * Author URI: https://markspeciale.com
 * License: GPL2
 * Unique Prefix: ms225CF2
 * Shortcode: ms-data-display-products
 */

////////// First, don't allow hackers to access this page directly
if ( ! defined( 'ABSPATH' ) ) {// WordPress defines ABSPATH in the normal 'loading' stage
	exit; // Shut it down if it's not a legit call
}

/////////////////////////////// The Main Plugin Class: ms225CF2_data_display
////////////////////////////// Replaces shortcode placed in website pages with database records.
class ms225CF2_data_display {

    //////////////////////// DEFAULT FUNCTION (METHOD) - runs automagically
    ///////////////////// __construct()
    public function __construct() {

	////// Plugin settings administration and management
	// Create a 'settings' page link in WordPress' Admin menu
    	add_action( 'admin_menu', array( $this, 'ms225CF2_create_plugin_settings_page_link' ) );

        // Set up the Admin page's sections and form fields (for simple "SORT BY" and "ORDER" settings)
        add_action( 'admin_init', array( $this, 'ms225CF2_setup_sections' ) );
    	add_action( 'admin_init', array( $this, 'ms225CF2_setup_fields' ) );

	// Add a style sheet for the data table formatting
	add_action('wp_enqueue_scripts', array( $this, 'ms225CF2_add_scripts'));		
		
		
	////// Plugin main functionality	
	// Replace the shortcode on the page with the data, sorted as set in the admin page
	add_shortcode('ms-data-display-products', array($this, 'ms225CF2_data_display_get_data'));	


    }//End __construct
	

	//////////////////////// OTHER METHODS
	//////////////////////// ms225CF2_add_scripts()
	// Register or enqueue scripts/styles as needed, here
	function ms225CF2_add_scripts(){
		$plugin_url = plugin_dir_url( __FILE__ );
		wp_enqueue_style('ms225CF2-data-display-style', $plugin_url . 'css/ms-data-display.css');// The style sheet for the table	
		
	}

	///////////////////// ms225CF2_data_display_get_data()
	// Gets and formats the data that replaces the shortcode
	public function ms225CF2_data_display_get_data(){

		// Check for existing 'SORT BY' and 'ORDER' settings from this plugin
		$sort_by_array = get_option( 'ms_data_display_field' );// SORT BY
		$sort_by = trim($sort_by_array[0]);

		$order_array = get_option( 'ms_data_display_order' );// ORDER
		$order = trim($order_array[0]);

		// Set up the basic query
		$query = 'SELECT * FROM Products';

		// If there's an existing setting for the $sort_by variable, add the ORDER BY section
		if($sort_by){
			$query .= ' ORDER BY ' . $sort_by;
		}

		// if there's a set value for $order, add it to the query last
		if($order){
			$query .= ' ' . $order;
		}	

		// Include the connection constants for the external database (EXTERNAL_DB, EXTERNAL_DB_USER, and EXTERNAL_DB_PASS
		include_once(dirname(__FILE__) . '/inc/ms-data-display-db.inc');

		// Get the data from an external database using a new instance of WordPress' built-in wpdb object for consistency
		$ProductsDB = new wpdb(EXTERNAL_DB_USER,EXTERNAL_DB_PASS,EXTERNAL_DB,'localhost');
		$ProductRows = $ProductsDB->get_results($query);
		$MarkUp_String = "<div class='ms225CF2DivTable'>";// Output the top row
		$MarkUp_String .= "<div class='ms225CF2DivTableBody'>";
		$MarkUp_String .= "<div class='ms225CF2DivTableRow'>";
		$MarkUp_String .= "<div class='ms225CF2DivTableCell'><strong>IMAGE</strong></div>";
		$MarkUp_String .= "<div class='ms225CF2DivTableCell'><strong>PRODUCT</strong></div>";
		$MarkUp_String .= "<div class='ms225CF2DivTableCell'><strong>PRICE</strong></div>";
		$MarkUp_String .= "<div class='ms225CF2DivTableCell'><strong>TYPE</strong></div>";
		$MarkUp_String .= "<div class='ms225CF2DivTableCell'><strong>POWER OUTPUT (Watts)</strong></div>";
		$MarkUp_String .= "</div>";
					
		//Loop through the records and display them in a div-based table
		foreach ($ProductRows as $Field){

			$MarkUp_String .= "<div class='ms225CF2DivTableRow'>";
			$MarkUp_String .= "<div class='ms225CF2DivTableCell'><img width='80px' height='80px' src='data:image/jpeg;base64,".base64_encode( $Field->Thumbnail )."' /></div>";
			$MarkUp_String .= "<div class='ms225CF2DivTableCell'>".$Field->ProductName."</div>";
			$MarkUp_String .= "<div class='ms225CF2DivTableCell'>$ ".money_format('%.2n',$Field->Price)."</div>";
			$MarkUp_String .= "<div class='ms225CF2DivTableCell'>".$Field->Type."</div>";
			$MarkUp_String .= "<div class='ms225CF2DivTableCell'>".$Field->PowerOut."</div>";
			$MarkUp_String .= "</div>";// End the row

		}//End foreach product

		// Close up the remaining open divs
		$MarkUp_String .= "</div></div>";	
		
		// Return the markup and data
		return $MarkUp_String;
		
	}//End get_data
	

    /////////////////////// ms225CF2_create_plugin_settings_page_link()
    ///////// Creates the admin settings page menu link 
    public function ms225CF2_create_plugin_settings_page_link() {

    	// Set up some variables for the Admin page
    	$page_title = 'MS Data Display - plugin settings';
    	$menu_title = 'MS Data Display'; // Without the underscore
    	$capability = 'manage_options';// Permissions level 
    	$slug = 'ms_data_display';// Identifies all the settings in the options table
    	$callback = array( $this, 'ms225CF2_plugin_settings_page_content' );
    	$icon = plugin_dir_url( __FILE__ ).'inc/msBlackWhiteIcon16.png';// The Icon that shows in the menu
    	$position = 100;// Essentially the bottom, by default. Use decimals to fine-tune
		
	// Add the admin menu page
    	add_menu_page( $page_title, $menu_title, $capability, $slug, $callback, $icon, $position );

    }//End create settings page


    /////////////////////// ms225CF2_plugin_settings_page_content()
    //////// Show the form and the Admin Notice if it's been updated
    public function ms225CF2_plugin_settings_page_content() {?>
    	<div class="wrap">
			<p><img src="<?php echo(plugin_dir_url( __FILE__ ).'inc/msLogo.png'); ?>" /></p>
			<h2><strong>MS Data Display - Simple Demonstration Plugin</strong></h2><hr />
			<ul>
				<li>Plugin Name: Mark Speciale - Tutorial Plugin: Data Display</li>
				<li>Plugin URI: <a href="https://markspeciale.com/custom-wordpress-plugins/" target="_blank">https://markspeciale.com/custom-wordpress-plugins/</a></li>
				<li>Description: Basic plugin example that displays data with shortcode and has a simple admin section.</li>
				<li>Version: 1.0.0</li>
				<li>Author: Mark Speciale</li>
				<li>Author URI: <a href="https://markspeciale.com" target="_blank">https://markspeciale.com</a></li>
				<li>License: GPL2</li>
				<li>Unique Prefix: ms225CF2</li>
				<li>Shortcode: ms-data-display-products</li>
			</ul>
			<?php
		
	    // If the form was just submitted, let them know it's been successful
            if ( isset( $_GET['settings-updated'] ) && $_GET['settings-updated'] ){//'settings-updated' is added after the form is processed in options.php
                  $this->ms225CF2_admin_notice();
            }
			?>
    		<form method="POST" action="options.php">
                <?php
                    settings_fields( 'ms_data_display' );
                    do_settings_sections( 'ms_data_display' );
                    submit_button();
                ?>
    		</form></div><?php

    }//End settings page content


    /////////////////////// ms225CF2_admin_notice()
    /////// Define the Admin Notice
    public function ms225CF2_admin_notice() {
        echo('<div class="notice notice-success is-dismissible"><p>Your settings have been updated!</p></div>');
    }//End admin notice
	

    //////////////////////// ms225CF2_setup_sections()
    ////// Set up the Admin page sections with WordPress
    public function ms225CF2_setup_sections() {

	// add_settings_section( string $id, string $title, callable $callback, string $page )
        add_settings_section( 'header_section', '<hr />Products Table - Sort By/Order Settings', array( $this, 'ms225CF2_section_callback' ), 'ms_data_display' );
        add_settings_section( 'body_section', '', array( $this, 'ms225CF2_section_callback' ), 'ms_data_display' );
        add_settings_section( 'footer_section', '', array( $this, 'ms225CF2_section_callback' ), 'ms_data_display' );		

    }//End setup sections
	

    //////////////////////// ms225CF2_section_callback()
    ////// Add initial content to the sections. The fields are in the body_section by default.
    public function ms225CF2_section_callback( $arguments ) {
    	switch( $arguments['id'] ){
    		case 'header_section':
    			echo 'Edit the "Sort By" and "Order" settings for this simple demonstration plugin.<br /><br />';
    			break;
    		case 'body_section':
    			echo "";
    			break;
    		case 'footer_section':
    			echo '<hr />&copy;' . date("Y") . ' Mark Speciale. Use as-is, no warranties. Open source GPL2 license. Enjoy :)';
    			break;
    	}
		
    }//End section callback
	

    //////////////////////// ms225CF2_setup_fields()
    ////// Set up the array of fields that will be displayed with do_settings_sections()
    public function ms225CF2_setup_fields() {
        $fields = array(

			// The "SORT BY:" drop-down
			array(
        		'uid' => 'ms_data_display_field',
        		'label' => 'SORT BY:',
        		'section' => 'body_section',
        		'type' => 'select',
				'helper' => 'Choose the field you want to sort by.',
				'supplemental' => 'Select one of the choices from the drop-down, above.',
        		'options' => array(
        			'ProductName' => 'ProductName',
        			'Type' => 'Type',
        			'Price' => 'Price',
        			'PowerOut' => 'PowerOut'
        		),
                'default' => array()// No default
        	),
			// The "ORDER:" drop-down
			array(
        		'uid' => 'ms_data_display_order',
        		'label' => 'ORDER:',
        		'section' => 'body_section',
        		'type' => 'select',
				'helper' => 'Choose the order.',
				'supplemental' => 'Select one of the choices from the drop-down, above.',
        		'options' => array(
        			'ASC' => 'ASC',
        			'DESC' => 'DESC'
        		),
                'default' => array()// No default
        	)
        ); //End construction of the $fields array
		
	// Loop through the $fields array and register them one-by-one with WordPress
    	foreach( $fields as $field ){
        	add_settings_field( $field['uid'], $field['label'], array( $this, 'ms225CF2_field_callback' ), 'ms_data_display', $field['section'], $field );
            register_setting( 'ms_data_display', $field['uid'] );
    	}
		
    }//End setup fields


    ////////////////////////////////// field_callback()
    // Returns the HTML markup for the fields
    public function ms225CF2_field_callback( $arguments ) {
        
	// Check for the field name in the db
	$existing_value_array = get_option( $arguments['uid'] );
		
	// If this field's not found in the db, use the empty array for this
        if( ! $existing_value_array ) {
            $existing_value_array = $arguments['default'];
        }

	// These are both select fields, so we need to process the options for each
	$attributes = '';
	$options_markup = '';

	// Loop through the options and format the markup for the "options" part of the form element.
	// If it matches the existing value if there is one, it adds "selected='selected'" to the markup
	foreach( $arguments['options'] as $key => $label ){
		$options_markup .= sprintf( '<option value="%s" %s>%s</option>', $key, selected( $existing_value_array[ array_search( $key, $existing_value_array, true ) ], $key, false ), $label );
	}

	// Now output the formatted HTML for the form field.
	printf( '<select name="%1$s[]" id="%1$s" %2$s>%3$s</select>', $arguments['uid'], $attributes, $options_markup );

	// The helper text to the right...
        if( $helper = $arguments['helper'] ){
            printf( '<span class="helper"> %s</span>', $helper );
        }

	// ...and the supplemental text below the field in its own paragraph
        if( $supplimental = $arguments['supplimental'] ){
            printf( '<p class="description">%s</p>', $supplimental );
        }

    }//End field callback
	

    ///////////////////// ms225CF2_data_display_uninstall()
    // Cleans up when we uninstall the plugin
    public function ms225CF2_data_display_uninstall(){

	// Access the built-in WP database
	global $wpdb;

	//Remove all the now unused shortcodes from all the pages
	add_shortcode( 'ms-data-display-products', '__return_false' );

	// Remove the settings from the built-in WP options table
	$DeleteFieldSetting = $wpdb->delete( $wpdb->options, "'option_name' LIKE '%ms_data_display_field%'" );
	$DeleteOrderSetting = $wpdb->delete( $wpdb->options, "'option_name' LIKE '%ms_data_display_order%'" );

	}//End uninstall

}//End the main class (ms225CF2_data_display())


////////////////////////// Instantiate the class
$ms225CF2_data_display = new ms225CF2_data_display();

// Set up what happens when this plugin gets uninstalled (we need to remove the options from the built-in WP table)
register_uninstall_hook( __FILE__, array($ms225CF2_data_display, 'ms225CF2_data_display_uninstall'));

inc/ms-data-display-db.inc
<?php
////////// Keep the hackers out
if ( ! defined( 'ABSPATH' ) ) {// WordPress defines ABSPATH in the normal 'loading' stage
	exit; // Shut it down if it's not a legit call
}

// Define connection vars for the external database
define('EXTERNAL_DB', 'YOUR_DATABASE_NAME');
define('EXTERNAL_DB_USER', 'YOUR_DATABASE_USER');
define('EXTERNAL_DB_PASS', 'YOUR_DATABASE_USER_PASSWORD');
css/ms-data-display.css
/* CSS Document */
.ms225CF2DivTable{
	display: table;
	width: 100%;
	text-align: center;
}
.ms225CF2DivTableRow {
	display: table-row;
}
.ms225CF2DivTableHeading {
	background-color: #EEE;
	display: table-header-group;
}
.ms225CF2DivTableCell, .ms225CF2DivTableHead {
	border: 1px solid #DDDDDD;
	display: table-cell;
	padding: 2px 4px;
	vertical-align: middle;
}
.ms225CF2DivTableHeading {
	background-color: #EEE;
	display: table-header-group;
	font-weight: bold;
}
.ms225CF2DivTableFoot {
	background-color: #EEE;
	display: table-footer-group;
	font-weight: bold;
}
.ms225CF2DivTableBody {
	display: table-row-group;
}

Download Mark's Resume

Download a PDF copy of Mark’s current resume!

A Million Ways to Make Things Happen For You

Mark maintains a large and ever-growing skill set and also won’t hesitate to bring in other qualified experts to fulfill your needs, where required.

By leveraging the vast resources of the Internet to find and use the most current, stable, relevant technologies, he’s better able to find solutions, solve problems and exceed requirements.