SHORTINIT constant: WordPress environment with minimal load

I was worried about a question: how can I use $wpdb object and site database with which I work, but with minimal loading of WP environment. Sometimes it is necessary, when using Ajax to get or write data to the database and nothing else:

  • do not need filters.
  • do not need to check for user authorization.
  • do not need WordPress functions
  • do not need other checks and a bunch of loaded functions.

In general, when we don't need anything other than the ability to communicate with the database using the usual WordPress methods.

WordPress optimisation

You can solve such a problem by reading the database connection data from the file wp-config.php and connect to the database separately. But it's not very convenient and would require extra code, which in fact is already in the files of WordPress.

Since version 3.4, the developers of WordPress have taken care of this and added a SHORTINIT constant in wp-settings.php:

// Stop the main load of WordPress if we only want the base.
if ( SHORTINIT )
	return false;

It works like this:

// specify that we need a minimum of WP
define('SHORTINIT', true);

// Loading the WordPress environment
// WP does some checks and loads only the essentials to connect to the database
require_once( $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php' );

// Here we can communicate with the database.
// But practically no WP functions will work.
// Global variables $wp, $wp_query, $wp_the_query are not set.
global $wpdb;
$result = $wpdb->get_results(
	"SELECT post_title FROM $wpdb->posts WHERE post_type='post'"
);

if( $result ){
	foreach( $result as $post ){
		echo "$post->post_title <br>";
	}
}

Concrete numbers

To see how initializations with and without SHORTINIT differ. I measured: the number of SQL queries, the time to execute the code and the memory used. Here's what came up:

require_once( $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php' );
// 5 SQL in 0.1 sec. Memory: 14.92 mb

define( 'SHORTINIT', true );
require_once( $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php' );
// 0 SQL in 0.02 sec. Memory: 2.35 mb

Таким образом SHORTINIT, грубо, по всем параметрам снижает нагрузку в 5 раз. Неплохо!

What works with SHORTINIT

When using SHORTINIT the filter system: apply_filters() do_action() already works. The filters are basic (file: /wp-includes/default-filters.php). Those that you specified in functions.php of your theme and many others will not work.

None of the usual functions: esc_attr(), is_single(), the_content(), get_permalink() etc. will work. Here are all the functions that are plugged in - see /wp-settings.php:

Include files required for initialization.

If the WP_CACHE constant is enabled:

  • /wp-content/advanced-cache.php

Load early WordPress files.

Loads for multisite:

Authorization with SHORTINIT

Example of checking authorization and getting all user rights with SHORTINIT:

GitHub
<?php

// specify that we need a minimum from WP
define( 'SHORTINIT', true );

// Loading the WordPress environment
require_once( $_SERVER['DOCUMENT_ROOT'] . '/wp/wp-load.php' );

// A shortened version of wp_hash from pluggable.php
function wp_hash( $data ){
	$salt = LOGGED_IN_KEY . LOGGED_IN_SALT;

	return hash_hmac( 'md5', $data, $salt );
}

function hash_token( $token ){
	if( function_exists( 'hash' ) ){
		return hash( 'sha256', $token );
	}
	else{
		return sha1( $token );
	}
}

function curr_user_can( $capability ){
	global $all_caps;

	return isset( $all_caps[ $capability ] ) && $all_caps[ $capability ];
}

function get_curr_user_can(){
	global $wpdb;
	
	$_options = $wpdb->get_results( "
		SELECT `option_name`, `option_value` FROM $wpdb->options 
		WHERE `option_name` IN ('siteurl', '{$wpdb->prefix}user_roles')
	", 'OBJECT_K' );
	
	if( ! $_options ){
		return [ 'error', 'Options not found' ];
	}

	$_c_hash = md5( $_options['siteurl']->option_value );
	if( ! isset( $_COOKIE[ "wordpress_logged_in_$_c_hash" ] ) ){
		return [ 'error', 'No cookies.' ];
	}

	$cookie = $_COOKIE[ "wordpress_logged_in_$_c_hash" ];
	$cookie_elements = explode( '|', $cookie );
	
	if( count( $cookie_elements ) !== 4 ){
		return [ 'error', 'Cookie is broken' ];
	}

	$username = $cookie_elements[0];
	$expiration = $cookie_elements[1];
	$token = $cookie_elements[2];
	$hmac = $cookie_elements[3];

	if( $expiration < time() ){
		return [ 'error', 'Session time has expired' ];
	}

	$user = $wpdb->get_row( $wpdb->prepare(
		"SELECT * FROM $wpdb->users WHERE `user_login`=%s", $username ), 'OBJECT'
	);

	if( ! $user ){
		return [ 'error', 'There is no such user' ];
	}

	$pass_frag = substr( $user->user_pass, 8, 4 );
	$key = wp_hash( $username . '|' . $pass_frag . '|' . $expiration . '|' . $token );
	$algo = function_exists( 'hash' ) ? 'sha256' : 'sha1';
	$hash = hash_hmac( $algo, $username . '|' . $expiration . '|' . $token, $key );
	if( ! hash_equals( $hash, $hmac ) ){
		return [ 'error', 'Hash is not equivalent to' ];
	}

	$user_options = $wpdb->get_results( "
		SELECT `meta_key` ,`meta_value` FROM $wpdb->usermeta 
		WHERE `user_id` = $user->ID AND `meta_key` IN ( 'session_tokens', '{$wpdb->prefix}capabilities' )
	", OBJECT_K );

	if( ! $user_options ){
		return [ 'error', 'User options are not set' ];
	}

	$sessions = unserialize( $user_options['session_tokens']->meta_value );
	$verifier = hash_token( $token );

	if( ! isset( $sessions[ $verifier ] ) ){
		return [ 'error', 'No authorization token' ];
	}

	if( $sessions[ $verifier ]['expiration'] < time() ){
		return [ 'error', 'Session time has expired' ];
	}

	$role_caps = unserialize( $_options[ $wpdb->prefix . 'user_roles' ]->option_value );
	$user_caps = unserialize( $user_options[ $wpdb->prefix . 'capabilities' ]->meta_value );
	$all_caps = [];

	foreach( $user_caps as $key => $value ){
		if( isset( $role_caps[ $key ] ) && $value ){
			$all_caps = array_merge( $all_caps, $role_caps[ $key ]['capabilities'], true );
		}
		else{
			$all_caps[ $key ] = $value;
		}
	}

	return [ 'success', $all_caps ];
}