Integrate EMP EDD Plugin Updater with Ajax

You sell plugins on EMP (Elegant Marketplace) and don’t use the many advantages of the “EDD_SL_Plugin_Updater” class yet? Then this post is surely interesting for you. In this post you will see how to create an input field for the license key directly below the plugin entry in the plugin index and how to handle activation and deactivation using Ajax.

If you are not yet sure about the advantages of the “EDD_SL_Plugin_Updater” class, here are some arguments:

  • You can handle license keys with it.
  • It’s very easy to integrate into your plugins
  • You can manage the display in the update info via readme.txt
  • You can integrate images into the update info as you know it from wp.org
  • It is compatible with a fixed activation limit and more

I explain the process here with the popular plugin “Divi – Filterable Blog Module”, but I provide a more general version via Github at the end of the post. This is what the input field looks like after it has been implemented:

Preview for the input field

To add the field behind the plugin entry, use the hook “‘after_plugin_row'”. We hook into these and check by means of the first parameter whether it is our plugin. In my case the following string is passed there: “divi-filterable-blog-module/divi-filterable-blog-module.php”

Since the domain was defined with “divi-filterable-blog-module”, you can easily check with it:

<?php

// check in "current_screen" if we're in the plugin index
add_action( 'current_screen', function( $screen )
{

  if ( 'plugins' == $screen->base ) :

    // DFBM() references a singletone object in which all constant values can also be retrieved
    $slug = DFBM()->prefix() . '-plugins';

    // If there is no license key, show the field
    if ( ! $this->licenseKey )
      add_action( 'after_plugin_row', [ $this, 'licenseField' ] );


    else
      add_filter( 'plugin_action_links_' . plugin_basename( DFBM()->file() ), [ $this, 'removeLink' ] );

    // A JS file to execute the Ajax instructions later
    ( new dfbmControllerEnqueue )->enqueueScript( $slug, [ 'jquery' ] );

    wp_localize_script( $slug, DFBM()->prefix() . 'PHP', $this->localize() );

  endif;

}); // end add_action

// Show the field

/**
* Add the license field
*
* @since 1.0.10
*/
public function licenseField( $plugin )
{

  // $plugin == "divi-filterable-blog-module/divi-filterable-blog-module.php"
  if ( false !== strpos( $plugin, DFBM()->domain() ) ) :

    $prefix = DFBM()->prefix();
    $text01 = esc_html__( 'Enter Your License Key for future updates', DFBM()->domain() );
    $text02 = esc_html__( 'Submit', DFBM()->domain() );
    $text03 = esc_html__( 'You can get it ', DFBM()->domain() );
    $text04 = esc_html__( 'here.', DFBM()->domain() );

    echo <<<FORM
      </tr><tr class="plugin-update-tr">
        <td colspan="3" class="plugin-update" style="border-left: 4px solid #00a0d2">
          <div class="update-message" style="margin-top:5px;margin-bottom:5px;">
            <form action="" method="POST">
              <input type="hidden" name="{$prefix}_license" value="license_key">
              <div class="about-text" style="display:inline-block">
                <div>
                  <input id="{$prefix}-license-field" type="text" class="regular-text" id="license_key" value="" name="license_key" placeholder="{$text01}" style="min-height:30px;">
                </div>
              </div>
              <div style="display:inline-block">
                <button id="{$prefix}-license-submit" class="button button-primary" type="submit" value="Submit" name="_submit" style="margin-top:-6px;border-radius:0">{$text02}</button>
              </div>
            </form>
            <div style="display:inline-block;margin-left:5px">
              <span style="margin-right:5px">-</span><span>{$text03}</span><a href="https://elegantmarketplace.com/checkout/purchase-history/" target="_blank">{$text04}</a>
            </div>
          </div>
        </td>
FORM;

  endif;

} // end licenseField

/**
 * Link to remove the license
 *
 * @since 1.0.10
 */
public function removeLink( $items )
{

  $items[] = '<a id="' . DFBM()->prefix() . '-remove-license' . '" href="#">' . esc_html__( 'Remove License', DFBM()->domain() ) . '</a>';

  return $items;

} // end removeLink

 

Now the input field has already been inserted

And you’ve probably noticed the last method: “removeLink”. This is used if the user wants to remove his license for a page. After entering a license key, it looks like this:

Setting the hooks for the Ajax requests

Now we have implemented the input field and the remove link, taking into account whether a license key is available or not. Now we need the appropriate hooks to accept the Ajax requests:

<?php

if ( $this->licenseKey ) :

  $this->args->args['license'] = $this->licenseKey;

  add_action( 'wp_ajax_' . DFBM()->prefix() . '_deactivate', [ $this, 'deactivateLicense' ] );

else :

  add_action( 'wp_ajax_' . DFBM()->prefix() . '_activate', [ $this, 'activateLicense' ] );

endif;

With this we can now receive the requests via the JavaScript events and trigger the activation or deactivation of the license:

(function($){$(function(){

  class dfbmUpdater
  {

    setProperties()
    {

      // Set the color values for the user feedback and accept the values from the localization
      this.red = '#fff0f0';
      this.green = '#f1fff0';
      this.localize = dfbmPHP;

    } // end setProperties

      constructor()
      {

          this.setProperties();

          this.bindEvents();

      } // end constructor

      // This colors the input field according to the response
      // and inserts the corresponding feedback as placeholder
    resp( color, message )
    {

      return this.field
            .css( 'background', color )
            .attr( 'value', '' )
            .attr( 'placeholder', message );

    }

    // The handler for Ajax requests
    ajax( action, key, self, that = this )
    {

      let data =
      {

        nonce   : self.localize.nonce,
        action  : action,
        license : key,

      }

      data[action] = true;

      $.ajax(
      {

        data 	 : data,
        type     : 'post',
        dataType : 'json',
        url      : self.localize.url,

        success: function( data )
        {

          if ( self.localize.prefix + '_activate' == action )
          {

            if ( 'success' in data )
            {

              self.resp.apply( that, [ self.green, data.success ] );

              setTimeout( () =>
              {

                $(that).closest( 'div.update-message' ).slideUp( 800, function()
                {

                  $(this).closest( 'tr' ).remove();

                  location.reload();

                });
              }, 3000 );
            }

            else
              self.resp.apply( that, [ self.red, data.failed ] );

          }

          else
          {

            if ( 'delete' in data )
            {

              alert( data.delete );

              location.reload();

            }

            else
              alert( data.failed );

          }
        },

        error: function( data )
        {

          alert( self.localize.error );

          location.reload();

            }
      });
    } // end ajax

      bindEvents( self = this )
      {

      	// The Event Handler for license activation (Submit button)
      $( '#' + self.localize.prefix + '-license-submit' ).on( 'click', function( e )
      {

        e.preventDefault();
        e.stopImmediatePropagation();

        this.field = $( '#' + self.localize.prefix + '-license-field' );
        this.val   = this.field.val();

        // The EMP license keys are all 32 characters long
        if ( this.val && 32 == this.val.length )
        {

          // Valid request
          self.ajax.apply( this, [ self.localize.prefix + '_activate', this.val, self ] );

        }

        else // Invalid request
          self.resp.apply( this, [ self.red, self.localize.valid, self ] );

      });

      // The Event Handler for license deactivation (Remove link)
      $( '#' + self.localize.prefix + '-remove-license' ).on( 'click', function( e )
      {

        e.preventDefault();

        self.ajax.apply( this, [ self.localize.prefix + '_deactivate', self.localize.key, self ] );

      });

      } // end bindEvents
  } // end class dfbmUpdater

  new dfbmUpdater;

});}(jQuery));

This is what happens when an incorrect key is entered:

A red field if an incorrect key is entered

This is what it looks like when the license has been accepted. The field then stops for 3 seconds, after which it is hidden and removed:

The field if the license has been accepted

If no license key has been entered and an update is available, it cannot be performed:

Updates are not possible without a license key

If you set the meta data in the download settings using a readme.txt file and place banners, the update info looks quite similar to the original wp.org plugins:

The plugin updates info panel

The activation and deactivation methods behave mainly like those from the EDD demo files, but have been adapted to handle the Ajax requests. The whole class looks like this:

<?php
/**
 * Divi - Filterable Blog Module - Update Initialize
 *
 * @since	1.0.0
 */
if ( ! class_exists( 'dfbmUpdateInitialize' ) )
{

  class dfbmUpdateInitialize
  {

    /**
     * Define properties
     *
     * @since	1.0.10
     */
    private $args;

    private $licenseKey;

    /**
     * Constructor
     *
     * @since	1.0.0
     */
    public function __construct()
    {

      /**
       * Initialize
       *
       * @since 1.0.10
       */
      if ( is_admin() ) // We just need it in the admin area
        $this->initialize();

    } // end constructor

    /**
     * Initialize
     *
     * @since 1.0.10
     */
    public function initialize()
    {

      // Get the args for the EDD Updater
      $this->args = DFBM()->updater();

      // check if a license exist
      $this->licenseKey = ( new dfbmModelGet )->option( DFBM()->prefix() . '_license_key' );

      // Register the Ajax request handlers
      if ( $this->licenseKey ) :

        // Add the license key to the args
        $this->args->args['license'] = $this->licenseKey;

        add_action( 'wp_ajax_' . DFBM()->prefix() . '_deactivate', [ $this, 'deactivateLicense' ] );

      else :

        add_action( 'wp_ajax_' . DFBM()->prefix() . '_activate', [ $this, 'activateLicense' ] );

      endif;

      // Initiate the updater
      add_action( 'admin_init', [ $this, 'updater' ], 0 );

      // Just add the input field and remove link in the plugin index
      add_action( 'current_screen', function( $screen )
      {

        if ( 'plugins' == $screen->base ) :

          $slug = DFBM()->prefix() . '-plugins';

          if ( ! $this->licenseKey )
            add_action( 'after_plugin_row', [ $this, 'licenseField' ] );


          else
            add_filter( 'plugin_action_links_' . plugin_basename( DFBM()->file() ), [ $this, 'removeLink' ] );

          // Enqueue the script to handle the Ajax requests
          ( new dfbmControllerEnqueue )->enqueueScript( $slug, [ 'jquery' ] );

          // Localize the script
          wp_localize_script( $slug, DFBM()->prefix() . 'PHP', $this->localize() );

        endif;

      }); // end add_action
    } // end initialize

    /**
     * Data for Localization
     *
     * @since 1.0.10
     */
    public function localize()
    {

      return
      [

        'prefix' => DFBM()->prefix(),
        'error'  => $this->getMessage()->error,
        'url'    => admin_url( 'admin-ajax.php' ),
        'key'    => $this->licenseKey ? $this->licenseKey : '',
        'nonce'  => wp_create_nonce( DFBM()->prefix() . "-nonce-value" ) ,
        'valid'  => esc_html__( "Insert a valid key.", DFBM()->domain() ),

      ];

    } // end localize

    /**
     * Instantiate the updater class
     *
     * @since 1.0.10
     */
    public function updater()
    {

      if ( ! class_exists( 'EDD_SL_Plugin_Updater' ) )
        include( dirname( __FILE__ ) . '/EDD_SL_Plugin_Updater.php' );

      // Initiate the updater class
      new EDD_SL_Plugin_Updater( $this->args->url, $this->args->file, $this->args->args );

    } // end updater

    /**
     * Add the license field
     *
     * @since 1.0.10
     */
    public function licenseField( $plugin )
    {

      if ( false !== strpos( $plugin, DFBM()->domain() ) ) :

        $prefix = DFBM()->prefix();
        $text01 = esc_html__( 'Enter Your License Key for future updates', DFBM()->domain() );
        $text02 = esc_html__( 'Submit', DFBM()->domain() );
        $text03 = esc_html__( 'You can get it ', DFBM()->domain() );
        $text04 = esc_html__( 'here.', DFBM()->domain() );

        echo <<<FORM
          </tr><tr class="plugin-update-tr">
            <td colspan="3" class="plugin-update" style="border-left: 4px solid #00a0d2">
              <div class="update-message" style="margin-top:5px;margin-bottom:5px;">
                <form action="" method="POST">
                  <input type="hidden" name="{$prefix}_license" value="license_key">
                  <div class="about-text" style="display:inline-block">
                    <div>
                      <input id="{$prefix}-license-field" type="text" class="regular-text" id="license_key" value="" name="license_key" placeholder="{$text01}" style="min-height:30px;">
                    </div>
                  </div>
                  <div style="display:inline-block">
                    <button id="{$prefix}-license-submit" class="button button-primary" type="submit" value="Submit" name="_submit" style="margin-top:-6px;border-radius:0">{$text02}</button>
                  </div>
                </form>
                <div style="display:inline-block;margin-left:5px">
                  <span style="margin-right:5px">-</span><span>{$text03}</span><a href="https://elegantmarketplace.com/checkout/purchase-history/" target="_blank">{$text04}</a>
                </div>
              </div>
            </td>
FORM;

      endif;

    } // end licenseField

    /**
     * Link to remove the license
     *
     * @since 1.0.10
     */
    public function removeLink( $items )
    {

      $items[] = '<a id="' . DFBM()->prefix() . '-remove-license' . '" href="#">' . esc_html__( 'Remove License', DFBM()->domain() ) . '</a>';

      return $items;

    } // end removeLink

    /**
     * Verify the Ajax request
     *
     * @since 1.0.10
     */
    private function verifyRequest( $opt )
    {

      $r = $_REQUEST;

      $nonce = isset( $r['nonce'] ) ? esc_attr( $r['nonce'] ) : false;

      if ( $nonce && wp_verify_nonce( $nonce, DFBM()->prefix() . '-nonce-value' ) ) :

        if ( isset( $r[$opt], $r['license'] ) && 'true' == $r[$opt] )
          return true;

      endif;

      wp_send_json( (object) [ 'failed' => $this->getMessage()->error ] );

      wp_die();

    } // end verifyRequest

    /**
     * Get the process messages
     *
     * @since 1.0.10
     */
    private function getMessage()
    {

      $message = 'The license has been successfully ';

      return (object)
      [

        'error'   => esc_html__( 'That fails. Please reload the page and try it again.', DFBM()->domain() ),
        'success' => esc_html__( $message . 'activated.', DFBM()->domain() ),
        'delete'  => esc_html__( $message . 'deactivated.', DFBM()->domain() ),

      ];
    } // end getMessage

    /**
     * Activate the license via Ajax
     *
     * @since 1.0.10
     */
    public function activateLicense()
    {

      if ( $this->verifyRequest( DFBM()->prefix() . '_activate' ) ) :

        $licenseKey = trim( esc_attr( $_REQUEST['license'] ) );

        $response   = $this->apiRemoteRequest( 'activate_license', $licenseKey );

        if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) :

          if ( is_wp_error( $response ) )
            $message = $response->get_error_message();

          else
            $message = esc_html__( 'An error occurred, please try again.', DFBM()->domain() );

        else :

          $license = json_decode( wp_remote_retrieve_body( $response ) );

          if ( false === $license->success ) :

            switch( $license->error ) :

              case 'expired' :

                $message = sprintf(
                  esc_html__( 'Your license key expired on %s.', DFBM()->domain() ),
                  date_i18n( get_option( 'date_format' ), strtotime( $license->expires, current_time( 'timestamp' ) ) )
                );

                break;

              case 'revoked' :

                $message = esc_html__( 'Your license key has been disabled.', DFBM()->domain() );

                break;

              case 'missing' :

                $message = esc_html__( 'Invalid license.', DFBM()->domain() );

                break;

              case 'invalid' :
              case 'site_inactive' :

                $message = esc_html__( 'Your license is not active for this URL.', DFBM()->domain() );

                break;

              case 'item_name_mismatch' :

                $message = sprintf(
                  esc_html__( 'This appears to be an invalid license key for %s.', DFBM()->domain() ),
                  DFBM()->title()
                );

                break;

              case 'no_activations_left':

                $message = esc_html__( 'Your license key has reached its activation limit.', DFBM()->domain() );

                break;

              default :

                $message = esc_html__( 'An error occurred, please try again.', DFBM()->domain() );

                break;

            endswitch;
          endif;
        endif;

        if ( isset( $message ) ) :

          wp_send_json( (object) [ 'failed' => $message ] );

          wp_die();

        endif;

        // Set the license information
        ( new dfbmModelSet )->option( DFBM()->prefix() . '_license_status', $license->license );
        ( new dfbmModelSet )->option( DFBM()->prefix() . '_license_key', $licenseKey );

        $this->clearCaches();

      endif;

      wp_send_json( (object) [ 'success' => $this->getMessage()->success ] );

      wp_die();

    } // end activateLicense

    /**
     * Deactivate the license via Ajax
     *
     * @since 1.0.10
     */
    public function deactivateLicense()
    {

      if ( $this->verifyRequest( DFBM()->prefix() . '_deactivate' ) ) :

        $response = $this->apiRemoteRequest( 'deactivate_license', $this->licenseKey );

        if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) :

          if ( is_wp_error( $response ) )
            $message = $response->get_error_message();

          else
            $message = __( 'An error occurred, please try again.' );

          if ( isset( $message ) ) :

            wp_send_json( (object) [ 'failed' => $message ] );

            wp_die();

          endif;
        endif;

        $licenseData = json_decode( wp_remote_retrieve_body( $response ) );

        if( $licenseData->license == 'deactivated' ) :

          // Remove the license data
          ( new dfbmModelDelete )->option( DFBM()->prefix() . '_license_status' );
          ( new dfbmModelDelete )->option( DFBM()->prefix() . '_license_key' );

          $this->clearCaches();

        endif;
      endif;

      wp_send_json( (object) [ 'delete' => $this->getMessage()->delete ] );

      wp_die();

    } // end deactivateLicense

    /**
     * Clear EDD and plugin caches
     *
     * @since 1.0.10
     */
    public function clearCaches()
    {

      $slug = basename( $this->args->file, '.php' );
      $beta = empty( $this->args->args['beta'] ) ? false : true;
      $key  = md5( serialize( $slug . $this->args->args['license'] . $beta ) );
      $key2 = md5( serialize( $slug . $beta ) );

      ( new dfbmModelDelete )->option( '_site_transient_update_plugins' );
      ( new dfbmModelDelete )->option( 'edd_api_request_' . $key2 );
      ( new dfbmModelDelete )->option( 'edd_api_request_' . $key );
      ( new dfbmModelDelete )->option( $key2 );
      ( new dfbmModelDelete )->option( $key );

    } // end clearCaches

    /**
     * Handle the API remote request
     *
     * @since 1.0.10
     */
    public function apiRemoteRequest( $request, $license )
    {

      $args =
      [

        'url'        => home_url(),
        'license'    => $license,
        'edd_action' => $request,
        'item_name'  => urlencode( DFBM()->title() ),

      ];

      return wp_remote_post( DFBM()->host(), array( 'timeout' => 15, 'sslverify' => false, 'body' => $args ) );

    } // end apiRemoteRequest
  } // end class
} // end if

 

Final words for this implementation

As you can see here, the implementation of the EMP EDD Plugin Updater is quite simple. Adding some simple Ajax instructions makes it very easy for users to use. This makes sense if you don’t want to implement your own settings page for your plugin, or if you want to separate it from each other like I do. As promised, here is a link to a gist that you can easily integrate into your workflow. Have fun with it!

Have you suggestions for improvements to this article? Just use the comment area below. Do you want support for implementation or do you need help elsewhere? You can book us. For this, simply use our contact form to get in touch with us.

Divi is a registered trademark of Elegant Themes, Inc. This website is not affiliated with nor endorsed by Elegant Themes.

Get the best out of your web!

About Me

WordPress Developer Bruno Bouyajdad from Indikator Design

Bruno Bouyajdad
WordPress Developer, Photographer, video editor, 3D animator and more.

Products

Bring your Divi Blog to the next level » Indikator DesignDivi WooCommerce re-invented » Indikator DesignEasily create catalogs and menus in Divi » Indikator DesignBring Bloom in line with GDPR » Indikator Design

Newsletter

Cookie Preference

Please select an option. You can find more information about the consequences of your choice at Info.

Select an option to continue

Your selection was saved!

Info

Info

To continue, you must make a cookie selection. The various options and their meaning are explained below.

  • Accept all cookies (recommended):
    Tracking and analysis cookies. This helps us to better understand what you like about our site and to provide you with good and interesting content. Of course, this data is collected anonymously.
  • Allow only necessary cookies:
    Only the cookies that are necessary for the operation of the website.

You can change your cookie setting here anytime: Imprint. Imprint

Back