Skip to content
← All writing

Episode 9 – WordPress Plugin Development with WooCommerce

Finishing the plugin

Three jobs left. Open your plugin to other developers. Make it translatable. Write the readme.

Part 1. Filters

Until now you only used add_action. There is a second kind of hook.

  • An action says “do something here”. It returns nothing.
  • filter says “here is a value, change it if you want”. It must return the value.

Action is a doorbell. Filter is a form that goes around the office, and each person may edit a line before passing it on.

Add a filter to the badge

In class-orb-badge.php, find this line:

		$text = get_option( 'orb_badge_text', 'Hot Deal' );

Add this under it:

		/**
		 * Filter the badge text.
		 *
		 * @param string     $text    Badge text.
		 * @param WC_Product $product Current product.
		 */
		$text = apply_filters( 'orb_badge_text', $text, $product );

apply_filters says: “anybody may change this value now”. If nobody joins in, you get back what you sent.

The second value onward is context. We pass the product, so the other developer knows which product they are changing.

Add one to the threshold

In class-orb-shipping-notice.php, under the threshold line:

		$threshold = apply_filters( 'orb_free_shipping_threshold', $threshold );

Add an action too

At the end of render_badge:

		do_action( 'orb_after_badge', $product );

do_action rings a new bell. Nothing happens by itself. It is a place for other code to join.

Test it as another developer

Put this in your theme functions.php, or in a small test plugin:

add_filter( 'orb_badge_text', function ( $text, $product ) {
	if ( $product->get_price() < 10 ) {
		return 'Bargain';
	}
	return $text;
}, 10, 2 );

Reload the shop. Cheap products now say Bargain.

The two numbers at the end matter:

  • 10 is the priority. Same queue rule as Episode 5.
  • 2 is how many values you want. Miss it, and $product never arrives, and PHP throws an error.

The rule for filters: always return something. Forget the return and the value becomes empty. The badge text disappears, and no error tells you why.

Why do this at all?

Because a customer will ask for something you did not build. With a filter, the answer is ten lines in their own file. Without it, the answer is “we cannot do that”, or they edit your plugin and lose it at the next update.

In support work you will send filter snippets every day. Now you know what you are sending.

Part 2. Translation

Every word a person reads must be translatable. Right now ours are stuck in English.

Wrap them. Two functions cover most cases:

  • __( 'text', 'order-boost' ) returns the text.
  • esc_html__( 'text', 'order-boost' ) returns it, escaped for printing.

The second value is the text domain from Episode 1. It must match, letter for letter.

Update the badge

		if ( $sales > 0 ) {
			echo ' &middot; ' . sprintf(
				/* translators: %d: number of units sold. */
				esc_html__( '%d sold this week', 'order-boost' ),
				$sales
			);
		}

Never break a sentence into pieces to insert a number. Word order changes between languages. Use %d or %s and let the translator move it.

That comment line is not decoration. Translation tools show it to the translator, so they know what %d is.

Update the notice

			wc_print_notice(
				sprintf(
					/* translators: %s: amount of money. */
					__( 'Spend %s more and get FREE shipping!', 'order-boost' ),
					wc_price( $remaining )
				),
				'notice'
			);
		} else {
			wc_print_notice( __( 'Nice! You qualify for FREE shipping.', 'order-boost' ), 'success' );
		}

Do the same in your settings screen for every label and every description.

One note: do not add load_plugin_textdomain(). WordPress 6.2 and newer loads translations by itself for plugins from WordPress.org. The old line is now a fault, and reviewers flag it.

Part 3. Clean up on uninstall

When a person deletes your plugin, your four lockers stay in the database forever. That is rude.

Make one more file:

cd "/Users/sejim/Local Sites/simple-boards/app/public/wp-content/plugins/order-boost"
touch uninstall.php

Type this:

<?php
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
	exit;
}

delete_option( 'orb_badge_enabled' );
delete_option( 'orb_badge_text' );
delete_option( 'orb_notice_enabled' );
delete_option( 'orb_threshold' );

WordPress looks for this exact file name, in the plugin folder. It runs it only on Delete, never on Deactivate.

WP_UNINSTALL_PLUGIN is the lock for this file, the same idea as ABSPATH.

Deactivate keeps the data. Delete removes it. Know the difference, because a customer will ask “will I lose my settings?”.

Part 4. readme.txt

This file makes your page on WordPress.org. Make it:

touch readme.txt

Type this:

=== Order Boost ===
Contributors: rafizsejim
Tags: woocommerce, sale badge, free shipping
Requires at least: 6.0
Tested up to: 6.7
Requires PHP: 7.4
Stable tag: 1.0.0
License: GPLv2 or later

Sale badges and a free shipping nudge for WooCommerce.

== Description ==

Order Boost adds two things to your shop:

* A badge on products that are on sale, with a "sold this week" counter.
* A "spend more for free shipping" message on the cart page.

Both can be turned off under WooCommerce > Order Boost.

== Changelog ==

= 1.0.0 =
* First release.

Stable tag is the one that catches people out. It tells WordPress.org which version to give to users. Get it wrong and your update goes nowhere, or the wrong files ship.

Keep three numbers in step, always:

  1. Version: in the plugin header.
  2. ORB_VERSION.
  3. Stable tag in readme.txt.

When a customer says “I updated and the CSS did not change”, check ORB_VERSION first. Episode 6 told you why.

Your finished plugin

order-boost/
├── order-boost.php
├── uninstall.php
├── readme.txt
├── assets/
│   ├── css/orb.css
│   └── js/orb.js
└── includes/
    ├── class-orb-settings.php
    ├── class-orb-badge.php
    └── class-orb-shipping-notice.php

Eight files. Two features. One settings screen. That is a complete WordPress plugin, and you typed every line.


Leave a response