wordpress plugin

Today, we are going to see how to create a very simple WordPress plugin for any web app that needs to insert a piece of code to your site.

Credits: https://unsplash.com/photos/I8OhOu-wLO4

To follow this tutorial, you need some knowledge of these basics:

  • PHP and OOP
  • JavaScript (we’ll use jQuery and Ajax)
  • WordPress development (as most functions are from the WordPress core).

12 Concepts That Will Level Up Your JavaScript Skills

You can find a working result of this tutorial on this Github repository.

These web apps could be anything, like CrazyEggFreshbookGoogle AnalyticsFacebook Pixel, or Feedier. Why? They all need to inject some HTML / JavaScript code to your site for various purposes.

This “code” is always parametrized with variables, and is usually a pain for the site owner. This is because you need to edit the theme’s templates. So, how about we create a plugin to do that for us? Okay, let’s do it!

Step 1: Find your web app

The goal of this tutorial is to create a WordPress plugin that adds a WordPress admin page. Plus, we’ll also add some settings to configure the app’s in-site widget and inject the HTML / JS code in our web page automatically. Nothing fancy, just something that works fine.

Please note: we do need a web application for this tutorial. We will use Feedier for this example. However, if you have another web application that you’d like to use in this tutorial, please do. Just rename anything named “feedier” with your app’s name and adapt the settings to what that app needs. Most of them will give you a snippet to add to your site in order to make it work.

Here’s a quick briefing of Feedier if you’ve never heard of it:

  • It’s a feedback collector tool, using surveys to understand your users
  • It’s very flexible
  • It’s free!
  • Has a good API (very important here)
  • Has an in-site widget (very important here)
  • Lets you reward your customers
  • Lets you create conditional questions
  • Has a complete analytic report dashboard
  • Lets you manage feedback individually

Here is the widget we want to add automatically:

Preview of the widget on woffice.io

If you signed up for Feedier, then you can simply find the code in the Share tab of your survey:

Grab the snippet from feedier.com

Step 2: Setup our plugin and its architecture

WordPress plugin are by design very simple. Our plugin will only need two files.

  • feedier.php: main plugin’s PHP file.
  • assets/js/admin.js: JavaScript script to save the options using Ajax.

You can create a new “feedier” directory (or name of your web app) in your wp-content/plugins/ folder.

The most important file will be the plugin’s feedier.php class. Here is its structure:

<?php
/**
 * Plugin Name:       Feedier
 * Description:       Smart surveys start now!
 * Version:           1.0.0
 * Author:            Alkaweb
 * Author URI:        https://alka-web.com
 * Text Domain:       alkaweb
 * License:           GPL-2.0+
 * License URI:       http://www.gnu.org/licenses/gpl-2.0.txt
 * GitHub Plugin URI: https://github.com/2Fwebd/feedier-wordpress
 */
 
/*
 * Plugin constants
 */
if(!defined('FEEDIER_URL'))
	define('FEEDIER_URL', plugin_dir_url( __FILE__ ));
if(!defined('FEEDIER_PATH'))
	define('FEEDIER_PATH', plugin_dir_path( __FILE__ ));
 
/*
 * Main class
 */
/**
 * Class Feedier
 *
 * This class creates the option page and add the web app script
 */
class Feedier
{
 
    /**
     * Feedier constructor.
     *
     * The main plugin actions registered for WordPress
     */
    public function __construct()
    {
 
    }
 
}
 
/*
 * Starts our plugin class, easy!
 */
new Feedier();

We are doing a few things here:

  • Declaring our plugin using the header comments
  • Defining a few handy constants to be able to find the plugin’s URL and PATH easily
  • Declaring our plugin class that will contain everything we need in this plugin. We just need a constructor method for now.

You should already see the plugin in your Plugins page, even though it’s not doing anything yet:

Activate the plugin from WordPress admin panel

Step 3: Create our admin page

For this part, we will add a new Feedier admin page to our WordPress site and dynamically fetch our surveys from Feedier’s API.

In our class’ constructor, let’s register three new actions which are required to add an admin page on WordPress:

<?php
/**
 * Feedier constructor.
 *
 * The main plugin actions registered for WordPress
 */
public function __construct()
{
 
	// Admin page calls:
	add_action( 'admin_menu', array( $this, 'addAdminMenu' ) );
	add_action( 'wp_ajax_store_admin_data', array( $this, 'storeAdminData' ) );
	add_action( 'admin_enqueue_scripts', array( $this, 'addAdminScripts' ) );
 
}
  • addAdminMenu will add a new page in the WordPress left menu. There will be also a callback to another method containing the page’s content.
  • storeAdminData will be called whenever the user clicks the “Save settings” button.
  • addAdminScripts will register a new JavaScript file to our WordPress admin in order to save the form’s data. But it also exchanges some variables between the PHP side and JavaScript side.

The first step is very easy. We just register the page, like this:

<?php
/**
 * Adds the Feedier label to the WordPress Admin Sidebar Menu
 */
public function addAdminMenu()
{
    add_menu_page(
	__( 'Feedier', 'feedier' ),
	__( 'Feedier', 'feedier' ),
	'manage_options',
	'feedier',
	array($this, 'adminLayout'),
	''
     );
}

As you can see, we use WordPress localization functions for all strings. Note that the

array($this, ‘adminLayout’)

is where we call another method containing the page’s content. The form needs to be adapted to your web app.

Here, we first need to get the public and private Feedier API keys. Once saved, we are going to use the private key to dynamically retrieve our surveys. Whenever we get the surveys and not an API error, we display some new options to configure the widget.

<?php
/**
 * Outputs the Admin Dashboard layout containing the form with all its options
 *
 * @return void
 */
public function adminLayout()
{
 
	$data = $this->getData();
 
	$surveys = $this->getSurveys($data['private_key']);
 
	?>
 
	<div class="wrap">
	    <h3><?php _e('Feedier API Settings', 'feedier'); ?></h3>
 
            <p>
	        <?php _e('You can get your Feedier API settings from your <b>Integrations</b> page.', 'feedier'); ?>
            </p>
 
            <hr>
 
            <form id="feedier-admin-form">
 
		<table class="form-table">
                    <tbody>
                        <tr>
                            <td scope="row">
                                <label><?php _e( 'Public key', 'feedier' ); ?></label>
                            </td>
                            <td>
                                <input name="feedier_public_key"
                                       id="feedier_public_key"
                                       class="regular-text"
                                       value="<?php echo (isset($data['public_key'])) ? $data['public_key'] : ''; ?>"/>
                            </td>
                        </tr>
                        <tr>
                            <td scope="row">
                                <label><?php _e( 'Private key', 'feedier' ); ?></label>
                            </td>
                            <td>
                                <input name="feedier_private_key"
                                       id="feedier_private_key"
                                       class="regular-text"
                                       value="<?php echo (isset($data['private_key'])) ? $data['private_key'] : ''; ?>"/>
                            </td>
                        </tr>
                        <tr>
                            <td>
                                <hr>
                                <h4><?php _e( 'Widget options', 'feedier' ); ?></h4>
                            </td>
                        </tr>
 
                        <?php if (!empty($data['private_key']) && !empty($data['public_key'])): ?>
 
                            <?php
                            // if we don't even have a response from the API
                            if (empty($surveys)) : ?>
 
                                <tr>
                                    <td>
	                                    <p class="notice notice-error">
                                            <?php _e( 'An error happened on the WordPress side. Make sure your server allows remote calls.', 'feedier' ); ?>
                                        </p>
                                    </td>
                                </tr>
 
                            <?php
                            // If we have an error returned by the API
                            elseif (isset($surveys['error'])): ?>
 
                                <tr>
                                    <td>
                                        <p class="notice notice-error">
                                            <?php echo $surveys['error']; ?>
                                        </p>
                                    </td>
                                </tr>
 
                            <?php
                            // If the surveys were returned
                            else: ?>
 
                                <tr>
                                    <td>
                                        <p class="notice notice-success">
	                                        <?php _e( 'The API connection is established!', 'feedier' ); ?>
                                        </p>
 
                                        <div>
                                            <label><?php _e( 'Choose a survey', 'feedier' ); ?></label>
                                        </div>
                                        <select name="feedier_widget_carrier_id"
                                                id="feedier_widget_carrier_id">
                                            <?php
                                            // We loop through the surveys
                                            foreach ($surveys['data'] as $survey) : ?>
                                                <?php
                                                // We also only keep the id -> x from the carrier_x returned by the API
                                                $survey['id'] = substr($survey['id'], 8); ?>
                                                <option value="<?php echo $survey['id']; ?>" <?php echo ($survey['id'] === $data['widget_carrier_id']) ? 'selected' : '' ?>>
                                                    <?php echo $survey['name']; ?>
                                                </option>
                                            <?php endforeach; ?>
                                        </select>
                                        <hr>
                                </tr>
 
                                <tr>
                                    <td>
                                        <div class="label-holder">
                                            <label><?php _e( 'Display probability (from 0 to 100)', 'feedier' ); ?></label>
                                        </div>
                                        <input name="feedier_widget_display_probability"
                                               id="feedier_widget_display_probability"
                                               class="regular-text"
                                               value="<?php echo (isset($data['widget_display_probability'])) ? $data['widget_display_probability'] : '100'; ?>"/>
                                    </td>
                                    <td>
                                        <div class="label-holder">
                                            <label><?php _e( 'Shaking effect (shake after 10s without click)', 'feedier' ); ?></label>
                                        </div>
                                        <input name="feedier_widget_shake"
                                               id="feedier_widget_shake"
                                               type="checkbox"
                                            <?php echo (isset($data['widget_shake']) && $data['widget_shake']) ? 'checked' : ''; ?>/>
                                    </td>
                                    <td>
                                        <div class="label-holder">
                                            <label><?php _e( 'Position', 'feedier' ); ?></label>
                                        </div>
                                        <select name="feedier_widget_position"
                                                id="feedier_widget_position">
                                            <option value="left" <?php echo (!isset($data['widget_position']) || (isset($data['widget_position']) && $data['widget_position'] === 'left')) ? 'checked' : ''; ?>>
                                                <?php _e( 'Left side', 'feedier' ); ?>
                                            </option>
                                            <option value="right" <?php echo (isset($data['widget_position']) && $data['widget_position'] === 'right') ? 'checked' : ''; ?>>
                                                <?php _e( 'Right side', 'feedier' ); ?>
                                            </option>
                                        </select>
                                    </td>
                                </tr>
 
                            <?php endif; ?>
 
                        <?php else: ?>
 
                            <tr>
                                <td>
                                    <p>Please fill up your API keys to see the widget options.</p>
                                </td>
                            </tr>
 
                        <?php endif; ?>
 
                        <tr>
                            <td colspan="2">
                                <button class="button button-primary" id="feedier-admin-save" type="submit"><?php _e( 'Save', 'feedier' ); ?></button>
                            </td>
                        </tr>
                    </tbody>
                </table>
 
            </form>
 
	</div>
 
	<?php
 
}

At the beginning of this method, you can see that we are first getting the saved data with:

$data = $this->getData();

And getting the surveys from the Feedier API:

$surveys = $this->getSurveys($data[‘private_key’]);

So let’s declare the first one:

/**
 * The option name
 *
 * @var string
 */
private $option_name = 'feedier_data';
 
/**
 * Returns the saved options data as an array
 *
 * @return array
 */
private function getData() {
    return get_option($this->option_name, array());
}

This function just reads our plugin’s option and gives us an array back so we can save multiple values in the same option.

To get the second method working, we need the Feedier private key. This depends on the first one to access this key saved in the option:

<?php
/**
 * Make an API call to the Feedier API and returns the response
 *
 * @param string $private_key
 * @return array
 */
private function getSurveys($private_key)
{
 
    $data = array();
    $response = wp_remote_get('https://api.feedier.com/v1/carriers/?api_key='. $private_key);
 
    if (is_array($response) && !is_wp_error($response)) {
	$data = json_decode($response['body'], true);
    }
 
    return $data;
 
}

The Feedier API is documented here, so you can see what you will get in the response.

At this moment, we have a complete new Admin page. But nothing happens when we click on the save button, because there is no saving mechanism — yet.

Good enough, let’s save our data!

As mentioned before, we will save our data using AJAX. Therefore, we need to register a new JavaScript file and exchange data using the wp_localize_script() function:

<?php
/** 
 * The security nonce 
 *
 * @var string 
 */
private $_nonce = 'feedier_admin';

/**
 * Adds Admin Scripts for the Ajax call
 */
public function addAdminScripts()
{

      wp_enqueue_script('feedier-admin', FEEDIER_URL. '/assets/js/admin.js', array(), 1.0);

      $admin_options = array(
	 'ajax_url' => admin_url( 'admin-ajax.php' ),
	 '_nonce'   => wp_create_nonce( $this->_nonce ),
      );

      wp_localize_script('feedier-admin', 'feedier_exchanger', $admin_options);

}

/** 
 * The security nonce 
 *
 * @var string 
 */
private $_nonce = 'feedier_admin';
 
/**
 * Adds Admin Scripts for the Ajax call
 */
public function addAdminScripts()
{
 
      wp_enqueue_script('feedier-admin', FEEDIER_URL. '/assets/js/admin.js', array(), 1.0);
 
      $admin_options = array(
	 'ajax_url' => admin_url( 'admin-ajax.php' ),
	 '_nonce'   => wp_create_nonce( $this->_nonce ),
      );
 
      wp_localize_script('feedier-admin', 'feedier_exchanger', $admin_options);
 
}

We also need to add a new file /assets/js/admin.js. That will simply make an Ajax call, and WordPress will automatically route the request correctly to the right method (already done in the constructor). You can read more about how WordPress smartly handles AJAX requests here.

/**
 * Feedier plugin Saving process
 */
jQuery( document ).ready( function () {
 
    jQuery( document ).on( 'submit', '#feedier-admin-form', function ( e ) {
 
        e.preventDefault();
 
        // We inject some extra fields required for the security
        jQuery(this).append('<input type="hidden" name="action" value="store_admin_data" />');
        jQuery(this).append('<input type="hidden" name="security" value="'+ feedier_exchanger._nonce +'" />');
 
        // We make our call
        jQuery.ajax( {
            url: feedier_exchanger.ajax_url,
            type: 'post',
            data: jQuery(this).serialize(),
            success: function ( response ) {
                alert(response);
            }
        } );
 
    } );
 
} );

At this very moment, we can click the save button and the above script will make an HTTP POST request to WordPress. We also append an action parameter containing: store_admin_data (which we declared at the beginning at this part in the constructor):

add_action( ‘wp_ajax_store_admin_data’, array( $this, ‘storeAdminData’ ) );

The method storeAdminData will receive the POST request and save the values we need in our WordPress option.

<?php
/**
 * Callback for the Ajax request
 *
 * Updates the options data
 *
 * @return void
 */
public function storeAdminData()
{
 
    if (wp_verify_nonce($_POST['security'], $this->_nonce ) === false)
	die('Invalid Request!');
 
    $data = $this->getData();
    
    foreach ($_POST as $field=>$value) {
 
        if (substr($field, 0, 8) !== "feedier_" || empty($value))
	   continue;
 
        // We remove the feedier_ prefix to clean things up
	$field = substr($field, 8);
 
        $data[$field] = $value;
 
    }
 
    update_option($this->option_name, $data);
 
    echo __('Saved!', 'feedier');
    die();
 
}

A few notes on the above method:

  • We use a “WordPress nonce” to handle the security and make sure this is coming from the website and not a hacker faking the request.
  • We identify the fields we need to save using a “feedier_” prefix. Once received, we loop through all the $_POST data and only save those fields. We also remove the prefix before saving every field.

That’s it for the saving process. When we click save, we can see a POST request and our data being saved on the database within the wp_optionstable.

Perfect, we are done with the admin page.

Step 4: Insert the dynamic code automatically into our pages

Now that we have our options saved, we can create a dynamic widget that will depend on the options set by the user though our admin page. We already know what the web app expects from us.

Something like:

<div class=”feedier-widget” data-type=”engager” data-position=”right” data-carrier-id=”x” data-key=”xxxxxxxxxxxxxxxxx”></div>
<! — Include this line only one time, also if you have multiple widgets on the current page →
<script src=”https://feedier.com/js/widgets/widgets.min.js" type=”text/javascript” async></script>

Thus, the first thing we want to do is to create a new method to our plugin that will print this code depending on the variables set by the user. So, using the architecture we already set up in the last part:

<?php
/**
 * Add the web app code to the page's footer
 *
 * This contains the widget markup used by the web app and the widget API call on the frontend
 * We use the options saved from the admin page
 *
 * @return void
 */
public function addFooterCode()
{

   $data = $this->getData();

   // Only if the survey id is selected and saved
   if(empty($data) || !isset($data['widget_carrier_id']))
      return;

   ?>
   <div class="feedier-widget"
             data-type="engager"
             data-position="<?php echo (isset($data['widget_position'])) ? $data['widget_position'] : 'left'; ?>"
             data-display-probability="<?php echo (isset($data['widget_display_probability'])) ? $data['widget_display_probability'] : '100'; ?>"
             data-shake="<?php echo (isset($data['widget_shake'])) ? $data['widget_shake'] : 'false'; ?>"
             data-carrier-id="<?php echo (isset($data['widget_carrier_id'])) ? $data['widget_carrier_id'] : '0'; ?>"
             data-key="<?php echo (isset($data['public_key'])) ? $data['public_key'] : '0'; ?>"></div>

   <script src="https://feedier.com/js/widgets/widgets.min.js" type="text/javascript" async></script>
   <?php

}

/**
 * Add the web app code to the page's footer
 *
 * This contains the widget markup used by the web app and the widget API call on the frontend
 * We use the options saved from the admin page
 *
 * @return void
 */
public function addFooterCode()
{
 
   $data = $this->getData();
 
   // Only if the survey id is selected and saved
   if(empty($data) || !isset($data['widget_carrier_id']))
      return;
 
   ?>
   <div class="feedier-widget"
             data-type="engager"
             data-position="<?php echo (isset($data['widget_position'])) ? $data['widget_position'] : 'left'; ?>"
             data-display-probability="<?php echo (isset($data['widget_display_probability'])) ? $data['widget_display_probability'] : '100'; ?>"
             data-shake="<?php echo (isset($data['widget_shake'])) ? $data['widget_shake'] : 'false'; ?>"
             data-carrier-id="<?php echo (isset($data['widget_carrier_id'])) ? $data['widget_carrier_id'] : '0'; ?>"
             data-key="<?php echo (isset($data['public_key'])) ? $data['public_key'] : '0'; ?>"></div>
 
   <script src="https://feedier.com/js/widgets/widgets.min.js" type="text/javascript" async></script>
   <?php
 
}

Now, we just need to call this function on every page load to add it at the bottom of the page. To do this, we’ll hook our method to the wp_footeraction. By registering a new action into our class’ constructor:

<?php
/**
 * Feedier constructor.
 *
 * The main plugin actions registered for WordPress
 */
public function __construct()
{
 
    add_action('wp_footer', array( $this, 'addFooterCode'));
 
    ... what we had before ...
 
}

That’s it!

Any questions, feedback, or ideas? Let me know in the comments!

You can find a working version of this tutorial on this Github repository.

Note that this is first version of the plugin, and many things can be improved. I’m open to suggestions and improvements. 😎

 

Source: Freecode Camp