Skip to content
← All writing

Episode 4 – WordPress Plugin Development with WooCommerce

The save

Now we catch the envelope. Four checks, then we write to the lockers.

Step 1. Put a nonce in the form

In render, add this line just inside the <form> tag, above the table:

				<?php wp_nonce_field( 'orb_save_settings' ); ?>

This prints a hidden field with a secret code in it. The code is tied to your user and to the clock. It dies after a day.

It is a ticket stub. You get one at the door. The save step asks for it back.

Step 2. Book the save function

In __construct, add a second line:

		add_action( 'admin_init', array( $this, 'maybe_save' ) );

admin_init rings early on every admin page load, before anything is printed. That is the correct moment to write data.

The name starts with maybe_. That is a habit. It says “this runs often, and it usually does nothing”.

Step 3. Write the four checks

Add this method to the class, under add_menu:

	public function maybe_save() {

		if ( ! isset( $_POST['orb_save'] ) ) {
			return;
		}

		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			return;
		}

		check_admin_referer( 'orb_save_settings' );
	}

Check 1. Is this my form?

admin_init runs on every admin page. Our code must stay quiet on all of them. orb_save is the name of our button, so only our form sends it.

Check 2. Is this person allowed?

We hid the menu with manage_woocommerce in Episode 2. Hiding a door is not a lock. A person can post data straight to the address with no menu at all. So we ask again here.

Rule: check the capability at the door and at the desk.

Check 3. Is this request real?

check_admin_referer asks for the ticket stub back. If the code is wrong or old, WordPress stops the page with “The link you followed has expired”.

This stops one attack. A bad site puts a hidden form on a page. You visit it while you are logged in to your shop. The form posts to your shop, and your browser sends your cookies with it. Without the stub, your shop obeys. With the stub, the bad site has nothing to send, because it cannot read your page.

Step 4. Clean the data

Add this under the three checks:

		$badge_enabled  = ( isset( $_POST['orb_badge_enabled'] ) && 'no' === $_POST['orb_badge_enabled'] ) ? 'no' : 'yes';
		$notice_enabled = ( isset( $_POST['orb_notice_enabled'] ) && 'no' === $_POST['orb_notice_enabled'] ) ? 'no' : 'yes';

		$badge_text = isset( $_POST['orb_badge_text'] ) ? sanitize_text_field( wp_unslash( $_POST['orb_badge_text'] ) ) : '';
		$threshold  = isset( $_POST['orb_threshold'] ) ? (float) $_POST['orb_threshold'] : 50;

Never trust $_POST. A person can send any value, from any tool, in any shape.

The two dropdowns. We accept one word only. no gives no. Everything else gives yes. That is a whitelist. A whitelist says what is allowed. It does not try to guess what is bad.

The text box. wp_unslash first. WordPress adds backslashes to posted data, and they must come off. Then sanitize_text_field strips tags, line breaks, and invisible junk.

The number. (float) forces a number. abc becomes 0. 50; DROP TABLE becomes 50.

Step 5. Save and say so

Add this at the end of the method:

		update_option( 'orb_badge_enabled', $badge_enabled );
		update_option( 'orb_badge_text', $badge_text );
		update_option( 'orb_notice_enabled', $notice_enabled );
		update_option( 'orb_threshold', $threshold );

		add_action( 'admin_notices', function () {
			echo '<div class="notice notice-success"><p>Order Boost settings saved.</p></div>';
		} );

update_option writes to the locker. It makes the locker if it is missing.

The notice hooks onto a later bell. admin_init runs first, admin_notices runs after. So the message appears at the top of the page.

Step 6. Test it

  1. Set Badge text to Big Sale.
  2. Set Show sale badge to No.
  3. Press Save Changes.

You see the green box. The fields keep your values. Reload the page. They stay.

Your plugin now has memory.

The finished method

	public function maybe_save() {

		if ( ! isset( $_POST['orb_save'] ) ) {
			return;
		}

		if ( ! current_user_can( 'manage_woocommerce' ) ) {
			return;
		}

		check_admin_referer( 'orb_save_settings' );

		$badge_enabled  = ( isset( $_POST['orb_badge_enabled'] ) && 'no' === $_POST['orb_badge_enabled'] ) ? 'no' : 'yes';
		$notice_enabled = ( isset( $_POST['orb_notice_enabled'] ) && 'no' === $_POST['orb_notice_enabled'] ) ? 'no' : 'yes';

		$badge_text = isset( $_POST['orb_badge_text'] ) ? sanitize_text_field( wp_unslash( $_POST['orb_badge_text'] ) ) : '';
		$threshold  = isset( $_POST['orb_threshold'] ) ? (float) $_POST['orb_threshold'] : 50;

		update_option( 'orb_badge_enabled', $badge_enabled );
		update_option( 'orb_badge_text', $badge_text );
		update_option( 'orb_notice_enabled', $notice_enabled );
		update_option( 'orb_threshold', $threshold );

		add_action( 'admin_notices', function () {
			echo '<div class="notice notice-success"><p>Order Boost settings saved.</p></div>';
		} );
	}

Break it on purpose

Break 1. Remove the stub.

Delete the wp_nonce_field line from the form. Save a setting.

You get: The link you followed has expired.

Now you know that message. It means one thing: the form sent no valid stub, or the stub was too old. A customer who leaves a tab open all night gets it too.

Put the line back.

Break 2. Remove check 1.

Delete the isset( $_POST['orb_save'] ) block. Now maybe_save runs on every admin page, and check_admin_referer stops the page dead. The admin becomes unusable.

Put it back. Check 1 is not optional.

Next episode: the badge. We leave the admin and print something on the shop.


Leave a response