: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @param string $homePath
* @param bool $relaxedFileOwnership
* @param bool $output Whether or not to output the credentials collection form. If false, this function only returns the status.
* @return bool Returns true if the path is writable, otherwise false.
public static function requestFilesystemCredentials($adminURL, $homePath = null, $relaxedFileOwnership = true, $output = true) {
if ($homePath === null) {
$homePath = wfUtils::getHomePath();
if (!$output) { ob_start(); }
if (false === ($credentials = request_filesystem_credentials($adminURL, '', false, $homePath, array('version', 'locale'), $relaxedFileOwnership))) {
if (!$output) { ob_end_clean(); }
if (!WP_Filesystem($credentials, $homePath, $relaxedFileOwnership)) { // Failed to connect, Error and request again
request_filesystem_credentials($adminURL, '', true, ABSPATH, array('version', 'locale'), $relaxedFileOwnership);
if (!$output) { ob_end_clean(); }
if ($wp_filesystem->errors->get_error_code()) {
if (!$output) { ob_end_clean(); }
if (!$output) { ob_end_clean(); }
public static function initRestAPI() {
if (wfCentral::isSupported()) {
$auth = new wfRESTAuthenticationController();
$config = new wfRESTConfigController();
$config->registerRoutes();
$scan = new wfRESTScanController();
public static function ajax_wfcentral_step1_callback() {
// Step 1: Makes GET request to `/central/api/site/access-token` endpoint authenticated with the auth grant supplied by the user.
// - Receives site GUID, public key, short lived JWT.
$authGrant = isset($_REQUEST['auth-grant']) ? $_REQUEST['auth-grant'] : null;
'errorMsg' => __("Auth grant is invalid.", 'wordfence'),
$request = new wfCentralAPIRequest('/site/access-token', 'GET', $authGrant);
$response = $request->execute();
wfCentralAPIRequest::handleInternalCentralAPIError($e);
wfCentralAPIRequest::handleInternalCentralAPIError($t);
'errorMsg' => __('Internal error when connecting to Wordfence Central (see server error log)', 'wordfence'),
else if ($response->isError()) {
return $response->returnErrorArray();
$body = $response->getJSONBody();
if (!is_array($body) || !isset($body['data']['attributes'])) {
'errorMsg' => sprintf(/* translators: Error message. */ __("Invalid response from Wordfence Central: %s", 'wordfence'), $response->getBody()),
if (!array_key_exists('id', $body['data'])) {
'errorMsg' => sprintf(/* translators: JSON property. */ __("Invalid response from Wordfence Central. Parameter %s not found in response.", 'wordfence'), 'id'),
$data = $body['data']['attributes'];
foreach ($expected as $key) {
if (!array_key_exists($key, $data)) {
'errorMsg' => sprintf(/* translators: JSON property. */ __("Invalid response from Wordfence Central. Parameter %s not found in response.", 'wordfence'), $key),
wfConfig::set('wordfenceCentralSiteID', $body['data']['id']);
wfConfig::set('wordfenceCentralPK', pack("H*", $data['public-key']));
wfConfig::set('wordfenceCentralAccessToken', $data['access-token']);
wfConfig::set('wordfenceCentralCurrentStep', 2);
wfConfig::set('wordfenceCentralDisconnected', false);
wfConfig::set('wordfenceCentralDisconnectTime', null);
wfConfig::set('wordfenceCentralDisconnectEmail', null);
public static function ajax_wfcentral_step2_callback() {
// Step 2: Makes POST request to `/central/api/wf/site/<guid>` endpoint passing in the new public key.
// Uses JWT from auth grant endpoint as auth.
require_once(WORDFENCE_PATH . '/lib/sodium_compat_fast.php');
$accessToken = wfConfig::get('wordfenceCentralAccessToken');
'errorMsg' => __("Access token not found.", 'wordfence'),
$keypair = ParagonIE_Sodium_Compat::crypto_sign_keypair();
$publicKey = ParagonIE_Sodium_Compat::crypto_sign_publickey($keypair);
$secretKey = ParagonIE_Sodium_Compat::crypto_sign_secretkey($keypair);
wfConfig::set('wordfenceCentralSecretKey', $secretKey);
$request = new wfCentralAPIRequest('/site/' . wfConfig::get('wordfenceCentralSiteID'), 'POST',
'public-key' => ParagonIE_Sodium_Compat::bin2hex($publicKey),
$response = $request->execute();
wfCentralAPIRequest::handleInternalCentralAPIError($e);
wfCentralAPIRequest::handleInternalCentralAPIError($t);
'errorMsg' => __('Internal error when connecting to Wordfence Central (see server error log)', 'wordfence'),
else if ($response->isError()) {
return $response->returnErrorArray();
wfConfig::set('wordfenceCentralCurrentStep', 3);
public static function ajax_wfcentral_step3_callback() {
// Step 3: Makes GET request to `/central/api/wf/site/<guid>` endpoint signed using Wordfence plugin private key.
// - Expects 200 response with site data.
$request = new wfCentralAuthenticatedAPIRequest('/site/' . wfConfig::get('wordfenceCentralSiteID'));
$response = $request->execute();
if ($response->isError()) {
return $response->returnErrorArray();
$body = $response->getJSONBody();
if (!is_array($body) || !isset($body['data']['attributes'])) {
'errorMsg' => __('Invalid response from Wordfence Central.', 'wordfence'),
wfConfig::set('wordfenceCentralSiteData', json_encode($body['data']['attributes']));
wfConfig::set('wordfenceCentralCurrentStep', 4);
catch (wfCentralAPIException $e) {
'errorMsg' => $e->getMessage(),
wfCentralAPIRequest::handleInternalCentralAPIError($e);
'errorMsg' => $e->getMessage(),
wfCentralAPIRequest::handleInternalCentralAPIError($t);
'errorMsg' => $t->getMessage(),
public static function ajax_wfcentral_step4_callback() {
// Step 4: Poll for PUT request at `/wp-json/wp/v2/wordfence-auth-grant/` endpoint signed using Wordfence Central private key with short lived JWT.
// - Expects verifiable signature of incoming request from Wordfence Central.
// - Stores auth grant JWT.
$wfCentralAuthGrant = wfConfig::get('wordfenceCentralUserSiteAuthGrant');
if ($wfCentralAuthGrant) {
wfConfig::set('wordfenceCentralCurrentStep', 5);
public static function ajax_wfcentral_step5_callback() {
// Step 5: Makes POST request to `/central/api/site/<guid>/access-token` endpoint signed using Wordfence plugin private key with auth grant JWT.
// - Expects 200 response with access token.
$wfCentralAuthGrant = wfConfig::get('wordfenceCentralUserSiteAuthGrant');
if (!$wfCentralAuthGrant) {
'errorMsg' => __('Auth grant not found.', 'wordfence'),
$request = new wfCentralAuthenticatedAPIRequest(
sprintf('/site/%s/access-token', wfConfig::get('wordfenceCentralSiteID')),
'auth-grant' => $wfCentralAuthGrant,
$response = $request->execute();
if ($response->isError()) {
return $response->returnErrorArray();
$body = $response->getJSONBody();
if (!is_array($body) || !isset($body['access-token'])) {
'errorMsg' => __('Invalid response from Wordfence Central.', 'wordfence'),
wfConfig::set('wordfenceCentralUserSiteAccessToken', $body['access-token']);
wfConfig::set('wordfenceCentralCurrentStep', 6);
'access-token' => $body['access-token'],
'redirect-url' => sprintf(WORDFENCE_CENTRAL_URL_SEC . '/sites/%s?access-token=%s',
rawurlencode(wfConfig::get('wordfenceCentralSiteID')), rawurlencode($body['access-token'])),
catch (wfCentralAPIException $e) {
'errorMsg' => $e->getMessage(),
wfCentralAPIRequest::handleInternalCentralAPIError($e);
'errorMsg' => $e->getMessage(),
wfCentralAPIRequest::handleInternalCentralAPIError($t);
'errorMsg' => $t->getMessage(),
public static function ajax_wfcentral_step6_callback() {
$wfCentralUserSiteAccessToken = wfConfig::get('wordfenceCentralUserSiteAccessToken');
if (!$wfCentralUserSiteAccessToken) {
'errorMsg' => __('Access token not found.', 'wordfence'),
$status = wfConfig::get('scanStageStatuses');
wfCentral::updateScanStatus($status);
wfConfig::set('wordfenceCentralConnectTime', time());
wfConfig::set('wordfenceCentralConnectEmail', wp_get_current_user()->user_email);
'access-token' => $wfCentralUserSiteAccessToken,
'redirect-url' => sprintf(WORDFENCE_CENTRAL_URL_SEC . '/sites/%s?access-token=%s',
rawurlencode(wfConfig::get('wordfenceCentralSiteID')), rawurlencode($wfCentralUserSiteAccessToken)),
public static function ajax_wfcentral_disconnect_callback() {
$dismiss = array_key_exists('dismiss', $_POST) && wfUtils::truthyToBoolean($_POST['dismiss']);
wfConfig::set('centralUrlMismatchChoice', '1');
$force = array_key_exists('force', $_POST) && $_POST['force'] === 'true';
$localOnly = array_key_exists('local', $_POST) && $_POST['local'] === 'true';
if ($force || !wfCentral::isCentralSiteUrlMismatched()) {
$request = new wfCentralAuthenticatedAPIRequest(
sprintf('/site/%s', wfConfig::get('wordfenceCentralSiteID')),
$response = $request->execute();
if ($response->isError()) {
return $response->returnErrorArray();
$message = sprintf(__('The current site URL does not match the Wordfence Central connection information. Local connection information has been removed, but %s is still registered in Wordfence Central.', 'wordfence'), wfCentral::getCentralSiteUrl());
catch (wfCentralAPIException $e) {
'errorMsg' => __('Unable to communicate with Wordfence Central', 'wordfence')
wfCentralAPIRequest::handleInternalCentralAPIError($e);
wfCentralAPIRequest::handleInternalCentralAPIError($t);
wfRESTConfigController::disconnectConfig();
'title' => __('Disconnected from Wordfence Central')
public static function queueCentralConfigurationSync($key, $value) {
\WordfenceLS\Controller_Settings::OPTION_USE_NTP,
\WordfenceLS\Controller_Settings::OPTION_NTP_OFFSET,
\WordfenceLS\Controller_Settings::OPTION_ALLOW_DISABLING_NTP,
\WordfenceLS\Controller_Settings::OPTION_NTP_FAILURE_COUNT,
\WordfenceLS\Controller_Settings::OPTION_CAPTCHA_STATS,
if (in_array($key, $ignored)) {
add_action('shutdown', 'wfCentral::requestConfigurationSync');
public static function hasWoocommerce() {
return class_exists('woocommerce');
class wfWAFAutoPrependHelper {
private $currentAutoPrependedFile;
public static function helper($serverConfig = null, $currentAutoPrependedFile = null) {
return new wfWAFAutoPrependHelper($serverConfig, $currentAutoPrependedFile);
public static function isValidServerConfig($serverConfig) {
return in_array($serverConfig, $validValues);
* Verifies the .htaccess block for mod_php if present, returning true if no changes need to happen, false
* if something needs to update.
public static function verifyHtaccessMod_php() {
if (WFWAF_AUTO_PREPEND && PHP_MAJOR_VERSION > 5) {
$serverInfo = wfWebServerInfo::createFromEnvironment();
if (!$serverInfo->isApacheModPHP()) {
$htaccessPath = wfUtils::getHomePath() . '.htaccess';
if (file_exists($htaccessPath)) {
$htaccessContent = file_get_contents($htaccessPath);
$regex = '/# Wordfence WAF.*?# END Wordfence WAF/is';
if (preg_match($regex, $htaccessContent, $matches)) {
$hasPHP5 = preg_match('/<IfModule mod_php5\.c>\s*php_value auto_prepend_file \'.*?\'\s*<\/IfModule>/is', $wafBlock);
$hasPHP7 = preg_match('/<IfModule mod_php7\.c>\s*php_value auto_prepend_file \'.*?\'\s*<\/IfModule>/is', $wafBlock);
$hasPHP8 = preg_match('/<IfModule mod_php\.c>\s*php_value auto_prepend_file \'.*?\'\s*<\/IfModule>/is', $wafBlock);
if ($hasPHP5 && (!$hasPHP7 || !$hasPHP8)) { //Check if PHP 5 is configured, but not 7 or 8.
* Updates the mod_php block of the .htaccess if needed to include PHP 7. Returns whether or not this was performed successfully.
public static function fixHtaccessMod_php() {
$htaccessPath = wfUtils::getHomePath() . '.htaccess';
if (file_exists($htaccessPath)) {
$htaccessContent = file_get_contents($htaccessPath);
$regex = '/# Wordfence WAF.*?# END Wordfence WAF/is';
if (preg_match($regex, $htaccessContent, $matches, PREG_OFFSET_CAPTURE)) {
$wafBlock = $matches[0][0];
$hasPHP5 = preg_match('/<IfModule mod_php5\.c>\s*php_value auto_prepend_file \'(.*?)\'\s*<\/IfModule>/is', $wafBlock, $php5Matches, PREG_OFFSET_CAPTURE);
$hasPHP7 = preg_match('/<IfModule mod_php7\.c>\s*php_value auto_prepend_file \'.*?\'\s*<\/IfModule>/is', $wafBlock, $php7Matches, PREG_OFFSET_CAPTURE);
$hasPHP8 = preg_match('/<IfModule mod_php\.c>\s*php_value auto_prepend_file \'.*?\'\s*<\/IfModule>/is', $wafBlock);
if ($hasPHP5 && !$hasPHP7) {
$beforeWAFBlock = substr($htaccessContent, 0, $matches[0][1]);
$afterWAFBlock = substr($htaccessContent, $matches[0][1] + strlen($wafBlock));
$beforeMod_php = substr($wafBlock, 0, $php5Matches[0][1]);
$afterMod_php = substr($wafBlock, $php5Matches[0][1] + strlen($php5Matches[0][0]));
$updatedHtaccessContent = $beforeWAFBlock . $beforeMod_php . $php5Matches[0][0] . "\n" . sprintf("<IfModule mod_php7.c>\n\tphp_value auto_prepend_file '%1\$s'\n</IfModule>\n<IfModule mod_php.c>\n\tphp_value auto_prepend_file '%1\$s'\n</IfModule>", $php5Matches[1][0] /* already escaped */) . $afterMod_php . $afterWAFBlock;
return file_put_contents($htaccessPath, $updatedHtaccessContent) !== false;
if ($hasPHP5 && $hasPHP7 && !$hasPHP8) {
$beforeWAFBlock = substr($htaccessContent, 0, $matches[0][1]);
$afterWAFBlock = substr($htaccessContent, $matches[0][1] + strlen($wafBlock));
$beforeMod_php = substr($wafBlock, 0, $php5Matches[0][1]);
$afterMod_php = substr($wafBlock, $php7Matches[0][1] + strlen($php7Matches[0][0]));
$updatedHtaccessContent = $beforeWAFBlock . $beforeMod_php . $php5Matches[0][0] . "\n" . $php7Matches[0][0] . "\n" . sprintf("<IfModule mod_php.c>\n\tphp_value auto_prepend_file '%s'\n</IfModule>", $php5Matches[1][0] /* already escaped */) . $afterMod_php . $afterWAFBlock;
return file_put_contents($htaccessPath, $updatedHtaccessContent) !== false;