WordPress Hooks and Filters Order Of Precedence

When building plugins and themes I often need to reference the WordPress order of precedence of hooks.   This helps ensure various components are loaded only when needed and at the right time.   The base list I reference is the old Codex Plugin API/Action Reference page.   Its sister resource, the Codex Plugin API/Filter Reference is also useful.

The only problem I have with those resources is when I need to determine what will fire on the front-end, backend (admin only) , AJAX, and Cron.    This is my cheat sheet to help sort out the first two (front-end v. admin processing).

WordPress Action Hooks

Stage 1 : Loading

Front EndAdmin Pages
muplugins_loadedmuplugins_loaded
registered_taxonomyregistered_taxonomy
registered_post_typeregistered_post_type
plugins_loadedplugins_loaded
sanitize_comment_cookiessanitize_comment_cookies
setup_themesetup_theme
load_textdomainload_textdomain
after_setup_themeafter_setup_theme
auth_cookie_malformed
auth_cookie_validauth_cookie_valid
set_current_userset_current_user
initinit
widgets_init *widgets_init *
register_sidebarregister_sidebar
wp_register_sidebar_widgetwp_register_sidebar_widget
wp_default_scripts wp_default_scripts
wp_default_styles wp_default_styles
admin_bar_init admin_bar_init
add_admin_bar_menus add_admin_bar_menus
wp_loaded wp_loaded

Notes

widgets_init is fired by hooking the init action.  It fires at priority 1.

Stage 2 : Processing

After wp_loaded is called, this is where the front end and admin processes diverge.

Front EndAdmin Pages
parse_request auth_redirect
send_headers _admin_menu
parse_query  admin_menu
pre_get_posts  admin_init
posts_selection current_screen
load-(page)
send_headers
pre_get_posts
posts_selection
wp  wp
template_redirect 
get_header
wp_enqueue_scripts admin_enqueue_scripts
wp_head
 admin_print_styles-(hookname)
 admin_print_styles
 admin_print_scripts-(hookname)
 admin_print_scripts
wp_print_scripts  wp_print_scripts
 admin_head-(hookname)
 admin_head
 adminmenu
in_admin_header
get_search_form admin_notices
all_admin_notices
loop_start restrict_manage_posts
the_post the_post
get_template_part_content  pre_user_query
loop_end 
get_sidebar 
dynamic_sidebar 
get_search_form 
pre_get_comments 
wp_meta 
get_footer   in_admin_footer
get_sidebar 
wp_footer  admin_footer
wp_print_footer_scripts 
admin_bar_menu  admin_bar_menu
wp_before_admin_bar_render wp_before_admin_bar_render
wp_after_admin_bar_render wp_after_admin_bar_render
 admin_print_footer_scripts
 admin_footer-(hookname)
shutdown shutdown
wp_dashboard_setup

 

Starting Up A Plugin

I use PHP autoloading to eliminate require and include statements throughout the code.   It requires a consistent directory structure and file naming convention.  If you follow PHP best practices this should not be an issue.  I also recommend naming your PHP files after the class they contain and keeping every class in its own file.

Here is an example from my latest side project that sets up the autoloader and loads up the bulk of the plugin code when the WordPress plugins_loaded hook is fired.   It also exits the plugin early if a WordPress heartbeat comes in since this plugin, like 99% of those out there, has no need to listen to the heartbeat and load up all the overhead.

 

Loading Code As Needed

Rather than load up a pile of code into memory when a plugin loads, I prefer to break down my code into classes that are only loaded as needed.   No need to load up all that admin settings page overhead if we are only rendering a shortcode for a user on the front-end.

I use plugins_loaded to fire up my main plugin class and let the construction of that class manage the logic of when to load up the other modules.

 

Front End Only Processing

The main plugin loads a class that runs several methods during construction, adding hooks to admin_menu to load up the admin class when on the back end, testing for Cron or AJAX calls, and checking is_admin() and loaded the front end if NOT running an admin page.

Admin Only Processing

The admin_menu Hook

I often use the admin_menu hook to load up classes that are only needed on admin pages.   My typical structure is to have a controlling MyClass_Admin object which loads via admin_menu and within that add intelligence to the loading by relegating any code that does not have to run during the admin_menu hook into further subclasses such as MyClass_Admin_Init which is loaded up via a method attached to the ‘admin_init’ hook.

I use this method if I do not need to load up features earlier in the call stack (Stage 1 Loading) such as tweaking the admin_bar.

 

The is_admin() Test

This built-in WordPress function can also be used to check if admin pages are being loaded.   I tend to leave this as a sanity check to ensure we are on an admin page within my objects.  This avoids the overhead of calling and is_admin() test on every single page load, though I’ve not yet tested if this is more or less efficient than tying into the admin_menu hook.

I use this method if I need things to happen earlier in the call stack than admin_menu allows such as modifying the admin menu bar.

AJAX Only Processing

I often load this when the main plugin is loaded, which gets invoked via the plugins_loaded hook.    I typically use a method in the class that is loaded to manage the plugin that does a simple AJAX test:

Cron Only Processing

Similar to AJAX, Cron can fire at any time.  It too is managed via plugins_loaded with the main plugin class and has a check for DOING_CRON:

 

 

WordPress 4.8.2 Data Query Woes

Millions of websites across the Internet were automatically updated to WordPress 4.8.2 yesterday.    Thousands of those sites have key components that are no longer working.   Many of those sites will be looking for new plugins and themes unless new patches are made to make those themes and plugins 4.8.2 compatible.   WordPress 4.8.2 Data Query WoesWordPress 4.8.2 Data Query WoesWordPress 4.8.2 Data Query Woesn the meantime there are going to be a lot more partially broken websites online for the next month.

Unfortunately for my Store Locator Plus add ons, we build and test against the latest “nightly” builds which has been on WordPress 4.9 beta for a while.  Somehow the changes to the prepare method did not make it to that build on our test systems until 36 hours ago; that is about 3 hours AFTER we finished testing the latest 4.8.4 updates.  The very same locator updates that we published just over 30 hours ago.

What Changed?

WordPress changed a key component of the system architecture that is used by thousands of plugins, the database management interface.    The prepare() function of the WordPress Database class (WPDB) is used to manage data queries and replace placeholders with variables when performing searches.

As part of the WordPress 4.8.2 security update the ability to use a variety of different standardized printf formats, where a %d represents a number or a %s represents a string as a placeholder, was severely restricted.    As of 4.8.2 any plugin or theme that uses “undocumented’ or “unsupported” formats will no longer work.    To be more specific, any plugins or themes using the placeholders WordPress deems undocumented are not supported.

The officially supported placeholders are limited to:

Many plugins and themes use positional arguments like %1s or , as is the case in my Store Locator Plus Power add on, use the more accurate %u placeholder instead of %d.   These plugins and themes no longer work as of 4.8.2.

The Fix

Any plugin and theme that uses the wpdb->prepare() call, and they all should if they are processing data queries with variables being passed in, will need to change the string formats to use the 4 approved placeholders noted above.

Why %d and %u Are Important

In my case I elected to use the standard printf %u format which is an unsigned integer format.   The reason I chose this option is because the WordPress standard for data storage of keys in WordPress tables is the MySQL unsigned integer.

What’s he difference?   Both PHP and MySQL store 2 billion values in a signed integer and 4 billion in an unsigned integer.    That means you can have TWICE as many records in your database with unsigned integers but more importantly you avoid possible confusion.   Can you imagine telling someone “No, no, no, I meant record number NEGATIVE 1357 not POSITITVE 1357”?

WordPress decided that they would limit the standard prepare to the %d , signed integer format which is limited to 2 billion unique entries.  Granted that will cover nearly any real world WordPress install, but for the one site that hits record 2-billion-and-one there is likely to be trouble.

Today, I brought this up on the WP Core ticket system.   Maybe they’ll change it.  Maybe not.  In the meantime I’m replacing all %u references with %d because my client’s don’t care about technical merit but rather just want their sites to stay running.

 

Update:  on my PHP7 based system the “internal workings” of PHP don’t change how %d and %u are working.  That’s good news.

You can run this same sprintf() on your box to verify %d and %u are interchangeable, but for WPDB ALWAYS use %d.   Now to change a lot of code.

PHP Autoload and Singleton(ish) Model

Once every couple of years I take a month off from hacking away at the Store Locator Plus products and delve into some personal projects.  It is a way to learn some new things and try out new techniques without breaking the locator product.   With the locator being my primary source of income these days it is important to keep that intact while “trying new things”.

Some of the things I’ve been working on this week include Backbone, Bootstrap, React, Underscores, some new REST techniques, and a simplified object model for my internal “who cares about backward compatibility” PHP 7 projects.

One of the things I started playing with was autoload functionality.  Yes, it has been around since PHP 5, but it had a major overhaul in 7 and as such I think it is ready for production use.    Here are some things I learned along the way.

<tl;dr/>

If you employ autoloading classes use a singleton-style model.

Autoloading makes it tempting to litter your code with $obj=new Thing(); $obj->do_it();   and later in another function do a similar operation with $obj2=new Thing(); $obj->do_something_else();.

If you don’t use a singleton-style model you are spewing pointers all over the place and chewing up small chunks of memory.   Those pointers can be a few-hundred bytes to several Kb.  Memory usage is less of a concern these days but  allocation and de-allocation (garbage collection) takes time.    These days performance is a critical design feature of any web or mobile app.

The Self-Managed Loader

In some of my older PHP code I use a self-managed loading mechanism.  Throughout the code I use a require_once( “… class file …”) call.    The PHP file uses the class_exists() function to wrap the entire class as well as a small snippet of code that checks a “global list of instatiated object pointers”.   Before you start going off about “God objects”, that is not  how this architecture works.   The list is an array of object pointers I can test to see if a class is already instantiated and if it is I use that pointer.   The objects do not require the main list to function and can live independently as a truly independent object in most cases. I know with my application architecture that each class that uses that method should only EVER be employed once so I use the “object list” to enforce that.

However I didn’t like all of the require_once() calls littering my code.   It slightly more code clutter and my guess is that checking the file resources table in PHP on every one of those calls is likely far less efficient than checking a symbol table (or PHPs version of one).

And example of my “require registration” code…

private function get_string_default( $key ) {
   global $slplus;
   require_once( SLPLUS_PLUGINDIR . 'include/module/i18n/SLP_Text.php' );
   $text_to_return = $slplus->Text->get_text_string( array( 'option_default', $key ) );
}


if ( ! class_exists( 'SLP_Text' ) ) {
   class SLP_Text extends SLP_Object_With_Objects {
     public function get_text_string( $slug ) { return $slug; }
   }

   global $slplus;
   if ( is_a( $slplus, 'SLPlus' ) ) {
      $slplus->add_object( new SLP_Text() );
   }
}

class SLPlus {
  public function add_object( $object ) {
   if ( ! is_object ( $object ) ) {
      return;
   }
   $key = preg_replace( "/^{$this->class_prefix}/" , '' , get_class( $object ) );
   if ( empty( $key ) ) {
      return;
   }
   $this->objects[ $key ] = array( 'object' => $object );
  }
}

Every single class has the class_exists() test wrapper and the global $slplus with the add_object() call.   Inside the SLPlus class I have some utilities to simplify property names and use a __get() magic method so I can reference $slplus->Text to get the single instantiation of the SLP_Text object.

Autoloading

PHP 5 had an autoload function whenever you tried to instantiate a class but forgot to require the PHP class definition file.  You could write a function that said “go look in this directory and require the source”.    PHP 7 reworked this function and gave it a new name.  It is more intelligent about autoloading, to the point where the older autoload() is deprecated in PHP 7.2.    I’m going to only refer to the new”ish” spl_autoload_register() methods here.   The ramifications on HOW you implement this (the Singleton thing mentioned above) are the same regardless.

As noted, autoloading is a way for PHP to perform a last-ditch effort to keep code from crashing when you reference a class but forgot to include the source explicity in your code.    It is a great way to reduce code clutter.    The simple version is you write a function that takes the class name that is missing and tells PHP how to go about finding where that file might be in your source architecture, then usually requiring it to avoid a crash.

Here is the overview from my new RUIn (Are You In?  or “Ruin”) side project.   This is a base class for a custom WordPress Theme I am building for this project.    It does use the class test only because I need at least one global object to reference for other functionality in my theme.  It will also be helpful for the singleton-style model I will describe later.

In my projects I make sure all of my PHP file names match the class name they contain.  This is a general best practice that many PHP developers employ.  It makes your life a LOT easier as you can do some neat code tricks like you see here.   It also means moder IDEs , including my favorite phpStorm, can be far more intelligent about your code (auto-complete, grunt and npm task managers, syntax checkers, code smell, and a lot of other tools need far less wrangling).   The means I can use something as simple as the following code to instantiate my objects when needed.

if ( ! class_exists( 'RUInTheme' ) ) :
   class RUInTheme {
   
      public function __construct() {
         spl_autoload_register( [ $this , 'auto_load' ] );
      }

      public function auto_load( $class_name ) {
         if ( strpos( $class_name, 'RUInTheme_' ) === FALSE ) return;
         require_once( RUInTheme_DIR . '/inc/module/' . $class_name . '.php' );
      }
   }

   $GLOBALS[ 'RUInTheme' ] = new RUInTheme();

endif;

 

Now anywhere in my code I need a new instantiation of an object I can call $obj = new RUInTheme_Thingy(); and it magically includes the source.   My architecture dictates all modules go in the /inc/module/ directory and start with RUInTheme_ if they are part of this application.     Nice.  No more requires throughout my code.

Here is an extended example that loads up WordPress admin functions only when the user is a logged in user looking at backend admin pages or loads UI functions when someone is viewing a front-end page.   It means that the app is not loading memory up with code that is never going to be executed such as UI features that aren’t used on an admin page and vice-versa.

 

<?php
if ( ! class_exists( 'RUInTheme' ) ) :
   class RUInTheme {
      public $objects;

      public function __construct() {
         require( 'RUInTheme_Object.php' );
         spl_autoload_register( [ $this , 'auto_load' ] );
         $this->add_hooks();
      }

      public function __get( $property ) {
         if ( property_exists( $this, $property ) ) return $this->$property;
         if ( array_key_exists( $property , $this->objects ) ) return $this->objects[ $property ];
      }


      private function add_hooks() {
         if ( is_admin() ) RUInTheme_Admin::get_instance(); // Admin Only Stuff
         if ( ! is_admin() ) RUInTheme_UserInterface::get_instance(); // UI Only Stuff
      }

      public function auto_load( $class_name ) {
         if ( strpos( $class_name, 'RUInTheme_' ) === FALSE ) return;
         require_once( RUInTheme_DIR . '/inc/module/' . $class_name . '.php' );
      }


   }

   $GLOBALS[ 'RUInTheme' ] = new RUInTheme();

endif;

Now to extend my application I only need to add a class and matching file name in the inc/module/ directory and use new ClassName() in my code. Sweet!

The Un-Singletons

Now what about those singletons and the memory issue?

I quickly discovered that having super-clean readable code was cool but I was pissing memory all over the floor and mucking up my server.   That’s fine for my local Vagrant box (thought it was breathing heavy when hammering the app) but on a production server that would not be a nice “feature”.    Here is what I was doing as I didn’t need to reference the object just employ the methods:

/**
 * WP Hooks
 */
private function add_hooks() {
   if ( is_admin() ) new RUInTheme_Admin(); // Admin Only Stuff
   if ( ! is_admin() ) new RUInTheme_UserInterface(); // UI Only Stuff
}

Functionally this was fine.  The constructor setup the proper WordPress hooks and the methods did their magic whenever I needed them to.   Great clean clode.   Code that happens to piss memory all over the floor just for fun.

No, the memory leak was not a huge issue.  A small 320 byte pointer that was never referenced.  In reality the memory was not a leak as the architecture only calls these objects once and they are never re-used.  Whether or not I assigned the new object to a variable the memory consumption would not change.    Not a big deal until you start getting into larger more complex apps.

The problem with larger apps is you will likely end up with a class that has an object that is polymorphic, or has independent properties for each element of a list of objects, or some other recursive deployment.     In a very useless example, you would end up with dozens of 300-700 byte pointers hanging around a give PHP a lot more clean-up work with something like this:

function setup() { $obj = new Theme_Admin(); $obj->setup(); }
function update() { $obj = new Theme_Admin(); $obj->update(); }
function breakdown() { $obj = new Theme_Admin(); $obj->breakdown(); }

While this is an over-simplified example that can be optimized in many ways you can imagine this being part of multiple independent classes such as an AJAX or REST processor that can not be certain the Theme_Admin() object has been invoked and does not have public properties to share the instantiation.    For these to be truly independent objects they each must employ their own pointers to the object.   Luckily PHP 7 is smart enough to handle the object definition itself in shared memory , signficantly reducing memory load and increasing performance, but there is still a lot of excess overhead with this model.

There is a better way.

A Singleton Autoloaded Model

Use a PHP class version of a singleton by employing a static instance property in your class.    Add it to a public method and instead of using a new call to deploy an object you use your instance manager method.     This ensures all of your PHP references that use this model get back the same memory pointer.    Less overhead for PHP overall and with a large or highly recursive application a lot less memory usage.

Since I employ this method for nearly all of my classes in my application I create a base object that each of my auto-loaded classes will extend.    To invoke a new object I use the static call to the class with something like Theme_Admin::get_instance().   Much cleaner than a long require_once() with a path.

Here is the full example:

RUInTheme_Object

<?php
defined( 'ABSPATH' ) || exit;
if ( class_exists( 'RUInTheme_Object' ) ) return;

/**
 * Class RUInTheme_Object
 *
 * A class that some objects will extend when they need to be a singleton connected to the RUInTheme global object.
 */
class RUInTheme_Object {

   /**
    * Return an instance of the object which is also registered to the RUInTheme global less the RUInTheme_ part.
    * @return mixed
    */
   public static function get_instance() {
      static $instance;

      if ( ! isset( $instance ) ) {
         $class = get_called_class();
         $instance = new $class;

         $short_class = str_replace( 'RUInTheme_' , '' , $class );
         $GLOBALS[ 'RUInTheme' ]->objects[ $short_class ] = $instance;
      }

      return $instance;
   }
}

 

RUInTheme

<?php
defined( 'ABSPATH' ) || exit;
if ( defined( 'DOING_AJAX' ) && DOING_AJAX && ! empty( $_POST[ 'action' ] ) && ( $_POST[ 'action' ] === 'heartbeat' ) ) return;

if ( ! class_exists( 'RUInTheme' ) ) :

   /**
    * Class RUInTheme
    *
    * @property        array       $objects        Arrays of instantiated singletons.  The key is the class name less the leading RUInTheme_ part.
    *
    * Auto-registered singletons so our phpStorm autocomplete is smarter...
    *
    * @property-read   RUInTheme_StartARuin_Shortcode  StartARuin_Shortcode
    */
   class RUInTheme {
      public $objects;

      /**
       * RUInTheme constructor.
       */
      public function __construct() {
         require( 'RUInTheme_Object.php' );
         spl_autoload_register( [ $this , 'auto_load' ] );
         $this->add_hooks();
      }

      /**
       * Get our properties, including registered objects.
       *
       * @param $property
       *
       * @return mixed
       */
      public function __get( $property ) {
         if ( property_exists( $this, $property ) ) return $this->$property;
         if ( array_key_exists( $property , $this->objects ) ) return $this->objects[ $property ];
      }

      /**
       * WP Hooks
       */
      private function add_hooks() {
         if ( is_admin() ) RUInTheme_Admin::get_instance(); // Admin Only Stuff
         if ( ! is_admin() ) RUInTheme_UserInterface::get_instance(); // UI Only Stuff
      }

      /**
       * Autoload objects.
       *
       * @param string $class_name
       */
      public function auto_load( $class_name ) {
         if ( strpos( $class_name, 'RUInTheme_' ) === FALSE ) return;
         require_once( RUInTheme_DIR . '/inc/module/' . $class_name . '.php' );
      }


   }

   $GLOBALS[ 'RUInTheme' ] = new RUInTheme();

endif;

RUInTheme_Admin

<?php
defined( 'ABSPATH' ) || exit;

/**
 * Class RUInTheme_Admin
 *
 * Things that are run on admin pages.
 */
class RUInTheme_Admin extends RUInTheme_Object{

   /**
    * RUInTheme_Admin constructor.
    */
   public function __construct() {
      add_action( 'admin_enqueue_scripts' , [ $this , 'give_us_style' ] );
   }

   /**
    * Admin CSS
    */
   public function give_us_style() {
      wp_register_style( 'ruin_admin_style' , RUInTheme_URL . '/assets/sass/wpadmin.min.css' );
      wp_enqueue_style( 'ruin_admin_style' );
   }
}

The Object List

You may have noticed I still have a __get() method in the main class as well as the objects property.    Even with the singleton method and autoloading I find it far easier to reference my instantiated objects from auto-derived properties on the main object.    This way I know that all of my primary utilities can be referenced via $MainObj->ClassNameWithoutBase.

I can also be confident that since I’m using the Singleton style implementation I can also call my get_instance() for the classes I need whenever I am starting a logic path and later reference the methods.

In my example below I do manually define global $RUInTheme and provide a @var code hint.  This is so phpStorm auto-completes all my method calls and type-checks parameters in real-time (far fewer bugs, yay!).  I could get away with cleaner code by referencing $GLOBALS[‘RUInTheme’]-><classname>->method() but I opt for faster development and syntax checking over saving a line of text.

Here is the UI code from RUInTheme that makes use of the Singleton which auto-registers itself on the RUInTheme base object using a shorthand (dropping the RUInTheme_ prefix I use on all classses in this project).

RUInTheme_UserInterface.php

<?php
defined( 'ABSPATH' ) || exit;

/**
 * Class RUInTheme_UserInterface
 *
 * Things that are run on UI pages only.
 */
class RUInTheme_UserInterface extends RUInTheme_Object {

   /**
    * RUInTheme_Admin constructor.
    */
   public function __construct() {
      add_action( 'wp_enqueue_scripts' , [ $this , 'give_us_style' ] );
      add_shortcode( 'start_a_ruin' , [ $this , 'start_a_ruin' ] );
   }

   /**
    * Admin CSS
    */
   public function give_us_style() {
      wp_register_style( 'ruin_ui_style' , RUInTheme_URL . '/assets/sass/master.min.css' );
      wp_enqueue_style( 'ruin_ui_style' );

      wp_enqueue_script( 'ruin_ui_scripts' , RUInTheme_URL . '/assets/scripts/ruin_ui.min.js' , [ 'backbone' ] , RUInTheme_VERSION , true );

      if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
         wp_enqueue_script( 'comment-reply' );
      }
   }

   /**
    * [start_a_ruin] processor.
    *
    * @var RUInTheme   $RUInTheme
    *
    * @return string               The shortcode HTML.
    */
   public function start_a_ruin() {
      RUInTheme_StartARuin_Shortcode::get_instance();
      
      global $RUInTheme;
      return $RUInTheme->StartARuin_Shortcode->get_shortcode_html();
   }
}

RUInTheme_StartARUIn_Shortcode

<?php
defined( 'ABSPATH' ) || exit;

/**
 * Class RUInTheme_StartSomething_Shortcode
 *
 * Render the [start_a_ruin] shortcode.
 */
class RUInTheme_StartARuin_Shortcode extends RUInTheme_Object{

   /**
    * Display the HTML for [start_a_ruin]
    */
   public function get_shortcode_html() {
      $text['start_a_ruin']   = __( 'Create A Group Text' , 'ruin' );
      $text['list_name']      = __( 'Tell people what your list is about. ' , 'ruin' );
      $text['list_name_help'] = __( 'A short description so people know what your list is about. ' , 'ruin' );
      $text['create']         = __( 'Create' , 'ruin' );

      $HTML = <<<HTML
      <div class="container-fluid">
         <div class="row">
         
            <!-- column 1 -->
            <div class="col visible">
                    <button id="start_a_ruin_button" type="submit" class="btn btn-primary">{$text['start_a_ruin']}</button>                               
            </div>
            
            <!-- column 2 -->
            <div class="col start_step_one hidden">
               <form id="start_a_ruin_step_one">
                  <div class="form-group">
                     <input type="text" class="form-control" id="list_name" placeholder="{$text['list_name']}" aria-label="{$text['list_name']}" aria-describedby="list_name_help">                
                     <small id="list_name_help" class="form-text text-muted">{$text['list_name_help']}</small>
                  </div> 
                  <button type="submit" class="btn btn-primary">{$text['create']}</button>                              
               </form>
            </div>          
         </div>
      </div>

      <!-- Backbone View -->
      <div id="list-app">
         <ul class="list-list"></ul>       
      </div>

      <!-- Backbone List Template -->
      <script type="text/template" id="list-item-tmpl">
         <p><a href="/wp-json/ruinbot/<%= id %>"><%= name %></a></p>
         <p>ARN: <i><%= arn %></i></p>
         <button class=""remove">Delete</button>          
      </script>
HTML;

      return $HTML;
   }
}

That is the guts of a fully functional WordPress theme with custom shortcodes, a Backbone processor, and is easily extensible with self-checking code (thanks to phpStorms) and object loading.     It is memory efficient in the new responsive JavaScript app being driven by the WordPress REST API.

No, it is not the perfect model, but it is a smarter model for the architecture I am deploying.    Some of this may even find its way back into Store Locator Plus and some of the other projects I have online ; at least the PHP 5.2 compatible parts for my WordPress projects.   Maybe someday WordPress will bump the requirement to PHP 5.6 and I can make more use of some super-cool PHP tricks.

Adding Columns To WordPress Screen Options

Adding the Screen Options drop down to a custom WordPress admin page takes some trickery which was described in the prior Screen Options blog post.  Using the “magic sauce” of the per_page option and adding it to the class that renders your admin page and handles page interaction is an easy way to get that started.    Assuming you already have the Screen Options rendering with something on there, probably Pagination settings, you can now add a column selector.   This is especially useful for admin pages built on top of the WordPress WP_List_Table (for private use only but everyone-and-their-brother is using it).

Getting The List Of Columns

The easiest way to get the list of columns to appear is to add a manage_{$screen-id}_columns filter to your app.   In the Store Locator Plus location manager I am setting this up in the class constructor as the WP_Screen interface likes to know about the columns very early in the process; well before the page rendering happens.

First we need to get our current screen.  This makes it easy to ensure your screen->id needed for the filter is an exact match for what the filter trigger will be looking for.  This is managed in the get_wp_screen() method in my example.

We then need to tell WordPress to run our manage_{$screen-id}_columns code, which is the private manage_columns() method in this example.   It accepts a list of already-in-play column slugs and titles, usually empty, and should send back a named array with the key set to the slug and the value set to the title.

Since our class is a WP_List_Table derivative and we already wired up the screen, the screen_options() WordPress function will now see our list of column names as stored in the static variable managed in the WordPress Core screen.php function get_column_headers.

The Code

<?php
defined( 'ABSPATH'     ) || exit;
if ( ! class_exists( 'SLP_Admin_Locations' ) ) {
    require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );

    class SLP_Admin_Locations extends WP_List_Table {
        private $wp_screen;

        /**
         * Called when this object is created.
         */
        function __construct( $args = array() ) {
            $screen = $this->get_wp_screen();
            add_filter( 'manage_' . $screen->id . '_columns' , array( $this , 'manage_columns') );
        }

       /**
        * Set the columns we will render on the manage locations page.
        */
       public function get_columns() {
          $this->columns = array(
             'sl_id'               => __( 'Actions', 'store-locator-le' ),
             'sl_store'            => __( 'Name', 'store-locator-le' ),
             'sl_address'          => __( 'Address', 'store-locator-le' ),
             'sl_address2'         => __( 'Address 2', 'store-locator-le' ),
             'sl_city'             => __( 'City', 'store-locator-le' ),
             'sl_state'            => __( 'State', 'store-locator-le' ),
             'sl_zip'              => __( 'Zip', 'store-locator-le' ),
             'sl_country'          => __( 'Country', 'store-locator-le' ),
             'sl_initial_distance' => __( 'Distance', 'store-locator-le' ),
             'sl_description'      => __( 'Description', 'store-locator-le' ),
             'sl_email'            => $this->slplus->WPML->get_text( 'label_email' ),
             'sl_url'              => $this->slplus->WPML->get_text( 'label_website' ),
             'sl_hours'            => $this->slplus->WPML->get_text( 'label_hours' ),
             'sl_phone'            => $this->slplus->WPML->get_text( 'label_phone' ),
             'sl_fax'              => $this->slplus->WPML->get_text( 'label_fax' ),
             'sl_image'            => $this->slplus->WPML->get_text( 'label_image' ),
          );

          return $this->columns;
       }

       /**
        * Get a list of all, hidden and sortable columns, with filter applied
        *
        * @return array
        */
       public function get_column_info() {
          if ( ! isset( $this->_column_headers ) ) {
             $this->_column_headers = array(
                $this->get_columns(),
                array(),
                array(),
                'sl_id',
             );
          }

          return $this->_column_headers;
       }

       /**
        * Get the wp_screen property.
        */
       private function get_wp_screen() {
          if ( empty( $this->wp_screen ) ) {
             $this->wp_screen = get_current_screen();
          }
          return $this->wp_screen;
       }

       /**
        * Set up our screen columns.
        *
        * Impacts screen options column list.
        *
        * @param   array   columns     the existing columns
        * @return  array               key = field slug, value = title
        */
       public function manage_columns( $columns ) {
          $this->get_column_info();
          return $this->_column_headers[0];
       }
    }
}

The User Experience

If everything is wired up properly you should see the screen options drop down with a new Columns header and list of check boxes that  let the user choose which columns to show on the interface.   Here is an example from the Manage Locations interface of Store Locator Plus when we have the Experience and Power add ons active.

Column Selector 2017-06-16_10-50-08.png

Adding Screen Options To WordPress Admin Pages

Many of the built-in WordPress admin pages have a Screen Options drop-down tab on the top right of the page.   This is used to set how many items are shown in the default WordPress tables, such as the list of pages or posts.   Some of the WordPress admin interfaces also allow you to set which columns to show or hide on the page.

As a plugin or theme author it provides a consistent interface for your users if you utilize this built-in feature as a way to control the admin user experience for your users.   I’ve been busy adding this to the Store Locator Plus Manage Locations interface for version 4.8 and found the documentation to be lacking at best.   Here are my notes on how I hacked this into existence.

The Steps

There are a few basic steps to the process;  Tell WordPress you have screen options you want to track, Save those options when they are changed, Implement those options while rendering the page.

Seems easy enough, but they are all wired together in what is a little less elegant that some of the other components of WordPress Core.  Hopefully these notes will help you get starts on how to do this properly.

Tell WordPress You Have Options

This is done by adding options using the WordPress add_screen_option() method.   It should be called via the WordPress load-<hook> action hook to ensure the proper WP_Screen environment is active when this function is called.

load-{$page_hook} is called from within the base WordPress admin.php call.    This comes AFTER the admin_init hook. Which means your instructions to add screen options to an admin page can come much later than the “Save The Options” code below.   However, you will need to know the page you are hooked into.    Since most admin pages are accessed by adding an entry to the WordPress admin menu, you should be able to store the page hook when calling add_menu_page() or add_submenu_page() to attach your custom admin page to the WordPress admin interface.   This is also a good place to add your hook to setup the screen options.

In this example code I use a named array in $this->menu_items to build a list of several pages I want to add to the admin interface.   It has several properties including the name of the method that will render my page and the name of the method that will add my screen options.    It will also store the hook.     This SLP_AdminUI class is a simple “traffic cop” that loads other classes that do the work so we can isolate code to the specific page being processed.

class SLP_AdminUI extends SLP_BaseClass_Admin {

/**
 * Create the admin menu.
 *
 * Roles and Caps
 * manage_slp_admin
 * manage_slp_user
 *
 * WordPress Store Locator Plus Menu Roles & Caps
 *
 * Info : manage_slp_admin
 * Locations: manage_slp_user
 * Settings: manage_slp_admin
 * General: manage_slp_admin
 */
public function create_admin_menu() {

$this->menu_items['slp_manage_locations'] =
   array(
      'label'          => __( 'Locations', 'store-locator-le' ),
      'slug'           => 'slp_manage_locations',
      'class'          => $this,
      'function'       => 'renderPage_Locations',
       'screen_options' => 'slp_manage_locations_screen_options' ,
   );


// Attach Menu Items To Sidebar and Top Nav
//
foreach ( $this->menu_items as $slug => $menu_item ) {

  $this->menu_items[ $slug ][ 'hook' ] = add_submenu_page(
   SLPLUS_PREFIX,
   $menu_item['label'],
   $menu_item['label'],
   $slpCapability,
   $menu_item['slug'],
   array( $menu_item['class'], $menu_item['function'] )
  );
  add_action( 'load-' . $this->menu_items[ $slug ][ 'hook' ] , array( $this , $this->menu_items[ $slug ][ 'screen_options' ] ) );
}

}



/**
 * Render the Locations admin page.
 */
function renderPage_Locations() {
   $this->slplus->Admin_Locations->render_adminpage();
}

/**
 * Add Manage Locations Screen Options
 */
public function slp_manage_locations_screen_options() {
   $this->slplus->Admin_Locations->add_screen_options();
}



}

 

And the page-specific class that does the per-page magic…

class SLP_Admin_Locations extends WP_List_Table {

/**
 * Add screen options.
 */
 public function add_screen_options() {
 add_screen_option( 'per_page', array( 'option' => 'admin_locations_per_page' , 'label' => __( 'Locations Per Page' , 'store-locator-le' ) , 'default' => 50 ) );
 }
}

Save The Options

Options are saved via the generic ‘set-screen-option’ filter in WordPress.   The option is called via the set_screen_options() function as part of every single WordPress admin.php call.   At least there is top-of-function short circuits that will drop out of the process for a variety of reasons which means the majority of admin pages that do not have screen options will not process much code before moving on.   For those pages that do have screen options, here are the important notes.

You must add the filter to set the screen options by employing the WordPress set-screen-option filter your code.   Since this is fired off very early in the process, basically at the start of WordPress when the admin page is processed, you need to add the filter early in the WordPress feature stack.   Calling it too late, for example inside your WordPress ‘admin_menu’ action hook functions, will not help you.  I’ve hooked my set-screen-options to the WordPress init action.

set-screen-option filter call stack 2017-06-15_09-20-20.png

The call stack an variable references from WordPress Core 4.8 when the set-screen-option filter if applied.

Note: Finding the right point of where to hook your code into WordPress is a key to leveling up your WordPress Foo.   You can find the general order of action hooks on the old-school WordPress Codex.

WordPress Admin Hooks 2017-06-15_09-54-46.png

The filter passes in 3 parameters and you really should be using object-oriented programming techniques so the full call should look something like the code below.  The SLP_Actions init() method is called by the main plugin code via the WordPress init action hook: add_action( ‘init’ , array( $this->Actions , ‘init’ ) ).  The save_screen_options() method in this action class is only a traffic cop; it will load up the proper code module based on what page is being processed by WordPress and call the save_screen_options() method for that specific page.  In this example it is the Manage Locations page.

class SLP_Actions extends SLPlus_BaseClass_Object {

/**
 * Called when the WordPress init action is processed.
 *
 * Current user is authenticated by this time.
 */
public function init() {
 add_filter( 'set-screen-option' , array( $this , 'save_screen_options' ) , 10 , 3 );
}


/**
 * Save screen options.
 *
 * @param boolean $status false by default, return this to not save options
 * @param string  $option the option name, a key in user_meta
 * @param mixed   $value  the option value for user_meta
 * @return mixed
 */
public function save_screen_options( $status, $option, $value) {
   $this_page = isset( $_REQUEST['page'] ) ? $_REQUEST['page'] : '';
   switch ( $this_page ) {
      case 'slp_manage_locations':
         require_once( SLPLUS_PLUGINDIR . 'include/module/admin_tabs/SLP_Admin_Locations.php' );
         return $this->slplus->Admin_Locations->save_screen_options( $status, $option, $value );
         break;
   }
   return $status;
}

}

This SLP_Admin_Locations class is only loaded when the Locations tab is active in the Store Locator Plus plugin.    This ensures less memory is consumed when other WordPress admin pages are loaded and keeps our screen options isolated to just those we are interested in for this page.

class SLP_Admin_Locations extends WP_List_Table {
/**
 * Save screen options.
 *
 * @param $status
 * @param $option
 * @param $value
 * @return mixedf
 */
public function save_screen_options( $status, $option, $value) {
 $valid_options = array( 'admin_locations_per_page' );
 if ( in_array( $option , $valid_options ) ) return $value;
 return $status;
}
}

If the filter, save_screen_options() of SLP_Admin_Locations in this example, returns a value other than false then the set_screen_options() of WordPress will call

update_user_meta($user->ID, $option, $value);

Implement The Options

Implementing the options will depend on  your specific use case.   In my case I’m using the per_page setting to determine how many locations to show in a locations table.  It is a modified version of WP_List_Table so the typical prepare_items() examples do not apply.   In my class I’ve also decided to implement the WP_Screen per_page standard of fetching the user_meta with get_user_option() and fall back to the WP_Screen _options property fallbacks for default values.

Here is the function I call in various places to limit how many locations are shown in my table.

/**
 * Get the screen option per_page.
 * @return int
 */
private function get_screen_option_per_page() {
   $this->get_wp_screen();
 $option = $this->wp_screen->get_option( 'per_page', 'option' );
 if ( ! $option ) {
  $option = str_replace( '-', '_', "{$this->wp_screen->id}_per_page" );
 }

 $per_page = (int) get_user_option( $option );
 if ( empty( $per_page ) || $per_page < 1 ) {
  $per_page = $this->wp_screen->get_option( 'per_page', 'default' );
  if ( ! $per_page ) {
   $per_page = 20;
  }
 }
 return $per_page;
}

private function get_my_data() {
  $max_to_show = min( $this->get_screen_option_per_page() , $this->slplus->Location_Manager->location_limit );
}
Manage Locations Per Page Option 2017-06-15_15-27-19.png

The Manage Locations per-page screen option in SLP 4.8

Related Notes

Some interesting things I found along the way that may be worth noting…

The default per_page up/down toggle will fire the page update/save (form submit) every time someone clicks the toggle.  Ouch.   Typing a number an clicking the Apply button is a lot less server overhead.

The set-screen-option is a per-user setting.   It is stored in user_meta.    The $option value is the user meta key , and value is the value.     Using serialized options may be a good idea here but it will complicate code.  I’m a big fan of less data I/O, because that is a costly performance hit, over multiple get_option() or user_meta() requests that hammer the database.

The option name ‘per_page’ is super-special.   This single screen option name will alone trigger the Screen Options drop down to be active.   Sadly this is a standalone option in user_meta which means it will force at least TWO data I/O operations if you use this AND any other options (including a serialized option).  Ugggh, WordPress…

The Screen Options drop down will only render if you add a screen option with the name ‘per_page’, the page has meta boxes attached, or you have defined layout columns for the page. From the WP_Screen::show_screen_options() method:

$show_screen = ! empty( $wp_meta_boxes[ $this->id ] ) || $columns || $this->get_option( 'per_page' );

Browsers that disable JavaScript will not see the Screen Options drop-down.  WordPress will apply the hide-if-no-js class which hides elements that depend on JS to work properly.

%d bloggers like this: