Files
2025-02-24 22:33:42 +01:00

263 lines
7.6 KiB
PHP

<?php
if (!defined('ABSPATH')) die('No direct access allowed');
// Add filter for storing wp_salt() values to cache config
add_filter('wpo_cache_update_config', 'wpo_cache_add_salt_to_config');
// Add filter for filtering user specific cache filename.
if (!empty($GLOBALS['wpo_cache_config']['enable_user_specific_cache'])) {
add_filter('wpo_cache_filename', 'wpo_user_cache_filename');
}
/**
* Get username from WordPress cookies.
*
* @return string|bool
*/
if (!function_exists('wpo_username_from_cookies')) :
function wpo_username_from_cookies() {
// if salt value doesn't exist in configuration the return.
if (empty($GLOBALS['wpo_cache_config']['wp_salt_logged_in'])) return false;
// get wordpress_logged_in cookie values.
$cookie_parts = array();
foreach ($_COOKIE as $key => $value) {
if (!preg_match('/^wordpress_logged_in_.+/', $key)) continue;
$cookie_parts = explode('|', $value);
}
if (4 > count($cookie_parts)) return false;
list($username, $expiration, $token, $hmac) = $cookie_parts;
// if cookies expired then return false.
if ($expiration < time()) return false;
global $wpdb;
// initialize database if it isn't initialized.
if (!$wpdb) {
require_wp_db();
wp_set_wpdb_vars();
}
// get user password hash from database.
$user = $wpdb->get_row($wpdb->prepare("SELECT user_pass FROM {$wpdb->users} WHERE user_login=%s", $username));
// if used doesn't exist return false.
if (!is_object($user)) return false;
// validate cookie hash, used code from:
// wp_validate_auth_cookie() wp-includes/pluggable.php
$pass_frag = substr($user->user_pass, 8, 4);
$key = hash_hmac('md5', $username . '|' . $pass_frag . '|' . $expiration . '|' . $token, $GLOBALS['wpo_cache_config']['wp_salt_logged_in']);
$algo = function_exists('hash') ? 'sha256' : 'sha1';
$hash = hash_hmac($algo, $username . '|' . $expiration . '|' . $token, $key);
if (!hash_equals($hash, $hmac)) return false; // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.hash_equalsFound
return $username;
}
endif;
if (!function_exists('wpo_user_cache_filename')) :
/**
* Filters cached filename.
*
* @param string $filename source filename
*
* @return string
*/
function wpo_user_cache_filename($filename) {
if (!wpo_cache_loggedin_users()) return $filename;
$username = wpo_username_from_cookies();
if (!$username) return $filename;
// if salt value doesn't exist in configuration the return.
if (empty($GLOBALS['wpo_cache_config']['wp_salt_auth'])) return $filename;
$salt = $GLOBALS['wpo_cache_config']['wp_salt_auth'];
// Use the original WordPress regular expression to exclude unwanted characters from the $username variable.
$username = preg_replace('|[^a-z0-9 _.\-@]|i', '', $username);
$encoded_filename = $filename.'-'.$username.'-'.sha1($filename.$salt.$username);
return $encoded_filename;
}
endif;
if (!function_exists('wpo_cache_add_salt_to_config')) :
/**
* Add required wp_salt() values to the cache config.
*
* @param array $config
*
* @return array
*/
function wpo_cache_add_salt_to_config($config) {
require_once ABSPATH. WPINC . '/pluggable.php';
$config['wp_salt_auth'] = wp_salt('auth');
$config['wp_salt_logged_in'] = wp_salt('logged_in');
return $config;
}
endif;
/**
* Filter cache filename and add current user role info to the filename.
*/
if (!function_exists('wpo_per_role_cache_filename')) :
function wpo_per_role_cache_filename($filename) {
$username = wpo_username_from_cookies();
if (!$username) return $filename;
// get selected roles in configuration.
$selected_roles = !empty($GLOBALS['wpo_cache_config']['per_role_cache']) ? $GLOBALS['wpo_cache_config']['per_role_cache'] : array();
// get current user roles
$roles = wpo_get_user_roles($username);
$roles = array_intersect($selected_roles, $roles);
sort($roles);
if (empty($roles || empty($GLOBALS['wpo_cache_config']['wp_salt_auth']))) return $filename;
$salt = $GLOBALS['wpo_cache_config']['wp_salt_auth'];
$filename = $filename . '-'.join('-', $roles) .'-'. sha1($filename.$salt.join('-', $roles));
return $filename;
}
endif;
/**
* Get user roles by username ordered.
*
* @param string $username
* @return array
*/
if (!function_exists('wpo_get_user_roles')) :
function wpo_get_user_roles($username) {
global $wpdb;
static $user_roles = array();
if (array_key_exists($username, $user_roles)) return $user_roles[$username];
// initialize database if it isn't initialized.
if (!$wpdb) {
require_wp_db();
wp_set_wpdb_vars();
}
$user = $wpdb->get_row($wpdb->prepare("SELECT `ID` FROM {$wpdb->users} WHERE `user_login`=%s", $username));
if (empty($user)) return false;
$roles_value = $wpdb->get_row($wpdb->prepare("SELECT meta_value FROM {$wpdb->usermeta} WHERE meta_key='{$wpdb->prefix}capabilities' AND user_id=%d", $user->ID));
if (empty($roles_value)) return false;
$roles = unserialize($roles_value->meta_value);
$result = array();
foreach ($roles as $role => $enabled) {
if ($enabled) $result[] = $role;
}
$user_roles[$username] = $result;
return $result;
}
endif;
/**
* Check if we currently cache per roles.
*/
if (!function_exists('wpo_we_cache_per_role')) :
function wpo_we_cache_per_role() {
// we don't cache per role when option disabled
if (empty($GLOBALS['wpo_cache_config']['enable_per_role_cache']) || empty($GLOBALS['wpo_cache_config']['wp_salt_auth'])) return false;
$logged_in = false;
$wp_cookies = array('wordpressuser_', 'wordpresspass_', 'wordpress_sec_', 'wordpress_logged_in_');
foreach ($_COOKIE as $key => $value) {
foreach ($wp_cookies as $cookie) {
if (0 === strpos($key, $cookie)) {
$logged_in = true;
break(2);
}
}
}
// we don't cache per role when user in not logged-in
if (!$logged_in) return false;
$username = wpo_username_from_cookies();
// we don't use per role cache when we can't get username from cookies
if (!$username) return false;
// get selected roles in configuration.
$selected_roles = !empty($GLOBALS['wpo_cache_config']['per_role_cache']) ? $GLOBALS['wpo_cache_config']['per_role_cache'] : array();
// get current user roles
$roles = wpo_get_user_roles($username);
$roles = array_intersect($selected_roles, $roles);
// we don't cache per role when user has no roles selected in cache settings
if (empty($roles)) return false;
return true;
}
endif;
if (!function_exists('str_contains')) :
/**
* Polyfill for `str_contains()` function added in PHP 8.0.
*
* This polyfill is necessary because the current codebase relies on the `class-wpdb.php` file, which
* utilizes the `str_contains()` function. Without this polyfill, a PHP fatal error may occur when
* running on MariaDB with a version prefix of '5.5.5-' and PHP versions earlier than 8.0.
*
* Note: We chose this solution over including `wp-includes/compat.php` using `require_once` to avoid
* introducing different types of fatal errors. This is because `compat.php` will be loaded later
* in `wp-settings.php` via a `require` statement (as opposed to `require_once`).
*
* @link https://github.com/WordPress/wordpress-develop/blob/6.2.0/src/wp-includes/class-wpdb.php#L4041
* Usage of `str_contains()` function inside `class-wpdb.php` file.
*
* @param string $haystack The string to search in.
* @param string $needle The substring to search for in the haystack.
* @return bool True if `$needle` is in `$haystack`, otherwise false.
*/
function str_contains($haystack, $needle) {
return ('' === $needle || false !== strpos($haystack, $needle));
}
endif;
/**
* Add cache filename filter when we cache per role
*/
if (wpo_we_cache_per_role()) {
add_filter('wpo_cache_filename', 'wpo_per_role_cache_filename', 1);
}