WordPress как на ладони
wordpress jino

WordPress среда с минимальной нагрузкой (константа SHORTINIT)

Дадим возможность улитке разгоняться

Меня волновал вопрос: как можно использовать объект $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-includes/load.php
wp_unregister_GLOBALS() 
wp_fix_server_vars() 
wp_check_php_mysql_versions() 
wp_favicon_request() 
wp_maintenance() 
timer_start() 
timer_stop() 
wp_debug_mode() 
wp_set_lang_dir() 
require_wp_db() 
wp_set_wpdb_vars() 
wp_start_object_cache() 
wp_not_installed() 
wp_get_mu_plugins() 
wp_get_active_and_valid_plugins() 
wp_set_internal_encoding() 
wp_magic_quotes() 
shutdown_action_hook() 
wp_clone() 
is_admin() 
is_blog_admin() 
is_network_admin() 
is_user_admin() 
is_multisite() 
get_current_blog_id() 
wp_load_translations_early() 

----- /wp-includes/load.php
wp_initial_constants() 
wp_plugin_directory_constants() 
wp_cookie_constants() 
wp_ssl_constants() 
wp_functionality_constants() 
wp_templating_constants()

----- /wp-includes/compat.php
_() 
mb_substr() 
_mb_substr() 
hash_hmac() 
_hash_hmac() 
json_encode() 
json_decode() 
_json_decode_object_helper()

----- /wp-includes/functions.php
mysql2date() 
current_time() 
date_i18n() 
number_format_i18n() 
size_format() 
get_weekstartend() 
maybe_unserialize() 
is_serialized() 
is_serialized_string() 
maybe_serialize() 
xmlrpc_getposttitle() 
xmlrpc_getpostcategory() 
xmlrpc_removepostdata() 
do_enclose() 
wp_get_http() 
wp_get_http_headers() 
is_new_day() 
build_query() 
_http_build_query() 
add_query_arg() 
remove_query_arg() 
add_magic_quotes() 
wp_remote_fopen() 
wp() 
get_status_header_desc() 
status_header() 
wp_get_nocache_headers() 
nocache_headers() 
cache_javascript_headers() 
get_num_queries() 
bool_from_yn() 
do_feed() 
do_feed_rdf() 
do_feed_rss() 
do_feed_rss2() 
do_feed_atom() 
do_robots() 
is_blog_installed() 
wp_nonce_url() 
wp_nonce_field() 
wp_referer_field() 
wp_original_referer_field() 
wp_get_referer() 
wp_get_original_referer() 
wp_mkdir_p() 
path_is_absolute() 
path_join() 
get_temp_dir() 
win_is_writable() 
wp_upload_dir() 
wp_unique_filename() 
wp_upload_bits() 
wp_ext2type() 
wp_check_filetype() 
wp_check_filetype_and_ext() 
wp_get_mime_types() 
get_allowed_mime_types() 
wp_nonce_ays() 
wp_die() 
apply_filters() 
_default_wp_die_handler() 
_xmlrpc_wp_die_handler() 
_ajax_wp_die_handler() 
_scalar_wp_die_handler() 
wp_send_json() 
wp_send_json_success() 
wp_send_json_error() 
_config_wp_home() 
_config_wp_siteurl() 
_mce_set_direction() 
smilies_init() 
wp_parse_args() 
wp_parse_id_list() 
wp_array_slice_assoc() 
wp_filter_object_list() 
wp_list_filter() 
wp_list_pluck() 
wp_maybe_load_widgets() 
wp_widgets_add_menu() 
wp_ob_end_flush_all() 
dead_db() 
absint() 
url_is_accessable_via_ssl() 
_deprecated_function() 
_deprecated_file() 
_deprecated_argument() 
_doing_it_wrong() 
is_lighttpd_before_150() 
apache_mod_loaded() 
iis7_supports_permalinks() 
validate_file() 
is_ssl() 
force_ssl_login() 
force_ssl_admin() 
wp_guess_url() 
wp_suspend_cache_addition() 
wp_suspend_cache_invalidation() 
is_main_site() 
global_terms_enabled() 
wp_timezone_override_offset() 
_wp_timezone_choice_usort_callback() 
wp_timezone_choice() 
_cleanup_header_comment() 
wp_scheduled_delete() 
get_file_data() 
_search_terms_tidy() 
__return_true() 
__return_false() 
__return_zero() 
__return_empty_array() 
__return_null() 
send_nosniff_header() 
_wp_mysql_week() 
wp_find_hierarchy_loop() 
wp_find_hierarchy_loop_tortoise_hare() 
send_frame_options_header() 
wp_allowed_protocols() 
wp_debug_backtrace_summary() 
_get_non_cached_ids() 
_device_can_upload() 
wp_is_stream() 
wp_checkdate()

----- /wp-includes/class-wp.php
class wp { }

----- /wp-includes/class-wp-error.php
class WP_Error { }

----- /wp-includes/plugin.php
add_filter() 
has_filter() 
apply_filters() 
apply_filters_ref_array() 
remove_filter() 
remove_all_filters() 
current_filter() 
add_action() 
do_action() 
did_action() 
do_action_ref_array() 
has_action() 
remove_action() 
remove_all_actions() 
plugin_basename() 
plugin_dir_path() 
plugin_dir_url() 
register_activation_hook() 
register_deactivation_hook() 
register_uninstall_hook()
_wp_call_all_hook() 
_wp_filter_build_unique_id() 

----- /wp-includes/pomo/translations.php
class Translations { }
class Gettext_Translations extends Translations { }
class NOOP_Translations { }

----- /wp-includes/pomo/entry.php
class Translation_Entry {

----- /wp-includes/pomo/mo.php
class MO extends Gettext_Translations { }

----- /wp-includes/pomo/streams.php
class POMO_Reader { }

----- /wp-includes/default-filters.php
Устанавливает базовые фильтры

Пример проверки авторизации и получения всех прав пользователя с SHORTINIT

В комментариях пользователь Александр поделился кодом, который исправно работает, за что ему отдельное спасибо.

Код тестировался на WP 4.5. В ранних или поздних версиях возможно нужно будет изменить проверку...

// указываем, что нам нужен минимум от WP
define('SHORTINIT', true);

// подгружаем среду WordPress
require_once( $_SERVER['DOCUMENT_ROOT'] . '/wp/wp-load.php' );

// укороченная версия wp_hash из 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 );
	}
}

global $wpdb;

// получаем значение siteurl и список ролей из БД, неудачно - прерываем выполнение скрипта
$_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) exit;

// получаем md5 хеш для siteurl - он формирует ключ куки
$_c_hash = md5( $_options['siteurl']->option_value );

if ( !isset( $_COOKIE['wordpress_logged_in_'.$_c_hash] ) ) exit;

// получаем параметр, разбиваем на элементы
$cookie = $_COOKIE['wordpress_logged_in_'.$_c_hash];
$cookie_elements = explode('|', $cookie);

// кол-во элементов = 4, если нет - параметр куки поврежден, прерываем скрипт
if ( count( $cookie_elements ) !== 4 ) exit;
list( $username, $expiration, $token, $hmac ) = $cookie_elements;

// проверяем время жизни куки, если истекло - прерываем скрипт
if ( $expiration < time() ) exit; 

// получаем данные о пользователе из БД по логину, не удалось - прерываем скрипт
$user = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->users WHERE `user_login`=%s", $username) , 'OBJECT' );
if ( ! $user ) exit;

$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 ) ) exit;

// проверяем сессию
// получаем сессии пользователя и доп. права по user ID из usermeta, не получили - прерываем скрипт
$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) exit;

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

// сессия не найдена или устарела - прерываем скрипт
if ( isset( $sessions[ $verifier ] ) ) {
	if ( $sessions[$verifier]['expiration'] < time() ) exit; 
} else exit;

// наборы прав для ролей и пользователя
$role_caps = unserialize( $_options[ $wpdb->prefix.'user_roles' ]->option_value );
$user_caps = unserialize( $user_options[ $wpdb->prefix.'capabilities' ]->meta_value );
$all_caps = array();
// формируем общий набор прав пользователя
foreach ($user_caps as $key => $value){
	//$key есть в наборе ролей - значит, это роль 
	if ( isset($role_caps[$key]) && $value ) 
		$all_caps = array_merge( $all_caps, $role_caps[$key]['capabilities'] );
	// это ключ-право пользователя
	else
		$all_caps[$key] = $value;
}

// аналог current_user_can()
function curr_user_can($capability){
	global $all_caps;
	return isset( $all_caps[$capability] ) && $all_caps[$capability];
}

// все проверки пройдены, можно выполнять запросы

// массив прав текущего пользователя
print_r( $all_caps );
WordPress среда с минимальной нагрузкой (константа SHORTINIT) 33 комментария
Полезные 2 Все
  • Дима @

    А как можно с этим знать username или display_name пользователя? ...не права а именно ихние информация в users table

    Ответитьгод назад #
    • Очень странный вопрос.
      Если используете код, очень желательно его просматривать.

      $user->user_nicename 
      $user->display_name 
      2
      Ответитьгод назад #
  • Дима @

    Кажетса здесь проблема: $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 );

    ... не работает. Помогите пожалуйста!

    Ответитьгод назад #
    • Если что-то не работает, как минимум, нужно после запроса на следующей строке вывести содержимое $user_options:

      var_dump($user_options);
      или
      print_r($user_options);

      и смотреть, в чем проблема

      1
      Ответитьгод назад #

Здравствуйте, !

Ваш комментарий