WooCommerce subscriptions speed up from external plugin

If you are running a WooCommerce subscriptions website you might avoid front-end delays by using proper caching. But in the admin you will still encounter issues with large databases and not only.

Using the Query monitor plugin I found that the class used by WooCommerce subscriptions to check for deprecated hooks is actually doubling the loading times for some of the pages. The class “WCS_Dynamic_Hook_Deprecator” is adding an action on the “all” hook. The “all” hook should never be used unless really needed ( this is a real case where this is needed ).

Removing the action

The main problem when trying to remove the action is that it is adding using an anonymous function in a class instance:

add_filter( 'all', array( &$this, 'check_for_deprecated_hooks' ) );

As explained in this awesome WordPress Stackexchange answer, because the function is anonymous WP generates a name for it using the _wp_filter_build_unique_id function. This means we can find the hook and remove it using the $GLOBALS variable.

It’s much easier than it sounds when using the function below ( from the same answer mentioned above ):

if ( ! function_exists( 'remove_anonymous_object_filter' ) )
{
   /**
    * Remove an anonymous object filter when a class uses $this
    *
    * @param  string $tag    Hook name.
    * @param  string $class  Class name
    * @param  string $method Method name
    * @return void
    */
   function remove_anonymous_object_filter( $tag, $class, $method )
   {
      $filters = $GLOBALS['wp_filter'][ $tag ];
      if ( empty ( $filters ) )
      {
         return;
      }
      foreach ( $filters as $priority => $filter )
      {
         foreach ( $filter as $identifier => $function )
         {
            if ( is_array( $function)
                 and is_a( $function['function'][0], $class )
                     and $method === $function['function'][1]
            )
            {
               remove_filter(
                  $tag,
                  array ( $function['function'][0], $method ),
                  $priority
               );
            }
         }
      }
   }
}

Add this as a stand-alone plugin or in your theme’s “functions.php” file. Afterwards you can remove the WooCommerce Subscriptions hook using:

// remove the hook deprecator check to speed up woocommerce subscriptions
remove_anonymous_object_filter('all', 'WCS_Dynamic_Hook_Deprecator', 'check_for_deprecated_hooks');

Obviously you can use this function for any other situation in which a class assigns an anonymous function to a hook.

WordPress admin check if content changed using Heartbeat

When creating a WordPress admin panel it is common to want to refresh the page based on real-time content changes. The most straightforward way of doing that is by hooking into the WP Heartbeat API.

Using the Heartbeat API you can have almost real-time updates to the admin content.

What is the WordPress Heartbeat API?

It was introduced in WordPress version 3.6 and basically it does a regular Ajax call. It does so in order to check if the user is still logged in or if someone else is editing a post. This way it can show you the whole “someone else is editing this post” screen which you can “Take over”.

The Ajax call is, by default, being triggered based on user activity and at a certain interval but we can also change that to a more “stable” call at a given interval.

But first, the data update!

When you are ready to update the data in your admin panel ( or another plugin’s admin panel ) there are 2 steps to follow:

  1. You add a piece of JavaScript that adds your custom variable
  2. You filter the value of “heartbeat_received” and populate the data which will also be used in the script you added above.

Here’s an example of doing that:

add_filter( 'heartbeat_received', 'm_add_heartbeat_data'), 10, 2 );
function m_add_heartbeat_data( $response, $data ) {
   if ( isset($data['daed_version']) && $data['daed_version']=='dashboard_editorial' ) {
      $response['daed_version'] = $this->get_version();
   }
   return $response;
}
add_action( 'admin_print_footer_scripts', 'm_heartbeat_footer_js');
function m_heartbeat_footer_js() {
   global $pagenow;
   // check the page you are on, maybe you don't want to run this on all the admin pages
   if( 'admin.php' != $pagenow )
      return;
   ?>
   <script>
      (function($){
         $(document).on('heartbeat-send', function(e, data) {
            // add your custom variable which will be populated in the ajax request
            data['m_sales'] = 'yes_send';
         });
         $(document).on( 'heartbeat-tick', function(e, data) {
            // don't run the script if the custom variable is not present
            if ( ! data['m_sales'] )
               return;
            if ( data['m_sales'] ) {
               $('#m_sales_count').text(data['m_sales']);
            }
         });
         // set the heartbeat interval - explained below
         wp.heartbeat.interval( 'slow' );
      }(jQuery));
   </script>
   <?php
}

If you’re asking yourself “why not add this in a separate js file?” – the answer is, because it will get cached and maybe you want to pass some variables to it.

Changing the interval

You can change the Heartbeat interval either by hooking into the “heartbeat_settings” filter using PHP and changing the “interval” value:

function m_heartbeat_settings( $settings ) {
   $settings['interval'] = 60; //only values between 15 and 60 seconds allowed
   return $settings;
}
add_filter( 'heartbeat_settings', 'm_heartbeat_settings' );

Or by using the JavaScript functions as I have done above using one of the 3 available values: fast, slow or standard.

// 5 seconds but no more than 2 and a half minutes
wp.heartbeat.interval( 'fast' );
// 60 seconds
wp.heartbeat.interval( 'slow' );
// 15 seconds
wp.heartbeat.interval( 'standard' );

 

Adding a custom table to the WordPress database

Many times in a WordPress plugin you want to store data in an easy accessible way without bloating the default WP tables. That’s when a custom database table comes in handy.

This isn’t something new just a way to do it clean and some warnings from troubles encountered in live environments.

The code

This is just a simple example of a database class which I include in custom plugins:

class M_Db {
    private static $db_version = '1.0.0';
    const DB_VERSION_NAME = 'm_db_version';
    const POSTS_TABLE = 'm_info';
    public static function install() {
        global $wpdb;
        $installed_ver = get_option(self::DB_VERSION_NAME);
        if ( $installed_ver != self::$db_version ) {
            $table_name = $wpdb->prefix . self::POSTS_TABLE;
            $charset_collate = $wpdb->get_charset_collate();
            $sql = "CREATE TABLE $table_name (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            user_id bigint(20) DEFAULT 0 NOT NULL,
            region VARCHAR(50) NOT NULL,
               UNIQUE KEY id (id)
           ) $charset_collate;";
            require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
            dbDelta( $sql );
            update_option( self::DB_VERSION_NAME, self::$db_version );
        }
    }
}

This code will allow you to create a custom table and also easily update its structure using the version check. After a change is made ( like adding a new column ) you just need to change the static version and the table will be updated.

Of course, you don’t want to run this code each time the website is loaded so you’ll have to call the “install” function from the proper hook.

In a plugin ( and hopefully you won’t be creating custom tables with a theme! ) you should run this on plugin activation by adding this code to the main plugin file:

register_activation_hook( __FILE__, array( 'M_Db', 'install' ) )

Warnings

  • if you have a column for user_id – make sure you match its format to the WordPress wp_user ID format. You wouldn’t want user ids to get saved as the maximum value of an integer for all the users with a larger id number – this applies for any relation column.
  • make sure you consider adding a case for plugin deactivation if that’s necessary using register_deactivation_hook.
  • on the same note above – if the plugin will be present in a repository, make sure you handle updates properly.

Adding Youtube video to Instant Articles for WP plugin

The official plugin for adding Instant Articles support to a WordPress website is Instant Articles for WP created by Automattic & Facebook.

The problem

This plugin generates the XML needed by Facebook to enable the Instant Articles functionality for your website. Although it will include your external resources ( like a Youtube video ) by default if it’s placed in the main post content, adding it from a custom field isn’t that easy and you need to define custom functionality.

The easiest way of adding custom content is by hooking into the “instant_articles_transformed_element” filter and modifying the content directly.

Because the instant article is composed of multiple XML components you can’t just add the iframe to the content as that will get ignored in the final output or worse: throw errors.

Adding a video to the Instant Articles

The recommended way is to use the Facebook Instant Articles PHP SDK functions available from the plugin:

add_filter( 'instant_articles_transformed_element','m_add_video' );
function m_add_video ($instant_article) {
    
    // ideally you should check that the class exists,
    // but as the filter will only be called in the plugin it shouldn't be a problem
    $post_id = $GLOBALS['_POST']['post_ID'];
    $video_url = get_post_meta($post_id, 'video_url', true);
    
    if ( !empty($video_url) ) {
        // make a full reference to the class in the plugin
        $video = \Facebook\InstantArticles\Elements\Interactive::create();
        $video->withSource($video_url);
        $video->withWidth(560);
        $video->withHeight(315);
        $video->withMargin(Facebook\InstantArticles\Elements\Interactive::NO_MARGIN);
        $instant_article->addChild( $video );
        
    }
    
    return $instant_article;
    
}

This will add a new iframe reference at the end of the instant article, if you are looking to add it in the beginning ( before the post content ) you can use “unshiftChild” instead of “addChild“.

Important:

  • you must set both width and height if you use “withSource” or it will get ignored by the SDK as not valid
  • if you want to avoid setting a height for custom iframes you can send something in the “withHTML” method
  • setting the iframe source directly to player.vimeo.com doesn’t work for some reason. You’ll have to add the Vimeo videos using the full iframe code in the withHTML method.

Updating WordPress password without logging out

I recently created a plugin which among others forces the users to update their passwords during the setup phase.

After going live with the functionality, the client reported the users were confused as they could no longer access the website. I then realized that using wp_set_password() will update the password but also destroy the current user session forcing him to re-authenticate, which is good, but in my case the function was called after the initial headers were set so the user saw the loaded page but couldn’t use any of the features there.

The fix?

Use instead

$userdata = (array)wp_get_current_user();
$userdata['user_pass'] = $new_password;
wp_update_user($userdata);

This will update the password but also re-create the user session, as it shows at the end of the wp_update_user() function:

// Update the cookies if the password changed.
$current_user = wp_get_current_user();
if ( $current_user->ID == $ID ) {
   if ( isset($plaintext_pass) ) {
      wp_clear_auth_cookie();
      // Here we calculate the expiration length of the current auth cookie and compare it to the default expiration.
      // If it's greater than this, then we know the user checked 'Remember Me' when they logged in.
      $logged_in_cookie    = wp_parse_auth_cookie( '', 'logged_in' );
      /** This filter is documented in wp-includes/pluggable.php */
      $default_cookie_life = apply_filters( 'auth_cookie_expiration', ( 2 * DAY_IN_SECONDS ), $ID, false );
      $remember            = ( ( $logged_in_cookie['expiration'] - time() ) > $default_cookie_life );
      wp_set_auth_cookie( $ID, $remember );
   }
}

Note: this has to be called before the headers were sent as it will attempt to re-write them and you will get the “headers already sent” error. So I recommend running it in the wp action which runs exactly before anything is printed.