Which PHP Framework should I learn? (2016 Comparison)

I’ve become interested in learning a PHP Framework (in addition to the WordPress CMS) for two reasons.  First, I’ve been looking at PHP job listings and experience working with at least one framework is required for most mid to senior level developer/software engineer jobs.

The second reason I’ve decided to learn a PHP framework is that I have an idea for expanding my Amazon Search program and I like the advice to “always be learning something new.”  I think this advice comes from the book Coders at Work by Peter Seibel, but it’s possible that the advice is really from some other place… But it makes sense that I might as well improve my skills while I’m also improving my application.

So I’ve been spending my last few evenings drawing UML class diagrams (as well as flow charts) to plan out my program’s design.  I also spent a day getting the Laravel framework set up on my development server.  So far, I’ve set up a Laravel program that does basic CRUD and I can see why people want to work with Laravel.  The hardest part, after initial set-up, was not overthinking things!  Laravel has a lot of “syntatic sugar” so the code is easy to write and read, but debugging is a pain because functions are hidden inside of traits and behind facades.

Before choosing Laravel for my project, I did some research on PHP Framework popularity.  I searched the job hunting website Indeed.com for major frameworks along with the keyword PHP.  This is what I found:

Framework Popularity based in Indeed Job Openings

For that graph, I omitted the frameworks with 20 or less mentions, which were Aura, Apigility, and Phalcon.

As you can see, I included both Model-View-Controller (MVC) frameworks as well as Content Management Systems (CMS).   While some applications could use either type of framework, in general a CMS is designed for a blog type site or a site managing a lot of content.

For CMS Frameworks, WordPress beat Drupal by a few hundred listings.  This is for a nationwide search that also included the keyword PHP. This was to hopefully exclude jobs that weren’t true PHP programming jobs (like WordPress front-end design or WordPress website management or WordPress Marketing or WordPress SEO), but because this search included jobs that list PHP as a “nice to have” skill, the actual number of WordPress backend PHP programming jobs is probably significantly lower.

As for the MVC frameworks, Laravel and Zend were the most in-demand, each having over 700 job openings.  One word of caution about this result is that some job lisitngs state something like this “Requires knowledge of an MVC PHP Framework, such as Laravel or Zend.”  In this case, they are just giving an example of a popular one, but if you were familiar with CakePHP or CodeIgniter (both MVC Frameworks), then you would also meet that job qualification.

What PHP framework should I use?

Ultimately, you should either search for local jobs and find companies that you want to work for, or search for open source projects that you are interested in, and use the same frameworks that they are using.

Tip: You can use the website builtwith.com to find out what frameworks and other technology a website is using.

An experienced programmer can learn a new framework with relative ease, so once you have strong experience with one framework, you can probably quickly learn another if needed.

What PHP framework should I LEARN?

Another option for choosing a framework is to look at the availability of tutorials and books.  If you can find a tutorial or book that covers the topics you’ll need for your project, in a learning style that you like, then perhaps you should use that framework.  Finding a good instructor or mentor is GOLD!   So, find the best source for learning whatever it is that you want to do, and then use the same frameworks and tools that they are using.  There are a plethora of code instruction websites that have video tutorials and online courses.  Most do cost something, but I’ve found free access to Lynda.com through my local library, Udacity has deeply discounted sales regularly, and Pluralsight gave me a couple of months free as a promotion, so if you join the mailing lists of these educational sights, you might find a low-cost deal.


Do I NEED a PHP Framwork?

Like most everything, there’s both positives and negatives.


  •  A good framework will save you time because it provides basic functionality common to
    many programs so you don’t have to reinvent the wheel!
  •  MVC Frameworks help with code organization.
  •  A framework may make it easier for other programmers to understand your code.
  •  A well-established, open source framework means more eyes on the code and lots of
    bugs already fixed


  • Frameworks can take awhile to set-up and learn.
  •  It may be harder to debug code if you don’t understand the framework thoroughly.
  • Applications may be slower and larger if the framework includes a lot of code that is not relevant to your project.
  •  Your application will depend upon the framework’s continued support and updates (you’ll have to update the framework, especially if security issues are discovered.)


AKG K92 Headphone Review, Photos and Comparison

I’ve been enjoying the AKG K92 headphones for about a week, and thought it was time to do a little comparison. I have a lot of headphones to compare to, and while I’m not the best at writing headphone reviews, maybe someone will find my brief notes helpful.

Compared to a modified pair of the Fostex T50RP

I prefer the AKG K92 headphones because they have more detail than the Fostex, better timbre, and are far more efficient.  On my Teac UD-H01 USB Audio DAC, I have to set the dial at the 12 o’clock position for the Fostex’s, while the AKG’s achieve the same volume level around the 10 o’clock position.

Compared to the Audio-technica ATH-A700X:

I can’t decide which I like better, as the sound is very different, and it would depend on the music and my mood which one I would prefer. The AKGs sound flatter than the ATH-A700X. The Audi-Technica ATH-A700X’s have a wider soundstage and also more treble. The extra treble provides more detail but can sometimes sound harsh.

Compared to the Digitech Pro Headphones (aka Fischer Audio FA-003 / Brainwavz HM5 Studio Monitor):

The AKG K92 headphones sound very similar to the Digitech headphones to me.  You may be familiar with the Digitechs as they are sold under a few different brand names (see above).  While I had trouble differentiating the sound, my husband says that the AKGs have more top end sparkle.

Compared to the Gemini DJ HSR-1000 Professional Monitoring Headphones:

I prefer the AKGs, as they have a much wider soundstage.

Compared to the Akai MPC Professional Headphones

The Akai MPC are the only ones I’ve listened to that without a doubt beat the AKGs on the audiophile sound quality 🙂 While the Akai are the clear winner in sound, the AKGs are more comfortable. The Akai has far more depth and the music seems to convey more emotion. I can hear the room acoustics very well.  As I write this, I would have to say that the Akai MPCs are my current all-time favorite, but I’m sure that will change in the future.  However, the price point is quite different.

For your information, the Akai MPC are the same as the M-Audio HDH-50 and Denon HP2000 DJ Headphones.

AKG K92 Positives

  • Low cost compared to other headphones of similar audio quality
  • Large Ear Openings
  • Wide soundstage for a closed headphone
  • A true pleasure to listen with!
  • Lightweight
  • Self-adjusting

AKG K92 Negatives

  • 3 Meter non-detachable cord  (if walking around, cord gets in the way)

My Conclusion

For the current retail price of $59.99, the AKG K92 are a great deal!  Whether they will stay at that price as more people discover their quality, is anyone’s guess.  Most of the headphones I compared them to usually retail for over $100.   I find them very comfortable to wear and my only non-sound complaint is that the cord is not detachable and it’s too long at times.  The sound quality is quite good and the sound stage is wider than you’d expect for a closed set of headphones.  They seem to “enjoy” all types of music.

More Info: AKG K92 Headphone Earpad Dimensions

My husband has large ears and some over-ear headphones aren’t big enough for him.  We were unable to find the dimensions prior to purchasing, so for your information, the ear opening has a circular opening with an approximate 2.5″ diameter.  

Interested in AKG’s other affordable studio headphones?

The AKG K92 are part of AKG’s new line-up of AKG Project Studio Headphones. The other models are the K52 and K72.  This new product line-up is aimed at combining pro quality sound, style, and affordability.  If you’re looking for an under-$50 pair, you may consider the AKG K72 which have the same driver.  We chose the K92 because for just a $10 price difference, you get better construction, better earpads, and extended frequency range.

Anyone else try these headphones?

They are new to the AKG product line up for this year (2016) and there aren’t many reviews. Perhaps you can take a few moments to share your thoughts.

Related Links

How does WordPress Store Taxonomy Terms in the Database?

While programming my Organize Drafts WordPress Plugin, I did a lot of research on how WordPress uses taxonomies and I spent some time digging around in the WordPress core.  Here’s what I found.

Creating your own taxonomy for use in a WordPress plugin or theme isn’t very difficult, and for a primer I would suggest checking out the WordPress documentation on taxonomies.

However, I came across some confusion on the web about whether WordPress stores the taxonomies in the database.  This confusion seems to be caused by the fact that you have to call register_taxonomy on every load of the WordPress core.  If you don’t, your taxonomy won’t be recognized.

Here’s an abbreviated example of how to call register_taxonomy in a WordPress plugin that uses a class.  As described in the codex, this function needs to be called at a specific time.  When writing WordPress plugins, one of the tricky things is to always make sure your code is being called at the correct time during the WordPress loading process.  The init action hook can be used to call code that needs to run after WordPress has finished loading but before any headers have been sent.

        final class LSW_Organize_Drafts {
        public function __construct() {
        private function hooks() {
	    add_action('init', array($this, 'setup_taxonomy'));

        public function setup_taxonomy() {
	    if(!taxonomy_exists('lswdrafttype')) {
		register_taxonomy( 'lswdrafttype', $post_types, $args );

       new LSW_Organize_Drafts();

In actuality, my code is more complex, as I made the class a Singleton, and also applied filters to the arguments, but hopefully the above code will help new WordPress plugin developers see how they can go about calling register_taxonomy inside of the init hook when using OOP design principles.

So what does register_taxonomy actually DO?

Register_taxonomy actually adds your taxonomy to the global variable $wp_taxonomies.  This happens on line 461 of taxonomy.php, as shown here:

Excerpt from the WordPress core file taxonomy.php

$wp_taxonomies is a global array of registered taxonomies. Before adding your taxonomy to the array, it checks the arguments that you supplied, and merges them with default arguments.

The $args that you supply tell WordPress what to do with your custom taxonomy. You get to specify whether it’s public, hierarchical, whether it should be shown in the menu, etc.  Here’s a screenshot that shows an example of what the $args might contain:

Screenshot taken while debugging WordPress in PHPStorm — this shows what the $args contain after some processing — for what you can actually supply, view the codex 

So, it’s true that the settings for your taxonomy are NOT saved in the database.  They are generated from a PHP array that’s coded in a plugin file or a theme file.

OK, so what if I don’t register my taxonomy?

If you don’t register the taxonomy, then it won’t be visible on any WordPress pages and you won’t be able to use it. However, your taxonomy terms and relationships that you may have previously created are still in the database.

What are Taxonomy Terms?

The terms are the actual category names or tag names, for example.  Or in my case, the terms are the draft types that I’ve created.

If you’re having trouble understanding taxonomy terms and what the difference is between the taxonomy name and the term names, then here are some real-world examples:

Taxonomy: Birds

Terms: Ducks, Geese, Eagles, Chickens, Sparrows

Taxonomy: Property Zoning Types

Terms: Commercial, Residential

Taxonomy: Medication Types

Terms: Over-the-counter, Prescription, Controlled

Why should I use a custom Taxonomy instead of just categories?

Using categories for everything will get messy fast.   Let’s say that you have a real estate website with a page for a house.  You probably want to organize that house by location, sale price, number of bedrooms, etc.  You could just simply add it to multiple categories, so the house could be in the cheap category, the Seattle category, the 4 bedroom category, etc.

However, custom taxonomies make life easier!  You can query each taxonomy separately and also display objects with the same taxonomy easily.  (NOTE: In WordPress, a post is an object!  A page is an object!  A custom post type is an object!  Knowing this becomes important when you look at the database tables.) If you have the taxonomies of sale_price, number_of_bedrooms, and location, you can then easily get all houses that are in the same location or the same price range.

For example, if we have custom taxonomies of sale_price and location, we could use this code to get all cheap houses in Seattle:

$args = array(
	 'posts_per_page' => 8,
	 'orderby' => 'rand',
	 'post_type' => 'houses',
	 'sale_price' => 'cheap',
	 'location' => 'seattle'
$cheap_houses_in_seattle = get_posts( $args );

Where does WordPress store Taxonomy Terms?

The actual terms are in the terms table.  All WordPress tables have a prefix.  In my example, the prefix is linsoft_, but the default prefix is wp_ so the actual name of your table might be wp_terms.

The taxonomy names are stored in the term_taxonomy table.

If you are familiar with database design, you would probably think that there would be a ONE TO MANY relationship between the taxonomy name and the terms, but in fact these tables have a ONE TO ONE relationship.  The WordPress database is not normalized!  There is duplicate data…  See this screenshot:

Screenshot from MySQL Workbench of the WordPress Term Taxonomy Table

As you can see, the taxonomy name is repeated many times. The term_id actually references the term_id that’s in the terms table.

Here’s what’s the terms table looks like:

Screenshot from MySQL Workbench of the WordPress terms table

How does get_terms work?

If you want to get the terms associated with the taxonomy, you use the function get_terms.  To display all of them, including the empty ones, you would do this:

get_terms( 'lswdrafttype', 'hide_empty=0');

Where ‘lswdrafttype’ is the name of your taxonomy.

The get_terms function begins at line 1070 of taxonomy.php  It’s a long function and most of it deals with forming the MySQL query.

The query is formed on line 1437:

$query = "SELECT $distinct $fields FROM $wpdb->terms AS t $join WHERE $where $orderby $order $limits";


As you can see, it’s going to join tables together.

The final query will look different depending on the paramters you gave to get_terms. In my case, the final query looks like this:

SELECT t.*, tt.* FROM wp_terms
AS t INNER JOIN wp_term_taxonomy
AS tt ON
t.term_id = tt.term_id
WHERE tt.taxonomy IN ('lswdrafttype') ORDER BY t.name ASC

What does the WordPress function taxonomy_exists do?

Taxonomy_exists does NOT check the database!  All it does is check the global array $wp_taxonomies.  If you have not called register_taxonomy, then your taxonomy will not be in the $wp_taxonomies variable, and the result will be false, even if you have terms associated with that taxonomy stored in the database!

So, do not fear, your taxonomies might still be in the database, even if taxonomy_exists returns false!

OK, so how does WordPress know which terms are associated with which posts?

Good question!  There’s another table for that, called term_relationships (remember, with your prefix before the table name, so commonly it might be called wp_term_relationships).

Screenshot from MySQL Workbench of the term_relationships WordPress table

Does this table look confusing?  Boring?  Remember how I told you that posts are objects in WordPress?  Well, this is why I mentioned it.  The object_id may be a post_id or an id of another object.  The term_taxonomy_id is the term_id found in the terms table.

You can get all of the taxonomy terms with the WordPress function wp_get_post_terms.  This function ends up called wp_get_object_terms, which creates a query that uses an inner join between the terms table and the term_taxonomy table.  See line 2401 of taxonomy.php.

In its simplest use, wp_get_post_terms can be supplied a $post_id and it will return the tags associated with it.  For example, calling this:


In my database set-up, this function ends up running this MySQL query:

SELECT t.*, tt.* FROM linsoft_terms AS t INNER JOIN linsoft_term_taxonomy AS tt ON tt.term_id = t.term_id INNER JOIN linsoft_term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id  WHERE tt.taxonomy IN ('post_tag') AND tr.object_id IN (636) ORDER BY t.name ASC

If you want to get your custom taxonomy terms instead of the tags, then you will want to pass wp_get_post_terms a second parameter, which is an array of taxonomy names.

For example:

wp_get_post_terms($post_id, 'my_custom_taxonomy');

I hope that this helps!   If you have any questions or clarifications, please do not hesitate to post a comment. Thank you!

WordPress Plugin Tutorial: Create a Widget that uses Post Meta Data

There are a lot of beginner tutorials for developing WordPress plugins, but most focus on creating one simple feature, for example a shortcode, and this may leave programmers new to WordPress feeling lost about how to do something a little more complicated.

What you Will Learn

  • How to create a Meta Box, which is a custom user interface that displays on the post edit page
  • How to save the values entered in the Meta Box into the wp_postmeta database table
  • How to retrieve these values and display them in a dynamic widget
  • How to create a WordPress widget by extending the WP_Widget class
  • How to use the Singleton design pattern (we will use Object Oriented Programming (OOP) principles)
  • Best practices for sanitizing inputs to prevent security concerns


  • PHP 5.3+
  • WordPress 4.4+ (This may work with earlier versions but has not been tested)
  • A general understanding of PHP and HTML

What we Will Make

In this tutorial, we are going to build a WordPress plugin that displays an Amazon link in a widget.  This Amazon link will be different for every post, as it will use the post’s custom fields to determine which link to display.

Here is what our finished product will look like:

Amazon Widget - Link with Affiliate Tag
We will create this widget, which has an input field for entering the affiliate tag that you want to use for your product link. This widget can go anywhere your theme allows widgets – typically this means the sidebar and (maybe) the footer.


WordPress Meta Box Example
We will create this meta box, which has input fields for specifying the Amazon title and the asin. The widget will use these fields to determine which product link to display.
This Plugin is simple – it displays an Amazon link. HOW it looks will depend on your theme and any custom CSS you write. This might not seem too exciting, but it allows you to specify a different product for every single post/page!  If you click on “Amazon Fire Tablet” it will send you to Amazon, using your affiliate link!

A Tutorial for Beginner WordPress Plugin Developers

This tutorial is aimed at people just beginning WordPress plugin development, to show you how to create a widget that displays dynamic content.  It’s easy to display the same content on every page — just add a text widget to your sidebar, footer, or any other widgetized area.  However, displaying custom content that varies based on which page/post you are viewing, requires a custom solution.

Note that the plugin we are going to build doesn’t interact with the Amazon Product Advertising API, which means that it doesn’t grab any database information from Amazon.  This means that it can’t display the current price or the product image.  However, you could extend this plugin to do that if you so desire.  You’ll want to use the WordPress HTTP API to retrieve the data and then you will need to parse the XML.  I have written several plugins that work with the Amazon database, and the whole process is a bit challenging, but definitely doable!

Everyone Learns Differently

This tutorial is written in a step-by-step way, where I only show small parts of the code at a time.  This is so that I can explain what they’re doing and provide relevant tips.  You can start with a blank text editor and follow along.

However, if you learn best by looking at the whole code first, you can get the completed code on Github.

Setting up our Plugin Folder and File

This is going to be a simple plugin with only one file.  We could put this file directly in the \wp-content\plugins directory, but a better idea is to create a directory for it. That allows us to easily extend the plugin in the future, by adding additional files, like CSS stylesheets or Javascript scripts or additional PHP classes.

  1. Create a directory titled lsw-amazon-in-widget
  2. In that directory, create the file lsw-amazon-widget.php

Note: Throughout this tutorial, we will preface many things with “lsw”.  When writing WordPress plugins, it’s a good idea to preface class names, function names, and setting names to decrease the chance of your plugin conflicting with another plugin.

Creating the Plugin File Header

All plugins have to have a file header at the top of their main PHP file. The file header is used by the WordPress File Header API to extract information about your plugin, like the plugin’s name.  Without this file header, your plugin won’t show up in the list of plugins in WordPress admin.   The file header is written inside of PHP comments.

Place this at the top of lsw-amazon-widget.php:

 * Plugin Name: Amazon Widget
 * Plugin URI:  http://www.linsoftware.com/wordpress-plugin-tutorial-widget/
 * Description: Display Amazon Product in Widget
 * Author: Linnea Wilhelm
 * Author URI: http://www.linsoftware.com
 * Version: 1.0.0
 * License: GPL2
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: lsw-amazon-widget
 * Tags: amazon, widget, amazon affiliate

 Creating the Outline of the Plugin

Our plugin will have two classes, which will both be in the same PHP file.  The first class, Lsw_Amazon, will deal with initialization of our plugin and it will contain the functions having to do with the meta box.  The second class, Lsw_Amazon_Widget, will extend WP_Widget and it will handle the front and back end display of the widget.

class Lsw_Amazon  {

	protected function __construct() {
		// constructor

	public static function add_custom_meta_box( $post_type, $post ) {
		//this function will be called by the add_meta_boxes hook

	public function init() {
		//this function will have all of the hooks for setting up our plugin

	public static function render_meta_box() {
		// this function will contain the html for displaying the meta box

	public static function save_meta_details( $post_id ) {
		// this function will be called by the save_post hook
		// it will save the values entered in the meta box

class Lsw_Amazon_Widget extends WP_Widget {

	function __construct() {
                // constructor
	public function widget( $args, $instance ) {
		//Front-end display of widget.

	public function form( $instance ) {
		//Back-end display of widget form.

	public function update( $new_instance, $old_instance ) {
		//Process widget options on save

	private function getAmazonUrl($asin, $affiliate_tag) {
		//a helper function


Looking over this outline, one thing you may notice is a lot of static methods (Also called “functions”).  When using WordPress hooks, the method that is called by the hook must be both public and static.


Implementing the Singleton Design Pattern

We are going to use the singleton design pattern for our Lsw_Amazon class because we only want it to be instantiated once.  If we allowed our class to be instantiated multiple times, not only could this use up more server memory, but it could cause unexpected behavior, like duplicate meta boxes.

Alter the beginning of your Lsw_Amazon class so that it looks like this:

class Lsw_Amazon  {

	public $version = '1.0.0';
	public static $text_domain = 'lsw-amazon-widget';

	protected static $_instance = null;

	protected function __construct() {

	public static function instance() {
		if ( is_null( self::$_instance ) ) {
			self::$_instance = new self();
		return self::$_instance;



Since we are using the Singleton pattern, when we want an instance of the Lsw_Amazon class, we will call Lsw_Amazon::instance() instead of calling the constructor directly with the word new. The instance method checks to see if we already have an instance, and if we do, it returns that instance.  If not, then it creates the instance with the new keyword and the constructor runs.   This means that the constructor runs only once each time WordPress runs. The constructor has the keyword protected in front of it to prevent it from being called with the new keyword.

We also added a couple of class properties, the $version and $text_domain, for our later use.  It is a good idea to save the version as a variable so that you can check it programmatically.  Sometimes, you may use this version number to run specific code when upgrading your plugin.

Let’s add the Action Hooks

For organization, all hooks and filters should go in the same place, if possible.  We’re going to put them in our init() function, which is called by the constructor.

	public function init() {
		add_action( 'add_meta_boxes', array('Lsw_Amazon', 'add_custom_meta_box'), 10, 2 );
		add_action( 'save_post',  array('Lsw_Amazon', 'save_meta_details') );
		add_action( 'widgets_init', function(){ register_widget( 'Lsw_Amazon_Widget' );});

As you can see with the above code, it’s possible to call functions that are inside of classes.  Instead of passing the function name as the second parameter to add_action, you pass an array that contains the class name and function name.

For the widgets_init hook, we are using an anonymous function.  Anonymous functions first became available in PHP with version 5.3.  If you are using an earlier version, you will have to rewrite that line to use a named function, as we are using in the first two lines.

Let’s Add the Meta Box and write the HTML to Display It

	public static function add_custom_meta_box( $post_type, $post ) {
		$post_types = apply_filters('lsw_amazon_post_types', array('post', 'page'));
		add_meta_box( 'lsw_amazon_settings_box',
                    __( 'Amazon Product Settings', self::$text_domain ), 
		    $post_types, 'normal', 'default' );

	public static function render_meta_box() {
		global $post;
		$lsw_amazon = get_post_meta($post->ID, 'lsw_amazon', true);
		$title = isset($lsw_amazon['title']) ? $lsw_amazon['title'] : '';
		$asin = isset($lsw_amazon['asin']) ? $lsw_amazon['asin'] : '';
		<label for="lsw_title">Title   </label><input class="widefat" type="text" name="lsw_title"
		     id="lsw_title" value="<?php echo esc_attr($title); ?>"> <br>
		<label for="lsw_asin">ASIN  </label><input class="widefat" type="text" name="lsw_asin"
		     id="lsw_asin" value="<?php echo esc_attr($asin); ?>">

Tip: Get your case right! $post->id will NOT work.  The correct field name is all capitalized, so it’s $post->ID.

Let’s take a moment to look at this line:

$post_types = apply_filters('lsw_amazon_post_types', array('post', 'page'));

That line is saying take the array and perform all of the filters that are registered with the add_filter hook.

In our case, we are filtering an array of post types.  The default action of our plugin is to only add the meta box to posts and pages.  However, if someone wanted to modify this and have the meta box appear on a custom post type, they could add this code snippet to their theme’s functions.php file.  (Or, they could add it to another plugin, or to a php file in the mu-plugin directory. See here: Must Use Plugins)

add_filter('lsw_amazon_post_types', 'associate_post_types_with_amazon_box');
function associate_post_types_with_amazon_box($post_types) {
   $post_types[] = 'YOUR_CUSTOM_POST_TYPE';
 return $post_types;

A few other notes about the meta box code:

  • We are using functions for internationalization. The function _() translates the text before displaying.
  • We escaped attribute values with esc_attr.  Wordpress has a variety of functions for sanitizing and validating text. (The WordPress Functions Reference is invaluable!)  It’s important to treat all user entered data and all data from the database as potentially dangerous.  User input data includes $_POST and $_SERVER and $_GET data, since those can all be changed by the user.  If you don’t sanitize or validate the data before displaying it on a webpage, your plugin is vulnerable to cross-site scripting attacks.
  • We’re using the PHP ternary operator along with the isset() function to check if our values are set, and to set them to an empty string if they are not set.  If you try to use a variable that has not been set, you will get a Notice: Undefined Variable error. This won’t stop execution of your plugin, but it may cause a very ugly error to appear.  The practice of using a ternary operator with the isset function is so common throughout PHP programming, that the latest version of PHP, PHP 7, has a new null coalescing operator which is much simpler!

Let’s Save the Values Entered in the Meta Box

	public static function save_meta_details( $post_id ) {
		if( isset($_POST['lsw_asin']) && isset($_POST['lsw_title'] )) {
			$lsw_amazon = array('asin'=>sanitize_text_field($_POST['lsw_asin']),
			update_post_meta( $post_id, 'lsw_amazon', $lsw_amazon);

A note about this code:

  • Once again, we sanitize or escape user input before using it. Remember, values from the database can contain dangerous characters and should be treated just like user input. If you’re familiar with PHP but not WordPress, you probably have used the filter_var() function. Well, sanitize_text_field is a WordPress helper function that you can use in its place when you are sanitizing a string.


Now, Let’s Create the Widget

In WordPress, widgets are created by extending the class WP_Widget. This means that there is a set of methods that they must have. Specifically, the WordPress core says that “WP_Widget::widget(), WP_Widget::update() and WP_Widget::form() need to be overridden.” This means that you implement these methods in your child class so that the child methods run instead of the parent methods. If you have followed other tutorials for creating WordPress widgets, or have had a peak at the documentation, then you won’t find any surprises here.

The Widget Constructor

	 * Register widget with WordPress.
	function __construct() {
			'lsw_amazon_widget', // Base ID
			'Amazon in Widget', // Name
			array( 'description' => 'Display an Amazon Product' )


Things to note about this code:

  • This calls the parent’s constructor method (in this case, WP_Widget::__construct())
  • The ID should be unique, and the other parameters are displayed in the Appearance-->Widgets admin area.

The Front-End Display of the Widget


	public function widget( $args, $instance ) {
		if(!is_single()) {
		global $post;
		$lsw_amazon = get_post_meta($post->ID, 'lsw_amazon', true);
		if(isset($lsw_amazon['title']) && isset($lsw_amazon['asin'])) {
			$tag = isset( $instance['tag'] ) ? $instance['tag'] : '';
			echo $args['before_widget'];
                        <h2>You Might Be Interested In...</h2>
			<a href="<?php echo $this->getAmazonUrl( $lsw_amazon['asin'], $tag ); ?>">
				<?php echo sanitize_text_field( $lsw_amazon['title'] ); ?>
			echo $args['after_widget'];

What this code is doing:

This widget method is called when WordPress tries to display your Widget in the dynamic sidebar or whatever widget area you have in your theme, on the front-end. It uses is_single() to check if this is a single page or single post. It doesn’t make sense to display this widget if there is no single post or page being displayed because this widget wouldn’t know what Amazon product to display. You could alter this to have it display a default, but that’s beyond the scope of this tutorial.

So, if is_single() is false, the function completes without doing anything.

The global $post variable holds the current WP_Post object, which has an ID field. We use this ID field as the parameter for get_post_meta, which does exactly what it sounds: it gets the post meta data from the database. In this case, we provide the name of our data as the second parameter and the third parameter, true, indicates that we only want this single value. This value is actually an array that contains the title and the ASIN.

The widget echoes the before_widget html (this is specified by the theme usually), and then echoes a link to the product, based on the ASIN and the affiliate tag. And, finally, the after_widget html is displayed.

The Back-End Display of the Widget

	public function form( $instance ) {
		// outputs the options form on admin
		$tag  = isset( $instance['tag'] ) ? $instance['tag'] : '';
		<label for="tag">Affiliate Tag:</label><input type="text"
		         name=" <?php echo $this->get_field_name( 'tag' ); ?>"
		id="<?php echo $this->get_field_id( 'tag' ); ?>" value="<?php echo esc_attr($tag); ?>"> <br>

The back-end display of the widget is a very simple form with one input field for an Affiliate Tag.

Saving the Widget Values

	public function update( $new_instance, $old_instance ) {
		// processes widget options to be saved
		$instance             = $old_instance;
		$instance['tag']     = sanitize_text_field( $new_instance['tag'] );
		return $instance;

If you don’t explicitly save the form value(s) in the update() method, then the form data won’t be saved. So, make sure you “wire it up” in this way. Once again, you see that we sanitized the input data for security reasons.

The Helper Function

	private function getAmazonUrl($asin, $affiliate_tag) {
		return "http://www.amazon.com/dp/" . $asin . "?tag=" . $affiliate_tag;

Whenever possible, you should use helper functions to do small jobs. This follows the DRY programming principle: Don’t Repeat Yourself. This allows for reusable code and makes test driven development (TDD) possible. Perhaps you later decide to extend this plugin to display links in other ways. You can change how the url is generated by simply changing one function. Or maybe you want to use this plugin on your Amazon UK site and need to change it to amazon.co.uk. Better yet would be to provide an option to specify the country (Amazon has numerous international sites).

The Finishing Touches


At the bottom of your plugin file, instantiate your class. If you don’t instantiate it, your plugin won’t run! Remember that we used that Singleton pattern so we don’t use the new keyword.

Ways this could be improved

This plugin is fully functioning now, but we could make it prettier and give it more options!

  • This plugin needs some CSS. Learn how to enqueue a CSS stylesheet.
  • Document your code! I omitted the PHPDoc to make the snippets smaller.

Get the complete code on Github

Here’s the finished product that we developed in this tutorial.

Troubleshooting Tips

If you run into an issue developing your WordPress plugin, there are lots of tools for troubleshooting.  One place to start is the WordPress Developer Plugin.  It offers an easy interface for installing a bunch of helpful plugins, including the debug bar and the debug bar console.   Here’s a screenshot I took while I was debugging this plugin with the Debug Bar Console:

screenshot of debug bar console, wordpress plugin for developers


More WordPress Plugin Development Guides

Use the Verizon Motorola Moto E on T-Mobile – Instructions

Verizon Moto e using T-mobile 4G Network
My Verizon Moto e using T-mobile 4G Network

I recently bought the Motorola Moto E (Verizon LTE Prepaid) phone for $40.  It is a pretty amazing phone for that price. It runs Android 5.1, uses a 1.2 GHz Quad-Core Processor, and has a 4.5″ display.

What makes this phone particularly attractive to me is that with a little work, you can instruct it to connect to T-Mobile’s 4G LTE network.  Here are the instructions.  I give credit to the wonderful developers over at XDA, as I only modified their instructions slightly.


  • Active T-Mobile Sim Card
  • Comfort with working at the command line
  • Moto E is running Android 5.1  (See this page for instructions for 5.0.2)

Instructions – These instructions worked for me using Windows 7 64 bit.

  1. Download and install the Android SDK Tools.
  2. Insert the T-mobile SIM card into your phone  (The Moto E has a removable band that goes around the phone’s sides.  Remove this band to replace the SIM card and also access the SD card slot. The back of the phone does not need to be removed.)
  3. Power on the phone and put it in airplane mode.
  4. Go to Settings -> About Phone and tap on “Build Number” until it says you are in Developer Mode (~10 times)
  5. Go back one menu, and right above About Phone there should now be an option for Developer Options, go into that.
  6. Turn on USB Debugging
  7. Connect the phone to your computer and wait for your computer to recognize it.  If your computer doesn’t recognize the phone, you may need to install the Motorola driver.
  8. On your Moto E phone, a pop-up window should appear asking “Allow USB Debugging?”  Select OK or Allow.
  9. Open a command prompt and navigate to the location of the SDK tools.  You want to be in the directory that has the Android Debug Bridge program (adb.exe)
  10. Type “adb shell” at the command prompt
  11.  Run the following commands in adb shell:
  12. pm clear com.android.providers.telephony
  13. settings put global preferred_network_mode 9
  14. settings put global preferred_network_mode1 9
  15. settings put global preferred_network_mode2 9
  16. Type “exit” or press CTRL+D in the shell to exit it.
  17. Type “adb reboot” at the command prompt
  18. After your phone has rebooted, you can disconnect it from the computer.
  19. On the Moto E, go to Settings.  Under “Wireless & Networks” select “More” and then select “Cellular Networks.”  In the “Cellular network settings”, click on “Preferred network type.”  If it says 4G (with the options 3G and 2G listed below), then you have successful made the change.  (If you don’t see these options, go back and repeat steps 7-18.  I had to do this twice to get it to work.)
  20. Go back one screen, and select “Access Point Names.”  Click the “+” symbol and add T-mobile.
  21. In the APN settings, update these two settings:
    APN: fast.t-mobile.com
    MMSC: http://mms.msg.eng.t-mobile.com/mms/wapenc
  22. Remove the Verizon messaging app and replace it with Google Messenger (you can get it from the Google Play store) and optionally remove/deactivate any other Verizon apps

Note: When you restart the Moto E, it may still first say “Verizon” and give you a warning about not having a Verizon SIM card, but after a moment, you should see the Verizon word switch to T-Mobile.

I hope this works for you!  As you can see, rooting or unlocking the device is unnecessary to get it to work with T-Mobile’s network.  I’ve read that this method might work with other GSM networks as well.

If you are successful at getting this android phone to run on a T-Mobile network, you save about $70, as the Verizon moto e prepaid version is selling for around $40, while the unlocked moto e GSM version is selling for about $110.  They are, to the best of my knowledge, the same phone, which is the 2nd Generation 2015 version. From what I’ve read, the 1st generation moto e had some issues that were fixed in this model.

If you’re looking for a budget / entry-level android smartphone, I think you will be very happy with the Moto E if you can get it to work on your network.  My family and I own the Samsung Galaxy S3 and the Galaxy S4, and while the screens on both of those Galaxies are higher resolution than the Moto e, the S3 runs older Android software and the Moto E will likely stay current for a much longer time period.  I’m personally happy to have this up-to-date Android phone as a back-up to my S4.


Windows Update Option Missing in Windows 7 (Control Panel Options Missing)

So, I recently turned off the automatic installation of Windows updates on my Windows 7 Operating System because I want to avoid having Windows 10 install automatically. Well, this caused another problem: I could look at a list of updates, but there was no “Install Updates” button. Basically, the following information was missing:

Screenshot of Windows 7 Windows Update Box

Instead of that, I saw a “Get Windows 10” banner. I really don’t mind the banner, but I need to be able to install security updates in order to keep my computer secure.

After a lot of research, I found the GWX Control Panel, which is an application that will remove the Windows 10 nag screen and help you to prevent Windows 10 from automatically installing.

I was hopeful that the GWX Control Panel would fix my problem, but kind of doubtful, as it’s not advertised as a means of fixing this issue.  But my programmer self thought that it looks like the Windows 10 banner is in the same location as the “Install updates” button that I’m used to seeing… so maybe it replaced the other?  So, I gave the GWX Control Panel a try and was really pleased with it.  It was literally a 3-minute solution.

So if you’re staring at your Windows 7 Control Panel -> Windows Update screen and wondering how do I tell it to download and install updates NOW, you’re not blind. It’s very possible that you can’t see this because instead you’re looking at a Windows 10 nag screen.  Get rid of the nag screen, and get all of the Windows Update manual control options back.

I hope this helps someone!   I tried to put enough keywords in this to get it to come up in the search results if you’re troubleshooting this issue and looking for a fix.  Before discovering this solution, I did searches for “Windows 7 Won’t Install Updates” but that just took me down a rabbit hole.

I’m so thankful for the GWX Control Panel, as I’d rather be programming than tinkering with Windows.

WordPress Edit Post Screen Hooks: A Visual Guide

There are lots of reasons that a WordPress developer might want to customize the post edit screen when developing their plugin or theme.  However, using a custom post type is probably the most common reason to customize it.  The developer might want to add additional headers, instructions, styles, javascript, or add/remove input fields.

Note that the easiest way to add custom input fields to the WordPress edit screen is to add a metabox.  However, if you need more ways to customize it, keep reading!  I will even provide some code examples.

Wordpress Admin Edit Post Screen Hooks
WordPress Admin Edit Post Screen Hooks

Reference List of WordPress Hooks that You can use to Modify the Post Edit Screen

1. in_admin_header

in_admin_header is the hook you want to use if you need to alter the heading of any of the admin screens.  It fires at the very top of the page for all admin pages, including the comments page, media page, plugins page, tools page, appearance page, etc.  It even fires on custom settings pages and custom post type pages, like the WooCommerce admin screens for products and orders.  Like all hooks, this hook is called by the WordPress function do_action.  Wordpress core uses this hook to render the WordPress admin bar.  I can see using this hook if you want to add a banner along the top of all admin screens.

2. admin_notices

admin_notices is a great hook to use for posting messages at the top of admin screens.  Wordpress core makes adding notices extremely easy!  Simple use this hook to output a div.  Wrap the div in one or more of the following classes to make your notice stand out

For examples, see the Codex page on admin_notices.

.error = This class will make your notice have a white background and red left border

.update_nag = This class will make your notice have a yellow border and move it higher on the page.

.notice and .is-dismissible = This class will make your notice dismissible!   A great option if you don’t want to annoy your users.  Wordpress core handles adding a close button and removing the notice for you.   (See this article on how to make your notices dismissible.)

3. all_admin_notices

There is not much documentation on this hook.  I looked at the code and it looks like this should fire in some cases when the admin_notices hook doesn’t fire (for example on the network admin screen for WordPress Multisite).  Take a look at WordPress Core for more details (\wp-admin\admin-header.php – around line 228)

4. edit_form_top

This is a hook for adding to the edit screen after the “Add New Post” title.  Note that if you want to change the words “Add New Post” for a custom post type, you should do that when registering a new post type. See the arguments that you can add.  The array of labels has lots of options.   That’s right, you set up those words upon registering the custom post type.  There is no need to try to filter it later.

5. edit_form_after_title

This hook is perfect for adding html to the post edit screen after the heading and above the post content box.  By the way, the rich text editor you use in WordPress is called TinyMCE, which is an open source HTML WYSIWYG editor.

6. edit_form_after_editor

This fires after the post content editor but before the excerpt, the slug, the author, and other optional/add-on meta boxes.

7. edit_form_advanced

This fires near the very bottom of the edit post screen.  It will display your custom HTML after all meta boxes.

Note: This does NOT fire on the edit page screen.  It will fire on all other post types.

8. post_submitbox_misc_actions

Want to add a checkbox, dropdown input field, or other input field or text to the publish post box?  This is the hook to use!  See the Codex for an example.

9. media_buttons

Add a button next to the add media button, for uploading files that your custom plugin or theme deals with in a specialized way.  Tutorial for using the media_buttons action hook.

How to Only Make Changes to the Edit Screen for a Custom Post Type?

In the function that you hook, you can check which screen is being displayed, and then only make the custom changes if the screen ID and screen post type are a match.  For example:

add_action('in_admin_header', 'in_admin_header_lw');
function in_admin_header_lw() {
	$screen = get_current_screen();
	if($screen->post_type=='post' && $screen->id=='post') {
		echo "This is the edit post screen.";

You actually only need to check the id and not the post_type if you only want to make the changes on one page.  But, if you want the change to appear on all pages with the same post_type, then checking the post_type property of the WP_Screen object is the way to go.  The function get_current_screen returns a WP_Screen object.  Each screen has a unique ID.

Everyone has their own workflow, but my personal preference in figuring out how PHP code works is to use a debugger and put a breakpoint at the location where I want to check the contents of variables.  When I did this in PHPStorm, while on the WooCommerce add product screen, I was able to see the contents of the WP_Screen object, therefore making it really obvious what  I should be checking for in my code.

PhpStorm Debugging WordPress WP_Screen Object
PhpStorm Debugging WordPress WP_Screen Object

How to Add Styles and Scripts to the Post Edit Screen?

Need to add javascript or CSS styles to only one admin screen?  Here’s the way to add it (this code assumes that admin-style.css is the name of your CSS file and it’s in the same directory as the plugin file).

add_action( 'admin_enqueue_scripts', 'lw_load_custom_wp_admin_style' );
function lw_load_custom_wp_admin_style() {
	$screen = get_current_screen();
	if( $screen->id=='post') {
		wp_register_style( 'lsw_custom_wp_admin_css', 
                        plugins_url( '/admin-style.css', __FILE__ ),
			false, '1.0.0' );
		wp_enqueue_style( 'lsw_custom_wp_admin_css' );

Questions, Improvements?

Are you puzzled about something?  Or did I make an error?  If so, please take a moment to comment.   Thank you!

Creating a MySQL Database to Track Job Applications

I’m looking for a job with a company who creates a product that I admire.  I want to be part of a talented team.

Organization is critical to the job hunting process. So, like for many things I want to organize in life, I created a MySQL Database. Here’s what it looks like:

ERR Diagram of Job Search Database
ERR Diagram of Job Search Database

Want to create it yourself?  Here is the MySQL code to run:


USE `job_search`;

CREATE TABLE `applications` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `job_id` int(11) unsigned DEFAULT NULL,
  `date` date DEFAULT NULL,
  `result` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `job_id_idx` (`job_id`),
  CONSTRAINT `job_id_ap` FOREIGN KEY (`job_id`) REFERENCES `jobs` (`id`) 

CREATE TABLE `companies` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `contact` varchar(100) DEFAULT NULL,
  `name` varchar(100) DEFAULT NULL,
  `email` varchar(100) DEFAULT NULL,
  `location` varchar(200) DEFAULT NULL,
  `website` varchar(300) DEFAULT NULL,
  PRIMARY KEY (`id`)

CREATE TABLE `interviews` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `job_id` int(10) unsigned DEFAULT NULL,
  `date` date DEFAULT NULL,
  `type` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `job_id_idx` (`job_id`),
  CONSTRAINT `job_id_intv` FOREIGN KEY (`id`) REFERENCES `jobs` (`id`) 

  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(200) DEFAULT NULL,
  `company_id` int(11) unsigned DEFAULT NULL,
  `date` datetime DEFAULT NULL,
  `salary` varchar(45) DEFAULT NULL,
  `url` varchar(300) DEFAULT NULL,
  `referrer` varchar(45) DEFAULT NULL,
  `description` longtext,
  PRIMARY KEY (`id`),
  KEY `COMPANY` (`company_id`)

I also created a view that joins fields from 3 tables for easily seeing a list of jobs that I’ve applied to:

CREATE VIEW `applied_for_jobs` AS
        `jobs`.`title` AS `title`,
        `a`.`date` AS `date`,
        `jobs`.`salary` AS `salary`,
        `jobs`.`referrer` AS `referrer`,
        `c`.`name` AS `name`
        join `companies` `c` ON ((`jobs`.`company_id` = `c`.`id`)))
        join `applications` `a` ON ((`a`.`job_id` = `jobs`.`id`)))


The next question is what technology should I use to create a CRUD (create, read, update, delete) interface for this database?  I could just use MySQL Workbench or PhpMyAdmin to insert data, but it would be easier to have a web form that automatically validates the data and updates the database.  Since this is an InnoDB database with foreign keys and multiple tables, doing manual insertion takes some time because first you have to insert into the companies table, and then the jobs table, and then after submitting an application, inserting that into the applications table, and so on.

In the past, I’ve created CRUD interfaces with PHP & Javascript. However, I like to learn something new with each project I take on, so I’m considering learning Angular.js for this task. Do you think that’s a good choice? What’s your preferred framework for building CRUD interfaces for the web?


Anatomy of an Amazon Lookup Program for Online Arbitrage

I designed and coded a program that queries the Amazon Product Advertising API. It records the prices and sends an alert if the price meets a certain threshold. This might sound like a pretty simple idea, but there is actually a lot to consider.

Program Requirements

  • Retrieves the price data for thousands of items as frequently as allowed
  •  Sends a mobile alert and updates a website when a price match is made
  •  Allow for easy removal, addition and editing of “watched” products
  •  Displays graphs of historical price data, as well as sales rank
  • Program needs to start up automatically and run for long periods of time without any stability issues.

Additional Challenges (these were “discovered” later)

  •  Amazon changes their database, by redirecting ASINs, and they do not provide this information via their Amazon Product Advertising API
  •  The need to ignore certain sellers became apparent, and this also is not available through the Amazon Product Advertising API.
  •  Text data can take up a lot of space and tables become too large.

A Look at the Program I Developed

I solved all of these challenges.  I wrote the program first in Java and then re-wrote it in PHP and Javascript with more features.  You can write a program that achieves these tasks in pretty much any language.  A popular choice these days would probably be Python or Ruby on Rails or Node.js.  Since I’m going to be discussing general design & implementation here, this article aims to be helpful irregardless of which language you choose.

Key Design Ideas

  • I chose to separate the background worker thread, which looks up the price data, from the user-facing interface.
  • I used the Model-View-Controller (MVC) architectural design pattern.  My model was a MySQL database.  My controller was a PHP script.  My view was implemented primarily with Javascript and HTML/CSS (using the Bootstrap framework).
  • I created a visual dashboard that gives feedback as to the program’s current status, most recent matches, and recent error messages
  • Exceptions are logged but the program is able to recover from them and continue running.
  • I wrote an algorithm that throws away the least important data to save space.  I created summary tables that save just the daily low and high prices and I used these for my graphs.

The Dashboard

Screenshot of Amazon Lookup Program for Online Arbitrage
Screenshot of the Amazon Lookup Program for Online Arbitrage
  1. Green status indicator.  This will turn to a red X if the lookup program is not running correctly (for example, no internet connection or MySQL connection error)
  2. Link to the Status page, which displays errors and information about recent queries
  3. Input field to lookup an ASIN. If it’s in the database, a price history chart is displayed.
  4. The user can star a match that they are considering.
  5. Clicking on the product’s name links to the price history page, which also has a link to Amazon for easy purchasing.


The Amazon Search Portal

Screenshot of the Amazon Lookup Program - Search Portal
Screenshot of the Amazon Lookup Program – Search Portal

The Amazon Search Portal is the part of my program that allows for easy addition, editing, or removal of products from the database.   Products that are checkmarked are already in the database.  The details badge is a link to a popup that allows for editing the desired price and notes.  The update button allows for easy addition or removal of products.  I used Boostrap.js for the design elements.  It’s a really easy way to make a web app responsive and less ugly!

Thinking about doing something like this?  Already did it?

I’d love to hear your experience.  You can definitely make money buying low and selling high — that’s not new.  But with programming skills and a lot of time, you can certainly write programs that do a lot of the work for you!  Automation is definitely one of my favorite programming tasks to take on.


Using the restrict_manage_users Action Hook in WordPress 4.4

I recently participated in an interesting discussion on Stack Exchange about how to add a filter button just above the Users list table on the Users page in WordPress Admin. To sum up the whole issue, if you used the restrict_manage_users hook to do this in an earlier version of WordPress, your code for filtering the users list will probably not work correctly in WordPress 4.4 without making a small change.

Examining The Issue

In WordPress 4.4, the restrict_manage_users hook is called twice when loading the Users page.   (It is only called once in WordPress 4.3.1)  This means that if you use that hook to add a drop down select and filter button (or any other input field or html code), your additions will actually show up on the Users page twice, both above the table and below it.

The restrict_manage_users action hook is called twice in WordPress 4.4
The restrict_manage_users action hook is called twice in WordPress 4.4

Now, normally you would think that this addition is a great convenience. However, unless you change your code, when the user makes a selection in one of the dropdowns, the other dropdown remains the same. Then when the Filter button is clicked, the form is submitted with two values for that input. One value will contain the selection and the other will be blank. If you use Chrome Developer tools, you can inspect the query vars sent with the get request and you will see what I mean:

Analyzing the Query Vars sent with the GET request
Analyzing the Query Vars sent with the GET request


Tip:  Follow these instructions to inspect the query string parameters:

  • Open the page in Google Chrome Web Browser and press CTRL+SHIFT+I to open Developer Tools
  • At the top of the tools, select the Network tab.
  • With the developer tools still open, click on the filter button or submit button or whatever other button that you want to examine the request for.  You should see a whole bunch of requests appear.
  • Click on the request you are interested in.  In this case, it’s going to start with users.php.
  • Click on the Headers tab and scroll down to “Query String Parameters.”

How this Effects Your Code

If you wrote a WordPress plugin that uses both the restrict_manage_users hook and the pre_get_users filter, you likely looked at the $_GET global variable to alter the WP_User_Query.  Because of this change, the $_GET variable will be different, and your code might only work if the user uses the bottom dropdown menu and not the top one.  This is a very big bug, but fortunately there are several different ways to fix this issue.

The Solution

There are at least 3 different solutions to handling this problem.  Visit this WordPress Stack Exchange discussion for example code on how to fix this using PHP.

Here I’m going to present a solution that uses Javascript.  Simply add this to your theme’s functions.php file and change the NAME_OF_YOUR_INPUT_FIELD to the name of your input field!  Since WordPress automatically loads jQuery on the admin side, you do not have to enqueue any scripts.  This snippet of code simply adds a change listener to the dropdown inputs and then automatically updates the other dropdown to match the same value.

add_action( 'in_admin_footer', function() {
	<script type="text/javascript">
		var el = jQuery("[name='NAME_OF_YOUR_INPUT_FIELD']");
		el.change(function() {
} );


If you tried this solution and are still having an issue, please comment below.  There may be use cases where this doesn’t work but I can’t think of any right now.  I’m interested in learning and your feedback is important.

Even if you use my javascript solution, I suggest that you read over the other solutions on Stack Exchange because they provide a lot of insight into how to use a static variable and/or your own custom action hook to deal with the issue of WordPress core calling a hook multiple times.   Using a static variable is a great way to manage how often your plugin performs certain actions, and doing so could help you future-proof your code.