: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
case wfIssues::SCAN_FAILED_TIMEOUT:
$scanFailedSeconds = time() - wfIssues::lastScanStatusUpdate();
$scanFailedTiming = wfUtils::makeTimeAgo($scanFailedSeconds);
if ($scanFailedSeconds > $timeLimit) {
$scanFailedTiming = sprintf(/* translators: Time until. */ __('more than %s', 'wordfence'), wfUtils::makeTimeAgo($timeLimit));
$scanFailedHTML = wfView::create('scanner/scan-failed', array(
'messageHTML' => sprintf(/* translators: Localized date. */ __('The current scan looks like it has failed. Its last status update was <span id="wf-scan-failed-time-ago">%s</span> ago. You may continue to wait in case it resumes or stop and restart the scan. Some sites may need adjustments to run scans reliably.', 'wordfence'), $scanFailedTiming) . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_FAILS) . '" target="_blank" rel="noopener noreferrer">' . __('Click here for steps you can try.', 'wordfence') . '<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>',
'buttonTitle' => __('Cancel Scan', 'wordfence'),
case wfIssues::SCAN_FAILED_FORK_FAILED:
case wfIssues::SCAN_FAILED_GENERAL:
$scanFailedHTML = wfView::create('scanner/scan-failed', array(
'messageHTML' => __('The previous scan has failed. Some sites may need adjustments to run scans reliably.', 'wordfence') . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_FAILS) . '" target="_blank" rel="noopener noreferrer">' . __('Click here for steps you can try.', 'wordfence') . '<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>',
'buttonTitle' => __('Close', 'wordfence'),
case wfIssues::SCAN_FAILED_DURATION_REACHED:
$scanFailedHTML = wfView::create('scanner/scan-failed', array(
'messageHTML' => sprintf(/* translators: Time limit (number). */ __('The previous scan has terminated because the time limit of %s was reached. This limit can be customized on the options page.', 'wordfence'), wfUtils::makeDuration($timeLimit)) . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_OPTION_OVERALL_TIME_LIMIT) . '" target="_blank" rel="noopener noreferrer" class="wf-inline-help"><i class="wf-fa wf-fa-question-circle-o" aria-hidden="true"></i><span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>',
'buttonTitle' => __('Close', 'wordfence'),
case wfIssues::SCAN_FAILED_VERSION_CHANGE:
$scanFailedHTML = wfView::create('scanner/scan-failed', array(
'messageHTML' => esc_html__('The previous scan has terminated because we detected an update occurring during the scan.', 'wordfence'),
'buttonTitle' => __('Close', 'wordfence'),
case wfIssues::SCAN_FAILED_START_TIMEOUT:
case wfIssues::SCAN_FAILED_CALLBACK_TEST_FAILED:
$resumeAttempts = wfScanMonitor::getConfiguredResumeAttempts();
if ($resumeAttempts > 0) {
if ($resumeAttempts === 1)
$resumeMessage = __('Wordfence will make one attempt to resume each failed scan stage. This scan may recover if this attempt is successful.', 'wordfence');
$resumeMessage = sprintf(__('Wordfence will make up to %d attempts to resume each failed scan stage. This scan may recover if one of these attempts is successful.', 'wordfence'), $resumeAttempts);
$resumeMessage = " {$resumeMessage} ";
$scanFailedHTML = wfView::create('scanner/scan-failed', array(
'messageTitle' => __('Scan Stage Failed', 'wordfence'),
'messageHTML' => __('A scan stage has failed to start. This is often because the site either cannot make outbound requests or is blocked from connecting to itself.', 'wordfence') . $resumeMessage . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_FAILED_START) . '" target="_blank" rel="noopener noreferrer">' . __('Click here for steps you can try.', 'wordfence') . '<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>',
'buttonTitle' => __('Close', 'wordfence'),
case wfIssues::SCAN_FAILED_API_SSL_UNAVAILABLE:
$scanFailedHTML = wfView::create('scanner/scan-failed', array(
'messageHTML' => esc_html__('Scans are not functional because SSL is unavailable.', 'wordfence'),
'buttonTitle' => __('Close', 'wordfence'),
case wfIssues::SCAN_FAILED_API_CALL_FAILED:
$scanFailedHTML = wfView::create('scanner/scan-failed', array(
'messageHTML' => __('The scan has failed because we were unable to contact the Wordfence servers. Some sites may need adjustments to run scans reliably.', 'wordfence') . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_FAILS) . '" target="_blank" rel="noopener noreferrer">' . __('Click here for steps you can try.', 'wordfence') . '<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>',
'rawErrorHTML' => esc_html(wfConfig::get('lastScanCompleted', '')),
'buttonTitle' => __('Close', 'wordfence'),
case wfIssues::SCAN_FAILED_API_INVALID_RESPONSE:
case wfIssues::SCAN_FAILED_API_ERROR_RESPONSE:
$scanFailedHTML = wfView::create('scanner/scan-failed', array(
'messageHTML' => __('The scan has failed because we received an unexpected response from the Wordfence servers. This may be a temporary error, though some sites may need adjustments to run scans reliably.', 'wordfence') . ' <a href="' . wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_FAILS) . '" target="_blank" rel="noopener noreferrer">' . __('Click here for steps you can try.', 'wordfence') . '<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>',
'rawErrorHTML' => esc_html(wfConfig::get('lastScanCompleted'), ''),
'buttonTitle' => __('Close', 'wordfence'),
'lastMessage' => $lastMessage,
'items' => self::getLog()->getStatusEvents($_POST['lastctime']),
'currentScanID' => wfScanner::shared()->lastScanTime(),
'signatureUpdateTime' => wfConfig::get('signatureUpdateTime'),
'scanFailedHTML' => $scanFailedHTML,
'scanStalled' => ($scanFailed == wfIssues::SCAN_FAILED_TIMEOUT || $scanFailed == wfIssues::SCAN_FAILED_START_TIMEOUT ? 1 : 0),
'scanRunning' => wfScanner::shared()->isRunning() ? 1 : 0,
'issueCounts' => $issueCounts,
'issueUpdateTimestamp'=> $lastIssueUpdateTimestamp,
public static function ajax_updateAlertEmail_callback(){
$email = trim($_POST['email']);
if(! preg_match('/[^\@]+\@[^\.]+\.[^\.]+/', $email) || in_array(hash('sha256', $email), wfConfig::alertEmailBlacklist())){
return array( 'err' => __("Invalid email address given.", 'wordfence'));
wfConfig::set('alertEmails', $email);
return array('ok' => 1, 'email' => $email);
private static function resolveLocalFile($issue) {
if (array_key_exists('realFile', $data)) {
return $data['realFile'];
$file = $issue['data']['file'];
$localFile = ABSPATH . '/' . $file;
$localFile = realpath($localFile);
if (strpos($localFile, ABSPATH) !== 0) {
public static function ajax_bulkOperation_callback() {
$op = sanitize_text_field($_POST['op']);
if ($op == 'del' || $op == 'repair') {
$wfIssues = new wfIssues();
$issueCount = $wfIssues->getIssueCount();
for ($offset = floor($issueCount / 100) * 100; $offset >= 0; $offset -= 100) {
$issues = $wfIssues->getIssues($offset, 100, 0, 0);
foreach ($issues['new'] as $i) {
if ($op == 'del' && @$i['data']['canDelete']) {
$file = $i['data']['file'];
$localFile = self::resolveLocalFile($i);
if ($localFile === ABSPATH . 'wp-config.php') {
$errors[] = esc_html__('Deleting an infected wp-config.php file must be done outside of Wordfence. The wp-config.php file contains your database credentials, which you will need to restore normal site operations. Your site will NOT function once the wp-config.php file has been deleted.', 'wordfence');
else if (@unlink($localFile)) {
$wfIssues->updateIssue($i['id'], 'delete');
$idsRemoved[] = $i['id'];
$errors[] = esc_html(sprintf(/* translators: 1. File path. 2. Error message. */ __('Could not delete file %1$s. Error was: %2$s', 'wordfence'), wp_kses($file, array()), wp_kses(str_replace(ABSPATH, '{WordPress Root}/', $err['message']), array())));
else if ($op == 'repair' && @$i['data']['canFix']) {
$file = $i['data']['file'];
$localFile = self::resolveLocalFile($i);
if (isset($i['data']) && is_array($i['data']) && isset($i['data']['file']) && isset($i['data']['cType']) && ( //Basics
$i['data']['cType'] == 'core' || //Core file
($i['data']['cType'] == 'plugin' || $i['data']['cType'] == 'theme') && isset($i['data']['cName']) && isset($i['data']['cVersion']) //Plugin or Theme file
$result = self::getWPFileContent($i['data']['file'], $i['data']['cType'], isset($i['data']['cName']) ? $i['data']['cName'] : null, isset($i['data']['cVersion']) ? $i['data']['cVersion'] : null);
if (is_array($result) && isset($result['errorMsg'])) {
$errors[] = esc_html($result['errorMsg']);
else if (!is_array($result) || !isset($result['fileContent'])) {
$errors[] = esc_html(sprintf(/* translators: File path. */ __('We could not retrieve the original file of %s to do a repair.', 'wordfence'), wp_kses($file, array())));
if (preg_match('/\.\./', $file)) {
$errors[] = sprintf(/* translators: File path. */ __('An invalid file %s was specified for repair.', 'wordfence'), wp_kses($file, array()));
$fh = fopen($localFile, 'w');
if (preg_match('/Permission denied/i', $err['message'])) {
$errMsg = esc_html(sprintf(/* translators: File path. */ __('You don\'t have permission to repair %s. You need to either fix the file manually using FTP or change the file permissions and ownership so that your web server has write access to repair the file.', 'wordfence'), wp_kses($file, array())));
$errMsg = esc_html(sprintf(/* translators: 1. File path. 2. Error message. */ __('We could not write to %1$s. The error was: %2$s', 'wordfence'), wp_kses($file, array()), $err['message']));
$bytes = fwrite($fh, $result['fileContent']);
$errors[] = esc_html(sprintf(/* translators: 1. File path. 2. Number of bytes. */ __('We could not write to %1$s. (%2$d bytes written) You may not have permission to modify files on your WordPress server.', 'wordfence'), wp_kses($file, array()), $bytes));
$wfIssues->updateIssue($i['id'], 'delete');
$idsRemoved[] = $i['id'];
if ($filesWorkedOn > 0 && count($errors) > 0) {
$headMsg = esc_html($op == 'del' ? __('Deleted some files with errors', 'wordfence') : __('Repaired some files with errors', 'wordfence'));
$bodyMsg = sprintf(esc_html($op == 'del' ?
/* translators: 1. Number of files. 2. Error message. */
__('Deleted %1$d files but we encountered the following errors with other files: %2$s', 'wordfence') :
/* translators: 1. Number of files. 2. Error message. */
__('Repaired %1$d files but we encountered the following errors with other files: %2$s', 'wordfence')),
$filesWorkedOn, implode('<br>', $errors));
else if ($filesWorkedOn > 0) {
$headMsg = sprintf(esc_html($op == 'del' ? /* translators: Number of files. */ __('Deleted %d files successfully', 'wordfence') : /* translators: Number of files. */ __('Repaired %d files successfully', 'wordfence')), $filesWorkedOn);
$bodyMsg = sprintf(esc_html($op == 'del' ? /* translators: Number of files. */ __('Deleted %d files successfully. No errors were encountered.', 'wordfence') : /* translators: Number of files. */ __('Repaired %d files successfully. No errors were encountered.', 'wordfence')), $filesWorkedOn);
else if (count($errors) > 0) {
$headMsg = esc_html($op == 'del' ? __('Could not delete files', 'wordfence') : __('Could not repair files', 'wordfence'));
$bodyMsg = sprintf(esc_html($op == 'del' ?
/* translators: Error message. */
__('We could not delete any of the files you selected. We encountered the following errors: %s', 'wordfence') :
/* translators: Error message. */
__('We could not repair any of the files you selected. We encountered the following errors: %s', 'wordfence')), implode('<br>', $errors));
$headMsg = esc_html__('Nothing done', 'wordfence');
$bodyMsg = esc_html($op == 'del' ? __('We didn\'t delete anything and no errors were found.', 'wordfence') : __('We didn\'t repair anything and no errors were found.', 'wordfence'));
wfScanEngine::refreshScanNotification($wfIssues);
$counts = $wfIssues->getIssueCounts();
return array('ok' => 1, 'bulkHeading' => $headMsg, 'bulkBody' => $bodyMsg, 'idsRemoved' => $idsRemoved, 'issueCounts' => $counts);
return array('errorMsg' => esc_html__('Invalid bulk operation selected', 'wordfence'));
public static function ajax_deleteFile_callback($issueID = null){
$issueID = intval($_POST['issueID']);
$wfIssues = new wfIssues();
$issue = $wfIssues->getIssueByID($issueID);
return array('errorMsg' => __('Could not delete file because we could not find that issue.', 'wordfence'));
if(! $issue['data']['file']){
return array('errorMsg' => __('Could not delete file because that issue does not appear to be a file related issue.', 'wordfence'));
$file = $issue['data']['file'];
$localFile = self::resolveLocalFile($issue);
return array('errorMsg' => __('An invalid file was requested for deletion.', 'wordfence'));
if ($file === 'wp-config.php') {
'errorMsg' => __('Deleting an infected wp-config.php file must be done outside of Wordfence. The wp-config.php file contains your database credentials, which you will need to restore normal site operations. Your site will NOT function once the wp-config.php file has been deleted.', 'wordfence')
/** @var WP_Filesystem_Base $wp_filesystem */
$adminURL = network_admin_url('admin.php?' . http_build_query(array(
'page' => 'WordfenceScan',
'subpage' => 'scan_credentials',
'action' => 'deleteFile',
'nonce' => wp_create_nonce('wp-ajax'),
if (!self::requestFilesystemCredentials($adminURL, null, true, false)) {
if ($wp_filesystem->delete($localFile)) {
$wfIssues->updateIssue($issueID, 'delete');
$counts = $wfIssues->getIssueCounts();
wfScanEngine::refreshScanNotification($wfIssues);
'localFile' => $localFile,
'issueCounts' => $counts,
/* translators: 1. File path. 2. Error message. */
__('Could not delete file %1$s. The error was: %2$s', 'wordfence'),
wp_kses(str_replace(ABSPATH, '{WordPress Root}/', $err['message']), array())
public static function ajax_deleteDatabaseOption_callback(){
$issueID = intval($_POST['issueID']);
$wfIssues = new wfIssues();
$issue = $wfIssues->getIssueByID($issueID);
return array('errorMsg' => __("Could not remove the option because we could not find that issue.", 'wordfence'));
if (empty($issue['data']['option_name'])) {
return array('errorMsg' => __("Could not remove the option because that issue does not appear to be a database related issue.", 'wordfence'));
$table_options = wfDB::blogTable('options', $issue['data']['site_id']);
if ($wpdb->query($wpdb->prepare("DELETE FROM {$table_options} WHERE option_name = %s", $issue['data']['option_name']))) {
$wfIssues->updateIssue($issueID, 'delete');
wfScanEngine::refreshScanNotification($wfIssues);
'option_name' => $issue['data']['option_name'],
return array('errorMsg' => sprintf(
/* translators: 1. WordPress option. 2. Error message. */
__('Could not remove the option %1$s. The error was: %2$s', 'wordfence'),
esc_html($issue['data']['option_name']),
esc_html($wpdb->last_error)
public static function ajax_fixFPD_callback(){
$issues = new wfIssues();
$issue = $issues->getIssueByID($_POST['issueID']);
return array('cerrorMsg' => __("We could not find that issue in our database.", 'wordfence'));
$htaccess = ABSPATH . '/.htaccess';
$change = "<IfModule mod_php5.c>\n\tphp_value display_errors 0\n</IfModule>\n<IfModule mod_php7.c>\n\tphp_value display_errors 0\n</IfModule>\n<IfModule mod_php.c>\n\tphp_value display_errors 0\n</IfModule>";
if (file_exists($htaccess)) {
$content = file_get_contents($htaccess);
if (@file_put_contents($htaccess, trim($content . "\n" . $change), LOCK_EX) === false) {
return array('cerrorMsg' => __("You don't have permission to repair .htaccess. You need to either fix the file manually using FTP or change the file permissions and ownership so that your web server has write access to repair the file.", 'wordfence'));
if (wfScanEngine::testForFullPathDisclosure()) {
// Didn't fix it, so revert the changes and return an error
file_put_contents($htaccess, $content, LOCK_EX);
'cerrorMsg' => __("Modifying the .htaccess file did not resolve the issue, so the original .htaccess file was restored. You can fix this manually by setting <code>display_errors</code> to <code>Off</code> in your php.ini if your site is on a VPS or dedicated server that you control.", 'wordfence'),
$issues->updateIssue($_POST['issueID'], 'delete');
wfScanEngine::refreshScanNotification($issues);
public static function ajax_restoreFile_callback($issueID = null){
$issueID = intval($_POST['issueID']);
$wfIssues = new wfIssues();
$issue = $wfIssues->getIssueByID($issueID);
return array('cerrorMsg' => __("We could not find that issue in our database.", 'wordfence'));
/** @var WP_Filesystem_Base $wp_filesystem */
$adminURL = network_admin_url('admin.php?' . http_build_query(array(
'page' => 'WordfenceScan',
'subpage' => 'scan_credentials',
'action' => 'restoreFile',
'nonce' => wp_create_nonce('wp-ajax'),
if (!self::requestFilesystemCredentials($adminURL, null, true, false)) {
'needsCredentials' => true,
$result = self::getWPFileContent($dat['file'], $dat['cType'], (isset($dat['cName']) ? $dat['cName'] : ''), (isset($dat['cVersion']) ? $dat['cVersion'] : ''));
if(isset($result['errorMsg']) && $result['errorMsg']){
} else if(! $result['fileContent']){
return array('errorMsg' => __("We could not get the original file to do a repair.", 'wordfence'));
if(preg_match('/\.\./', $file)){
return array('errorMsg' => __("An invalid file was specified for repair.", 'wordfence'));
if (array_key_exists('realFile', $dat)) {
$localFile = $dat['realFile'];
$localFile = rtrim(ABSPATH, '/') . '/' . preg_replace('/^[\.\/]+/', '', $file);
if ($wp_filesystem->put_contents($localFile, $result['fileContent'])) {
$wfIssues->updateIssue($issueID, 'delete');
$counts = $wfIssues->getIssueCounts();
wfScanEngine::refreshScanNotification($wfIssues);
'localFile' => $localFile,
'issueCounts' => $counts,
'errorMsg' => __("We could not write to that file. You may not have permission to modify files on your WordPress server.", 'wordfence'),
public static function ajax_scan_callback(){
self::status(4, 'info', __("Ajax request received to start scan.", 'wordfence'));
$err = wfScanEngine::startScan();
return array('errorMsg' => wp_kses($err, array()));
$issueCounts = array_merge(array('new' => 0, 'ignoreP' => 0, 'ignoreC' => 0), wfIssues::shared()->getIssueCounts());
return array("ok" => 1, 'issueCounts' => $issueCounts);
public static function ajax_exportSettings_callback() {
$result = wfImportExportController::shared()->export();
public static function ajax_importSettings_callback(){
$token = $_POST['token'];
return self::importSettings($token);
public static function importSettings($token) { //Documented call for external interfacing.
return wfImportExportController::shared()->import($token);
public static function ajax_dismissNotification_callback() {
$n = wfNotification::getNotificationForID($id);
public static function ajax_utilityScanForBlacklisted_callback() {
if (wfScanner::shared()->isRunning()) {
return array('wait' => 2); //Can't run while a scan is running since the URL hoover is currently implemented like a singleton
$pageURL = stripslashes($_POST['url']);
$source = stripslashes($_POST['source']);
$apiKey = wfConfig::get('apiKey');
$wp_version = wfUtils::getWPVersion();
$h = new wordfenceURLHoover($apiKey, $wp_version);
$hooverResults = $h->getBaddies();
return array('wait' => 3, 'errorMsg' => $h->errorMsg); //Unable to contact noc1 to verify
if (sizeof($hooverResults) > 0 && isset($hooverResults[1])) {
$hresults = $hooverResults[1];
$count = count($hresults);
wfNotification::PRIORITY_HIGH_WARNING,
sprintf(/* translators: Number of URLs. */ _n("Page contains %d malware URL: ", "Page contains %d malware URLs: ", $count, 'wordfence') . esc_html($pageURL)),
'wfplugin_malwareurl_' . md5($pageURL),
array(array('link' => wfUtils::wpAdminURL('admin.php?page=WordfenceScan'), 'label' => __('Run a Scan', 'wordfence'))));
return array('bad' => $count);
public static function ajax_dashboardShowMore_callback() {
$grouping = $_POST['grouping'];
$period = $_POST['period'];
$dashboard = new wfDashboard();
if ($grouping == 'ips') {
if ($period == '24h') { $data = $dashboard->ips24h; }
else if ($period == '7d') { $data = $dashboard->ips7d; }
else if ($period == '30d') { $data = $dashboard->ips30d; }
$d['IP'] = esc_html(wfUtils::inet_ntop($d['IP']));
$d['blockCount'] = esc_html(number_format_i18n($d['blockCount']));
$d['countryFlag'] = esc_attr('wf-flag-' . strtolower($d['countryCode']));
$d['countryName'] = esc_html($d['countryName']);