: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @param array $types Array of issue types to delete
* @param string $status Issue status to delete
* @return bool|wfCentralAPIResponse
public static function deleteIssueTypes($types, $status = 'new') {
$siteID = wfConfig::get('wordfenceCentralSiteID');
$request = new wfCentralAuthenticatedAPIRequest('/site/' . $siteID . '/issues', 'DELETE', array(
$response = $request->execute();
catch (wfCentralAPIException $e) {
wfCentralAPIRequest::handleInternalCentralAPIError($e);
wfCentralAPIRequest::handleInternalCentralAPIError($t);
public static function requestConfigurationSync() {
if (! wfCentral::isConnected() || !self::$syncConfig) {
$endpoint = '/site/'.wfConfig::get('wordfenceCentralSiteID').'/config';
$args = array('timeout' => 0.01, 'blocking' => false);
$request = new wfCentralAuthenticatedAPIRequest($endpoint, 'POST', array(), $args);
// We can safely ignore an error here for now.
wfCentralAPIRequest::handleInternalCentralAPIError($t);
protected static $syncConfig = true;
public static function preventConfigurationSync() {
self::$syncConfig = false;
* @return bool|wfCentralAPIResponse
public static function updateScanStatus($scan = null) {
$scan = wfConfig::get_ser('scanStageStatuses');
wfScanner::shared()->flushSummaryItems();
$siteID = wfConfig::get('wordfenceCentralSiteID');
$running = wfScanner::shared()->isRunning();
$request = new wfCentralAuthenticatedAPIRequest('/site/' . $siteID . '/scan', 'PATCH', array(
'scan-summary' => wfConfig::get('wf_summaryItems'),
$response = $request->execute();
wfConfig::set('lastScanStageStatusUpdate', time(), wfConfig::DONT_AUTOLOAD);
catch (wfCentralAPIException $e) {
wfCentralAPIRequest::handleInternalCentralAPIError($e);
wfCentralAPIRequest::handleInternalCentralAPIError($t);
* @param callable|null $alertCallback
public static function sendSecurityEvent($event, $data = array(), $alertCallback = null, $sendImmediately = false) {
return self::sendSecurityEvents(array(array('type' => $event, 'data' => $data, 'event_time' => microtime(true))), $alertCallback, $sendImmediately);
public static function sendSecurityEvents($events, $alertCallback = null, $sendImmediately = false) {
if (!$sendImmediately && defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) {
if (!self::pluginAlertingDisabled() && is_callable($alertCallback)) {
call_user_func($alertCallback);
foreach ($events as $e) {
'type' => 'security-event',
'event_time' => $e['event_time'],
$siteID = wfConfig::get('wordfenceCentralSiteID');
$request = new wfCentralAuthenticatedAPIRequest('/site/' . $siteID . '/security-events', 'POST', array(
// Attempt to send the security events to Central.
$response = $request->execute();
catch (wfCentralAPIException $e) {
// If we didn't alert previously, notify the user now in the event Central is down.
if (!$alerted && is_callable($alertCallback)) {
call_user_func($alertCallback);
wfCentralAPIRequest::handleInternalCentralAPIError($e);
wfCentralAPIRequest::handleInternalCentralAPIError($t);
$table_wfSecurityEvents = wfDB::networkTable('wfSecurityEvents');
$query = "INSERT INTO {$table_wfSecurityEvents} (`type`, `data`, `event_time`, `state`, `state_timestamp`) VALUES ";
$query .= implode(', ', array_fill(0, count($events), "('%s', '%s', %f, 'new', NOW())"));
$immediateSendTypes = array('adminLogin',
'nonAdminLoginNewLocation',
foreach ($events as $e) {
$sendImmediately = $sendImmediately || in_array($e['type'], $immediateSendTypes);
$args[] = json_encode($e['data']);
$args[] = $e['event_time'];
$wfdb->queryWriteArray($query, $args);
if (($ts = self::isScheduledSecurityEventCronOverdue()) || $sendImmediately) {
self::unscheduleSendPendingSecurityEvents($ts);
self::sendPendingSecurityEvents();
self::scheduleSendPendingSecurityEvents();
public static function sendPendingSecurityEvents() {
$table_wfSecurityEvents = wfDB::networkTable('wfSecurityEvents');
$rawEvents = $wfdb->querySelect("SELECT * FROM {$table_wfSecurityEvents} WHERE `state` = 'new' ORDER BY `id` ASC LIMIT 100");
foreach ($rawEvents as $r) {
$ids[] = intval($r['id']);
'data' => json_decode($r['data'], true),
'event_time' => $r['event_time'],
$idParam = '(' . implode(', ', $ids) . ')';
$wfdb->queryWrite("UPDATE {$table_wfSecurityEvents} SET `state` = 'sending', `state_timestamp` = NOW() WHERE `id` IN {$idParam}");
if (self::sendSecurityEvents($events, null, true)) {
$wfdb->queryWrite("UPDATE {$table_wfSecurityEvents} SET `state` = 'sent', `state_timestamp` = NOW() WHERE `id` IN {$idParam}");
self::checkForUnsentSecurityEvents();
$wfdb->queryWrite("UPDATE {$table_wfSecurityEvents} SET `state` = 'new', `state_timestamp` = NOW() WHERE `id` IN {$idParam}");
self::scheduleSendPendingSecurityEvents();
public static function scheduleSendPendingSecurityEvents() {
if (!defined('DONOTCACHEDB')) { define('DONOTCACHEDB', true); }
$notMainSite = is_multisite() && !is_main_site();
switch_to_blog($current_site->blog_id);
if (!wp_next_scheduled('wordfence_batchSendSecurityEvents')) {
wp_schedule_single_event(time() + 300, 'wordfence_batchSendSecurityEvents');
public static function unscheduleSendPendingSecurityEvents($timestamp) {
if (!defined('DONOTCACHEDB')) { define('DONOTCACHEDB', true); }
$notMainSite = is_multisite() && !is_main_site();
switch_to_blog($current_site->blog_id);
if (!wp_next_scheduled('wordfence_batchSendSecurityEvents')) {
wp_unschedule_event($timestamp, 'wordfence_batchSendSecurityEvents');
public static function isScheduledSecurityEventCronOverdue() {
if (!defined('DONOTCACHEDB')) { define('DONOTCACHEDB', true); }
$notMainSite = is_multisite() && !is_main_site();
switch_to_blog($current_site->blog_id);
if ($ts = wp_next_scheduled('wordfence_batchSendSecurityEvents')) {
if ((time() - $ts) > 900) {
public static function checkForUnsentSecurityEvents() {
$table_wfSecurityEvents = wfDB::networkTable('wfSecurityEvents');
$wfdb->queryWrite("UPDATE {$table_wfSecurityEvents} SET `state` = 'new', `state_timestamp` = NOW() WHERE `state` = 'sending' AND `state_timestamp` < DATE_SUB(NOW(), INTERVAL 30 MINUTE)");
$count = $wfdb->querySingle("SELECT COUNT(*) AS cnt FROM {$table_wfSecurityEvents} WHERE `state` = 'new'");
self::scheduleSendPendingSecurityEvents();
public static function trimSecurityEvents() {
$table_wfSecurityEvents = wfDB::networkTable('wfSecurityEvents');
$count = $wfdb->querySingle("SELECT COUNT(*) AS cnt FROM {$table_wfSecurityEvents}");
$wfdb->truncate($table_wfSecurityEvents); //Similar behavior to other logged data, assume possible DoS so truncate
else if ($count > 1000) {
$wfdb->queryWrite("DELETE FROM {$table_wfSecurityEvents} ORDER BY id ASC LIMIT %d", $count - 1000);
* @param callable|null $alertCallback
public static function sendAlertCallback($event, $data = array(), $alertCallback = null) {
if (is_callable($alertCallback)) {
call_user_func($alertCallback);
public static function pluginAlertingDisabled() {
if (!self::isConnected()) {
return wfConfig::get('wordfenceCentralPluginAlertingDisabled', false);
* Returns the site URL as associated with this site's Central linking.
* The return value may be:
* - null if there is no `site-url` key present in the stored Central data
* - a string if there is a `site-url` value
public static function getCentralSiteUrl() {
$siteData = json_decode(wfConfig::get('wordfenceCentralSiteData', '[]'), true);
return (is_array($siteData) && array_key_exists('site-url', $siteData)) ? (string) $siteData['site-url'] : null;
* Populates the Central record's site URL if missing locally.
public static function populateCentralSiteUrl() {
$siteData = json_decode(wfConfig::get('wordfenceCentralSiteData', '[]'), true);
if (!is_array($siteData) || !array_key_exists('site-url', $siteData)) {
$request = new wfCentralAuthenticatedAPIRequest('/site/' . wfConfig::get('wordfenceCentralSiteID'), 'GET', array(), array('timeout' => 2));
$response = $request->execute();
if ($response->isError()) {
return $response->returnErrorArray();
$responseData = $response->getJSONBody();
if (is_array($responseData) && isset($responseData['data']['attributes'])) {
$siteData = $responseData['data']['attributes'];
wfConfig::set('wordfenceCentralSiteData', json_encode($siteData));
catch (wfCentralAPIException $e) {
wfCentralAPIRequest::handleInternalCentralAPIError($e);
wfCentralAPIRequest::handleInternalCentralAPIError($t);
public static function isCentralSiteUrlMismatched() {
if (!wfCentral::_isConnected()) {
$centralSiteUrl = self::getCentralSiteUrl();
if (!is_string($centralSiteUrl)) {
$localSiteUrl = get_site_url();
return !wfUtils::compareSiteUrls($centralSiteUrl, $localSiteUrl, array('www'));
public static function mismatchedCentralUrlNotice() {
echo '<div id="wordfenceMismatchedCentralUrlNotice" class="fade notice notice-warning"><p><strong>' .
__('Your site is currently linked to Wordfence Central under a different site URL.', 'wordfence')
. __('This may cause duplicated scan issues if both sites are currently active and reporting and is generally caused by duplicating the database from one site to another (e.g., from a production site to staging). We recommend disconnecting this site only, which will leave the matching site still connected.', 'wordfence')
. __('If this is a single site with multiple domains or subdomains, you can dismiss this message.', 'wordfence')
. '<a class="wf-btn wf-btn-primary wf-btn-sm wf-dismiss-link" href="#" onclick="wordfenceExt.centralUrlMismatchChoice(\'local\'); return false;" role="button">' .
__('Disconnect This Site', 'wordfence')
. '<a class="wf-btn wf-btn-default wf-btn-sm wf-dismiss-link" href="#" onclick="wordfenceExt.centralUrlMismatchChoice(\'global\'); return false;" role="button">' .
__('Disconnect All', 'wordfence')
. '<a class="wf-btn wf-btn-default wf-btn-sm wf-dismiss-link" href="#" onclick="wordfenceExt.centralUrlMismatchChoice(\'dismiss\'); return false;" role="button">' .
__('Dismiss', 'wordfence')
. '<a class="wfhelp" target="_blank" rel="noopener noreferrer" href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_DIAGNOSTICS_REMOVE_CENTRAL_DATA) . '"><span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a></p></div>';