blog-img
blog-img
blog-img

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.

To follow this tutorial, you need basics:

  • PHP and Object Oriented Programming
  • JavaScript, we’ll use jQuery and Ajax
  • WordPress development, most functions are from the WordPress core.

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

These web apps could be CrazyEgg, Freshbook, Google Analytic, Facebook 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 usually is a pain for the site owner because he needs 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 – Feedier Example

The goal of this tutorial is to create a WordPress plugin that adds a WordPress admin page.

Plus, 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.

So, we do need a web application for this tutorial. We will use for this example Feedier.

However, if you do have the other web application to apply this tutorial, please do. Just rename the “feedier” with your app and adapt the settings to what this app needs. 

Most of them will give you a snippet to add to your site in order to work.

Quick briefing of Feedier if you 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:

Feedier Widget Preview

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

Feedier Preview Sharing Method

Step 2: Setup Our Plugin and Its Architecture

WordPress plugins 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.

Directory Architecture

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:

WordPress plugin activation

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 the Feedier’s API.

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

/**
 * 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 also exchanges some variables between the PHP side and JavaScript side.

The first one is very easy, we just register the page:

/**
 * 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.

/**
 * 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.

Obviously, to get the second method working, we need the Feedier private key, which thus depends on the first one to access this key saved in the option:

/**
 * 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:

/**
 * 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, WordPress will automatically route correctly the request 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.

/**
 * 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 the save process we can see a POST request and our data is being saved on the database within the wp_options table.

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 through our admin page.

We already now what the web app expect 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. While using the architecture we already set up in the last part:

/**
 * 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_footer action.

By registering a new action into our class’ constructor:

/**
 * 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 question, feedback, idea? Let me know in the comment!

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

Note that this is the first version of the plugin, many things can be improved.

Open to suggestions and improvements.

blog-img
blog-img