By default, WordPress does not cache WP_Query queries. Doing so can greatly improve performance. The way I do this is via the Advanced Post Cache plugin:
By running this plugin (hopefully as an mu-plugin) with a persistent object cache, WP_Query calls, along with get_post() calls (only if suppress_filters is false) will be cached.
Bonus!
Now that we’re caching queries, here’s how I do a little extra caching to squeeze out a tiny bit more performance:
<?php// By default Jetpack does not cache responses from Instagram oembeds.
add_filter( 'instagram_cache_oembed_api_response_body', '__return_true' );
// Cache WP Dashboard Recent Posts Query
add_filter( 'dashboard_recent_posts_query_args', 'cache_dashboard_recent_posts_query_args', 10, 1 );
functioncache_dashboard_recent_posts_query_args( $query_args ){
$query_args['cache_results'] = true;
$query_args['suppress_filters'] = false;
return $query_args;
}
// Cache WP Dashboard Recent Drafts Query
add_filter( 'dashboard_recent_drafts_query_args', 'cache_dashboard_recent_drafts_query_args', 10, 1 );
functioncache_dashboard_recent_drafts_query_args( $query_args ){
$query_args['suppress_filters'] = false;
return $query_args;
}
// Cache comment counts, https://github.com/Automattic/vip-code-performance/blob/master/core-fix-comment-counts-caching.php
add_filter( 'wp_count_comments', 'wpcom_vip_cache_full_comment_counts', 10, 2 );
functionwpcom_vip_cache_full_comment_counts( $counts = false , $post_id = 0 ){
//We are only caching the global comment counts for now since those are often in the millions while the per page one is usually more reasonable.if ( 0 !== $post_id ) {
return $counts;
}
$cache_key = "vip-comments-{$post_id}";
$stats_object = wp_cache_get( $cache_key );
//retrieve comments in the same way wp_count_comments() doesif ( false === $stats_object ) {
$stats = get_comment_count( $post_id );
$stats['moderated'] = $stats['awaiting_moderation'];
unset( $stats['awaiting_moderation'] );
$stats_object = (object) $stats;
wp_cache_set( $cache_key, $stats_object, 'default', 30 * MINUTE_IN_SECONDS );
}
return $stats_object;
}
// Cache monthly media array.
add_filter( 'media_library_months_with_files', 'wpcom_vip_media_library_months_with_files' );
functionwpcom_vip_media_library_months_with_files(){
$months = wp_cache_get( 'media_library_months_with_files', 'extra-caching' );
if ( false === $months ) {
global $wpdb;
$months = $wpdb->get_results( $wpdb->prepare( "
SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
FROM $wpdb->posts
WHERE post_type = %s
ORDER BY post_date DESC
", 'attachment' )
);
wp_cache_set( 'media_library_months_with_files', $months, 'extra-caching' );
}
return $months;
}
add_action( 'add_attachment', 'media_library_months_with_files_bust_cache' );
functionmedia_library_months_with_files_bust_cache( $post_id ){
if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
return;
}
// What month/year is the most recent attachment?global $wpdb;
$months = $wpdb->get_results( $wpdb->prepare( "
SELECT DISTINCT YEAR( post_date ) AS year, MONTH( post_date ) AS month
FROM $wpdb->posts
WHERE post_type = %s
ORDER BY post_date DESC
LIMIT 1
", 'attachment' )
);
// Simplify by assigning the object to $months
$months = array_shift( array_values( $months ) );
// Compare the dates of the new, and most recent, attachmentif (
! $months->year == get_the_time( 'Y', $post_id ) &&
! $months->month == get_the_time( 'm', $post_id )
) {
// the new attachment is not in the same month/year as the// most recent attachment, so we need to refresh the transient
wp_cache_delete( 'media_library_months_with_files', 'extra-caching' );
}
}Code language:HTML, XML(xml)
One of the most important things to do when working on new themes, plugins, or debugging issues in WordPress is to turn on WP_DEBUG. According to the Codex:
WP_DEBUG is a PHP constant (a permanent global variable) that can be used to trigger the “debug” mode throughout WordPress. It is assumed to be false by default and is usually set to true in the wp-config.php file on development copies of WordPress.
It’s common to edit the wp-config.php file every time you want to turn this off and on, but another way to do it is via a secret cookie:
Let’s say you’re going along your day, developing things, and fixing things, and making the world a better place when all of a sudden you get a call from a client that their website is broken!! After a bit of panicking and digging around, it turns out that they’ve been “optimizing” things by disabling random, but critical, plugins on the site.
The Solution
One way that you might fix this is to not install the plugins via the WordPress UI and require() them either in the theme directory, or as an mu-plugin. The downside with this is that you lose the ability to easily and auto-update the plugins (if you’re okay with that). You also the ability to easily see what plugins are active and installed in the admin UI.
The way I’ve got around this is to create this helper function inside an mu-plugin that allows the plugins to be installed and managed in the UI, but not disabled:
<?php/**
* Secures a plugin from accidental disabling in the UI.
*
* If a plugin is necessary for a site to function, it should not be disabled.
* This functionc can also optionally "force" activate a plugin without having to
* activate it in the plugin UI. Forcing activation will cause it to skip all
* core plugin activation hooks.
*
* @param string $plugin Plugin file to secure.
* @param boolean $force_activation Optional. Whether to force load the plugin. Default false.
*/functionemrikol_secure_plugin( $plugin, $force_activation = false ){
$proper_plugin_name = false;
// Match if properly named: wp-plugin (wp-plugin/wp-plugin.php).if ( file_exists( WP_PLUGIN_DIR . '/' . $plugin . '/' . $plugin . '.php' ) && is_file( WP_PLUGIN_DIR . '/' . $plugin . '/' . $plugin . '.php' ) ) {
$proper_plugin_name = $plugin . '/' . $plugin . '.php';
} else {
// Match if improperly named: wp-plugin/cool-plugin.php.if ( file_exists( WP_PLUGIN_DIR . '/' . $plugin ) && is_file( WP_PLUGIN_DIR . '/' . $plugin ) ) {
$proper_plugin_name = $plugin;
}
}
if ( false !== $proper_plugin_name ) {
if ( true === $force_activation ) {
// Always list the plugin as active.
add_filter( 'option_active_plugins', function( $active_plugins )use( $proper_plugin_name ){
// Crappy hack to prevent infinite loops. Surely there's a better way.global $emrikol_is_updating_active_plugins;
if ( true === $emrikol_is_updating_active_plugins ) {
unset( $emrikol_is_updating_active_plugins );
return array_unique( $active_plugins );
}
if ( ! in_array( $proper_plugin_name, $active_plugins, true ) ) {
$active_plugins[] = $proper_plugin_name;
$emrikol_is_updating_active_plugins = true;
update_option( 'active_plugins', array_unique( $active_plugins ) );
}
return array_unique( $active_plugins );
}, 1000, 1 );
}
// Ensure the plugin doesn't get disabled somehow.// TODO: Diff arrays. Only run if the plugin is being removed.
add_filter( 'pre_update_option_active_plugins', function( $active_plugins )use( $proper_plugin_name ){
if ( ! in_array( $proper_plugin_name, $active_plugins, true ) ) {
$active_plugins[] = $proper_plugin_name;
}
return array_unique( $active_plugins );
}, 1000, 1 );
// Remove the disable button.
$plugin_basename = plugin_basename( $proper_plugin_name );
add_filter( "plugin_action_links_$plugin_basename", function( $links )use( $proper_plugin_name, $force_activation ){
if ( isset( $links['deactivate'] ) ) {
$links['deactivate'] = sprintf(
'<span class="emrikol-secure-plugin wp-ui-text-primary">%s</span>',
$force_activation ? 'Plugin Activated via Theme Code' : 'Plugin Secured via Theme Code'
);
}
return $links;
}, 1000, 1 );
}
}
Code language:HTML, XML(xml)
It’s not perfect, but it’s working for me right now. Like, right now on this site as you’re reading this. I’ve added it as an mu-plugin like so:
As you can see, this completely removes the “Deactivate” link in the UI:
The emrikol_secure_plugin() function takes two arguments:
The plugin to secure. This can either be the plugin slug (ex. jetpack) or the full plugin path if the plugin doesn’t follow standard naming conventions (wp-super-cache/wp-cache.php)
A boolean, defaults to false. If it is true the plugin will be forced to activate without user intervention. This can be used to activate a plugin on a new install without having to manually enable it in the UI or via WP-CLI
At WordPress.com VIP one of the features we have on our platform is automated concatenation of Javascript and CSS files when registered through the core WordPress wp_enqueue__*() functions.
This plugin was written to work with nginx, but the server running derrick.blog is Apache. I’ve worked around this and have nginx-http-concat running fully in WordPress, with added caching.
The bulk of the plugin is this file, which does all of the work of caching and calling the nignx-http-concat plugin: