Most of you will have heard about WooCommerce extensions by now, and WooCommerce sure is great for extending because it has loads of hooks and filters to play with, but in truth you can extend any WordPress plugin in pretty much any way you want and without touching any of the plugin files.

Here is my personal choice on how to do this, along with some handy hints on getting around the problem of not having the right hook in place etc.

I have a template which I call a boilerplate, which I will embed below:

So the first thing to do is change all instances of PB to the name of the plugin you are creating, which, as it is an extension, should probably be the name of the original plugin plus something unique to your extension - possibly what it does. I once built an extension to the events manager plugin that allowed the site owner to pay commission to those who posted events - I called it Events Manager Commissions (events-manager-commissions).

If you do a find/replace for PB to Events Manager Commissions you are good to go except for the classname is no good, so it is better to do find replace for PB to Events_Manager_Commissions and then you only have the Plugin Name (in the top comments) to change from Events_Manager_Commissions to Events Manager Commissions and bobs your uncle.

Now on to the actual meat of the boilerplate, as I said I like to go for a class based approach (OOP) because it is easier to store data and to pass it around. We already changed the name of our class to Events_Manager_Commissions and so we just need to instantiate it. Now in some cases, if you rely on other classes you may have to hook into the plugins_loaded hook and instantiate the class there, but in most cases you can just go ahead and do it either above the class declaration or below. I always go for above:

global $PB;
$PB = new PB;

This instantiates the class and stores it to global as well. We could do this in one line


$GLOBALS[‘pb’] = new PB;

Now if we go into the class we first declare 2 very important variables we need in the plugin:

private $textdomain = "pb";
private $required_plugins = array();

The first one is the textdomain for i18n translation functions - you can change it to something more in line with the plugin name, or leave it as is. The next one is rather more important, because it can literally stop your plugin from being run. You insert into this array the required plugins for this extension. Following the examples we would make it private

$required_plugins = array(‘events-manager’,’events-manager-pro’);

And then the first function in the class checks that those plugins are active. See my last post for more on that function. Now on to the main function not only for this class (practically every class actually), but for my entire method of extending plugins…

 
function __construct() {
        if (!$this->have_required_plugins())
            return;
        load_plugin_textdomain($this->textdomain, false, dirname(plugin_basename(__FILE__)) . '/languages');
    }

So here we call up our check for required plugins and exit (return;) if we don’t have them then we load the plugin textdomain up for the language related functions. Nothing spectacular there, but this is the main function because it is called automatically when the class is instantiated, and that means we can use it as an easy and central place to connect to any hooks we may need to.

Here is the __contstruct function from a recent Woocommece


function __construct() {
        if (!$this->have_required_plugins())
            return;
        $this->box_created = false;
        add_filter('woocommerce_subscription_settings', array($this, 'set_sub_product_settings'));
        add_action('admin_notices', array($this, 'need_box_product'));
        /* New */
        add_action('woocommerce_add_to_cart', array($this, 'sort_items'), 100, 6);
        add_filter('woocommerce_cart_item_price_html', array($this, 'add_per_month'), 100, 3);
        add_filter('woocommerce_cart_item_subtotal', array($this, 'add_per_month'), 100, 3);
        add_filter('woocommerce_create_order', array($this, 'prepare_pak'), 1000, 2);
        add_action('woocommerce_checkout_update_order_meta',array($this,'build_pak'),100,2);
        add_filter('woocommerce_cart_totals_order_total_html',array($this,'show_subs_total'));
        /* ENDS */

        add_action('woocommerce_before_add_to_cart_button',array($this,'ptinterface'));
        add_action('woocommerce_product_options_sku',array($this,'discount_field'));
        add_action('woocommerce_process_product_meta',array($this,'save_discount'));
        add_action('wp_footer',array($this,'javascript'));
    }

So you see that framework gives you a simple and central place from where you can hook into everything and anything you need to: multiple plugins, theme, enqueue styles and scripts anything all in one place. I am not saying it is perfect but for me it allows me to make extensions to any plugin very easily.

Dash I am already over 700 words and I haven’t covered anything about what to do if you don’t have a hook where you need it. Sure, you can add a hook and contact the plugin devs about adding it to the next release but more often than not a workaround can be found. Please comment if you are stuck in such a situation and I will make a post on that very subject for next week. If you liked this post and want to hear more tips and posts from me please like and share - thanks a million.

Codeable... Where Realistic Clients Meet World Class WordPress Developers

Post Your Project Today

  • Are you sure you want to use global variable to store your plugin object? Globals are evil and we have enough of that in WordPress. This approach is much better:

    /**

    * Main MyPlugin Class.

    *

    * @class MyPlugin

    * @version 1.0.0

    */

    class MyPlugin {

    /**

    * The single instance of the class.

    *

    * @var MyPlugin

    * @since 1.0.0

    */

    protected static $_instance = null;

    /**

    * Custom Property.

    *

    * @var $custom_property

    */

    public $custom_property = null;

    /**

    * Main MyPlugin Instance.

    *

    * Ensures only one instance of MyPlugin is loaded or can be loaded.

    *

    * @since 1.0.0

    * @static

    * @see MyPlugin()

    * @return MyPlugin - Main instance.

    */

    public static function instance() {

    if ( is_null( self::$_instance ) ) {

    self::$_instance = new self();

    }

    return self::$_instance;

    }

    /**

    * Custom Method.

    * @return custom_method()

    */

    public function custom_method() {

    return 'Custom Method';

    }

    }

    /**

    * Main instance of MyPlugin.

    *

    * Returns the main instance of MyPlugin to prevent the need to use globals.

    *

    * @since 1.0.0

    * @return MyPlugin

    */

    function MyPlugin() {

    return MyPlugin::instance();

    }

    //Now we can easily access any available properties and methods of MyPlugin object

    MyPlugin()->custom_property;

    MyPlugin()->custom_method();

    • Liam Bailey

      Hi there,

      Thanks for your comment. I love that approach. However I can see no reason why it is any better in this case, because if you note in this use case the global is not used and could easily be removed altogether. I have assigned the global simply because I may want to use it later.

      When I build WooCommerce integrations such as the integration I did for Salesforce, or a payment gateway I use the instance based approach because it better suits those use-cases.

      It is easy to call things evil but in my experience globals are just like everything else in coding, fine as long as they are used wisely and properly to fit the use case.

      Liam

      • I agree globals work but they could be very confusing in PHP, especially for starting developers. What I try to say here is “boilerplate” should provide best design for you plugin and globals are just not best way to go here.

        And when I say “evil” I mean all the arguments why you should avoid them you can find around the internet discussions.

        • Liam Bailey

          Everyone is entitled to their opinion and no one should ever think that their opinion of best is right, I certainly don’t – that is why I said My Plugin Boilerplate. I have built dozens of WordPress plugin extensions using that very one and it has never failed me yet. What is best? Best for who? The one in this article is the best for me in most cases, as explained if the case dictates a different approach then that is what I use. The only other thing I will say is that most starting PHP developers stay away from OOP unless things have drastically changed lately :-)

    • Felipe

      This is called Singleton Pattern. Thanks!

    • Julix

      Repost this in a blog post somewhere, not hidden away in a comment section! Your Plugin Boilerplate :)

  • jsligh

    I am trying to modify a function within a class in a plugin called “WP Job Manager Applications”.
    file wp-job-manager-applications-admin.php
    class WP_Job_Manager_Applications_Admin
    there is an action in the class:
    add_action( ‘manage_job_application_posts_custom_column’, array( $this, ‘custom_columns’ ), 2 );

    The function is
    public function custom_columns( $column )

    I currently have my revisions and additions to this function working. It is coded directly in the aforementioned file.

    Id really like to use your method to extend this and other examples. I have set the required_plugins array and then tried to do a remove_action on the existing and then an add_action to replace it with the modified my_custom_columns function. Im not seeing any results and I am not showing debug errors.

  • Liam Bailey

    @dunkotron:disqus Try attaching to the hook at an earlier priority,

    “`add_action( ‘manage_job_application_posts_custom_column’, array( $this, ‘your_own_function’ ), 2 );“`

    function your_own_function($column) {
    remove_action( ‘manage_job_application_posts_custom_column’, array( WP_Job_Manager, ‘custom_columns’ ), 2 );
    //Then my custom code
    }

    The only other thing that can trouble you there is the `WP_Job_Manager’ You can try it with and without quotes, and if that doesn’t work then see if it is stored into a global by the plugin and use that. Failing that the hook you are looking at runs off a preceeding filter that sets the column names, if you can’t deattach then unset the column from the array in the filter (later priority) and add one with a different key => same value (value is column heading) and then attach to the hook we are talking about at any priority, and hit your new key with whatever output you wish.

    I have that plugin somewhere, come back to me if you’re still stuck and I will try and give you something tested.

  • Muhammad Fathur Rahman

    where is the continuation for this article? Its been 2017 and people wander from Google only to find incomplete article :)