SHORTINIT константа: WordPress среда с минимальной нагрузкой
Меня волновал вопрос: как можно использовать объект $wpdb и Базу Данных сайта с которой я работаю, но чтобы при этом минимально подгружать среду WP. Иногда бывает нужно, при использовании Ajax получить или записать данные в БД и ничего больше: не нужны фильтры, не нужна проверка на авторизацию пользователя, не нужны функции WоrdPress, не нужны всякие проверки и куча подгружаемых функций. В общем, не нужно ничего кроме возможности общаться с Базой Данных привычными для WordPress методами.
Решить такую задачу можно, считав данные подключения к БД из файла wp-config.php и отдельно подключится к БД. Но это не очень удобно и потребует лишнего кода, который по сути уже есть в файлах WordPress. А то получатся очередные костыли.
С версии 3.4 разработчики WordPress позаботились об этом и добавили константу SHORTINIT в wp-settings.php:
// Останавливаем основную загрузку WordPress если нам нужна только база. if ( SHORTINIT ) return false;
Работает она так:
// указываем, что нам нужен минимум от WP
define('SHORTINIT', true);
// подгружаем среду WordPress
// WP делает некоторые проверки и подгружает только самое необходимое для подключения к БД
require_once( $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php' );
// тут мы можем общаться с БД. Но практически никакие функции WP работать не будут.
// Глобальные переменные $wp, $wp_query, $wp_the_query не установлены...
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>";
}
}
Конкретные числа
Чтобы посмотреть чем отличаются инициализации с SHORTINIT и без. Я замерил: количество SQL запросов, время на выполнение кода и используемая память. Вот что получилось:
require_once( $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php' ); // 5 SQL за 0.1 сек., память: 14.92 mb define( 'SHORTINIT', true ); require_once( $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php' ); // 0 SQL за 0.02 сек., память: 2.35 mb
Таким образом SHORTINIT, грубо, по всем параметрам снижает нагрузку в 5 раз. Неплохо!
Что работает при SHORTINIT
При использовании SHORTINIT система фильтров: apply_filters() do_action() уже работает. Фильтры прописаны основные (файл: /wp-includes/default-filters.php) Те что вы указывали в functions.php вашей темы и многие другие работать не будут.
Из привычных функций: esc_attr(), is_single(), the_content(), get_permalink() и т.д. не работает ни одна. Вот все функции которые подключаются - см. /wp-settings.php:
Include files required for initialization.
- /wp-includes/class-wp-paused-extensions-storage.php
- /wp-includes/class-wp-fatal-error-handler.php
- /wp-includes/class-wp-recovery-mode-cookie-service.php
- /wp-includes/class-wp-recovery-mode-key-service.php
- /wp-includes/class-wp-recovery-mode-link-service.php
- /wp-includes/class-wp-recovery-mode-email-service.php
- /wp-includes/class-wp-recovery-mode.php
- /wp-includes/error-protection.php
- /wp-includes/default-constants.php
- /wp-includes/plugin.php
If WP_CACHE constant enabled:
/wp-content/advanced-cache.php
Load early WordPress files.
- /wp-includes/class-wp-list-util.php
- /wp-includes/formatting.php
- /wp-includes/meta.php
- /wp-includes/functions.php
- /wp-includes/class-wp-meta-query.php
- /wp-includes/class-wp-matchesmapregex.php
- /wp-includes/class-wp.php
- /wp-includes/class-wp-error.php
- /wp-includes/default-filters.php
Loads for multisite:
- /wp-includes/class-wp-site-query.php
- /wp-includes/class-wp-network-query.php
- /wp-includes/ms-blogs.php
- /wp-includes/ms-settings.php
Авторизация при SHORTINIT
Пример проверки авторизации и получения всех прав пользователя с 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 ];
}
Первый протестированный вариант кода
Код тестировался на WP 4.5. В ранних или поздних версиях возможно нужно будет изменить проверку...
GitHubДополнительные функции WP при SHORTINIT
Если в среде ШОРТИНИТ вам недостаточно какой-либо функциональности, то можно вручную прописать подгрузку нужных файлов ядра и запуск важных функций. Сотрите файл wp-settings.php.
Пример того как можно подключить все необходимое для запроса. Ниже описан файл который принимает запрос с включенной SHORTINIT константой:
define( 'SHORTINIT', true ); // Load WP $root = isset( $_SERVER['DOCUMENT_ROOT'] ) ? filter_var( $_SERVER['DOCUMENT_ROOT'], FILTER_SANITIZE_STRING ) : ''; // Some servers return document root with trailing slash. $root = rtrim( $root, '/\\' ); require $root . '/wp-load.php'; // Load the L10n library - needed for number_format_i18n() to work properly. require_once ABSPATH . WPINC . '/l10n.php'; require_once ABSPATH . WPINC . '/class-wp-locale.php'; load_default_textdomain(); $GLOBALS['locale'] = get_locale(); $GLOBALS['wp_locale'] = new WP_Locale(); // Components needed for get_the_date() to work. require $root . WPINC . '/general-template.php'; require $root . WPINC . '/post.php'; require $root . WPINC . '/class-wp-post.php'; // Components needed for check_ajax_referer() to work. require $root . WPINC . '/capabilities.php'; require $root . WPINC . '/class-wp-roles.php'; require $root . WPINC . '/class-wp-role.php'; require $root . WPINC . '/class-wp-user.php'; require $root . WPINC . '/user.php'; require $root . WPINC . '/class-wp-session-tokens.php'; require $root . WPINC . '/class-wp-user-meta-session-tokens.php'; require $root . WPINC . '/kses.php'; require $root . WPINC . '/rest-api.php'; wp_plugin_directory_constants(); wp_cookie_constants(); require $root . WPINC . '/pluggable.php'; // do your staff here // ...