: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
catch (wfInaccessibleDirectoryException $e) {
throw new Exception(__("Wordfence could not read the content of your WordPress directory. This usually indicates your permissions are so strict that your web server can't read your WordPress directory.", 'wordfence'));
$entrypoint = new wfScanEntrypoint($scanPath->createScanFile('/'), true);
$entrypoint->addTo($entrypoints);
catch (wfInvalidPathException $e) {
wordfence::status(4, 'info', sprintf(__("Ignoring invalid base scan file: %s", 'wordfence'), $e->getPath()));
$_cache = wfScanEntrypoint::getScannedSkippedFiles($entrypoints);
private function scan_checkSkippedFiles() {
$haveIssues = wfIssues::STATUS_SECURE;
$status = wfIssues::statusStart(__("Checking for paths skipped due to scan settings", 'wordfence'));
$this->scanController->startStage(wfScanner::STAGE_SERVER_STATE);
$paths = $this->_scannedSkippedPaths();
if (!empty($paths['skipped'])) {
foreach ($paths['skipped'] as $index => $file) {
$path = esc_html($file->getDisplayPath());
$skippedList .= sprintf(/* translators: Number of paths skipped in scan. */ __(', and %d more.', 'wordfence'), count($paths['skipped']) - 10);
if (!empty($skippedList)) {
if (count($paths['skipped']) == 2) {
} else if ($index == count($paths['skipped']) - 1) {
$skippedList .= ', and ';
$c = count($paths['skipped']);
$added = $this->addIssue(
sprintf(/* translators: Number of paths skipped in scan. */ _n('%d path was skipped for the malware scan due to scan settings', '%d paths were skipped for the malware scan due to scan settings', $c, 'wordfence'), $c),
/* translators: 1. Number of paths skipped in scan. 2. Support URL. 3. List of skipped paths. */
'The option "Scan files outside your WordPress installation" is off by default, which means %1$d path and its file(s) will not be scanned for malware or unauthorized changes. To continue skipping this path, you may ignore this issue. Or to start scanning it, enable the option and subsequent scans will include it. Some paths may not be necessary to scan, so this is optional. <a href="%2$s" target="_blank" rel="noopener noreferrer">Learn More<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a><br><br>The path skipped is %3$s',
'The option "Scan files outside your WordPress installation" is off by default, which means %1$d paths and their file(s) will not be scanned for malware or unauthorized changes. To continue skipping these paths, you may ignore this issue. Or to start scanning them, enable the option and subsequent scans will include them. Some paths may not be necessary to scan, so this is optional. <a href="%2$s" target="_blank" rel="noopener noreferrer">Learn More<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a><br><br>The paths skipped are %3$s',
wfSupportController::esc_supportURL(wfSupportController::ITEM_SCAN_RESULT_SKIPPED_PATHS),
if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) {
$haveIssues = wfIssues::STATUS_PROBLEM;
} else if ($haveIssues != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) {
$haveIssues = wfIssues::STATUS_IGNORED;
wfIssues::statusEnd($status, $haveIssues);
$this->scanController->completeStage(wfScanner::STAGE_SERVER_STATE, $haveIssues);
private function scan_knownFiles_init() {
$paths = $this->_scannedSkippedPaths();
$includeInKnownFilesScan = $paths['scanned'];
if ($this->scanController->scanOutsideWordPress()) {
wordfence::status(2, 'info', __("Including files that are outside the WordPress installation in the scan.", 'wordfence'));
$this->status(2, 'info', __("Getting plugin list from WordPress", 'wordfence'));
$knownFilesPlugins = $this->getPlugins();
$this->status(2, 'info', sprintf(/* translators: Number of plugins. */ _n("Found %d plugin", "Found %d plugins", sizeof($knownFilesPlugins), 'wordfence'), sizeof($knownFilesPlugins)));
$this->status(2, 'info', __("Getting theme list from WordPress", 'wordfence'));
$knownFilesThemes = $this->getThemes();
$this->status(2, 'info', sprintf(/* translators: Number of themes. */ _n("Found %d theme", "Found %d themes", sizeof($knownFilesThemes), 'wordfence'), sizeof($knownFilesThemes)));
$this->hasher = new wordfenceHash($includeInKnownFilesScan, $this, wfUtils::hex2bin($this->malwarePrefixesHash), $this->coreHashesHash, $this->scanMode);
private function scan_knownFiles_main() {
$this->hasher->run($this); //Include this so we can call addIssue and ->api->
$this->suspectedFiles = $this->hasher->getSuspectedFiles();
private function scan_knownFiles_finish() {
private function scan_fileContents_init() {
$options = $this->scanController->scanOptions();
if ($options['scansEnabled_fileContents']) {
$this->statusIDX['infect'] = wfIssues::statusStart(__('Scanning file contents for infections and vulnerabilities', 'wordfence'));
//This stage is marked as started earlier in the hasher rather than here
wfIssues::statusDisabled(__("Skipping scan of file contents for infections and vulnerabilities", 'wordfence'));
if ($options['scansEnabled_fileContentsGSB']) {
$this->statusIDX['GSB'] = wfIssues::statusStart(__('Scanning file contents for URLs on a domain blocklist', 'wordfence'));
//This stage is marked as started earlier in the hasher rather than here
wfIssues::statusDisabled(__("Skipping scan of file contents for URLs on a domain blocklist", 'wordfence'));
if ($options['scansEnabled_fileContents'] || $options['scansEnabled_fileContentsGSB']) {
$this->scanner = new wordfenceScanner($this->apiKey, $this->wp_version, ABSPATH, $this);
$this->status(2, 'info', __("Starting scan of file contents", 'wordfence'));
private function scan_fileContents_main() {
$options = $this->scanController->scanOptions();
if ($options['scansEnabled_fileContents'] || $options['scansEnabled_fileContentsGSB']) {
$this->fileContentsResults = $this->scanner->scan($this);
private function scan_fileContents_finish() {
$options = $this->scanController->scanOptions();
if ($options['scansEnabled_fileContents'] || $options['scansEnabled_fileContentsGSB']) {
$this->status(2, 'info', __("Done file contents scan", 'wordfence'));
if ($this->scanner->errorMsg) {
throw new Exception($this->scanner->errorMsg);
$haveIssues = wfIssues::STATUS_SECURE;
$haveIssuesGSB = wfIssues::STATUS_SECURE;
foreach ($this->fileContentsResults as $issue) {
$this->status(2, 'info', sprintf(/* translators: Scan result description. */ __("Adding issue: %s", 'wordfence'), $issue['shortMsg']));
$added = $this->addIssue($issue['type'], $issue['severity'], $issue['ignoreP'], $issue['ignoreC'], $issue['shortMsg'], $issue['longMsg'], $issue['data']);
if (isset($issue['data']['gsb'])) {
if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) {
$haveIssuesGSB = wfIssues::STATUS_PROBLEM;
} else if ($haveIssuesGSB != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) {
$haveIssuesGSB = wfIssues::STATUS_IGNORED;
if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) {
$haveIssues = wfIssues::STATUS_PROBLEM;
} else if ($haveIssues != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) {
$haveIssues = wfIssues::STATUS_IGNORED;
$this->fileContentsResults = null;
if ($options['scansEnabled_fileContents']) {
wfIssues::statusEnd($this->statusIDX['infect'], $haveIssues);
$this->scanController->completeStage(wfScanner::STAGE_MALWARE_SCAN, $haveIssues);
if ($options['scansEnabled_fileContentsGSB']) {
wfIssues::statusEnd($this->statusIDX['GSB'], $haveIssuesGSB);
$this->scanController->completeStage(wfScanner::STAGE_CONTENT_SAFETY, $haveIssuesGSB);
private function scan_suspectedFiles() {
$haveIssues = wfIssues::STATUS_SECURE;
$status = wfIssues::statusStart(__("Scanning for publicly accessible quarantined files", 'wordfence'));
$this->scanController->startStage(wfScanner::STAGE_PUBLIC_FILES);
if (is_array($this->suspectedFiles) && count($this->suspectedFiles) > 0) {
foreach ($this->suspectedFiles as $file) {
wordfence::status(4, 'info', sprintf(/* translators: File path. */ __("Testing accessibility of: %s", 'wordfence'), $file));
$test = wfPubliclyAccessibleFileTest::createFromRootPath($file);
if ($test->fileExists() && $test->isPubliclyAccessible()) {
$key = "publiclyAccessible" . bin2hex($test->getUrl());
$added = $this->addIssue(
sprintf(/* translators: File path. */ __('Publicly accessible quarantined file found: %s', 'wordfence'), esc_html($file)),
/* translators: URL to publicly accessible file. */
__('<a href="%1$s" target="_blank" rel="noopener noreferrer">%1$s<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a> is publicly accessible and may expose source code or sensitive information about your site. Files such as this one are commonly checked for by scanners and should be removed or made inaccessible.', 'wordfence'),
'url' => $test->getUrl(),
if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) {
$haveIssues = wfIssues::STATUS_PROBLEM;
} else if ($haveIssues != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) {
$haveIssues = wfIssues::STATUS_IGNORED;
wfIssues::statusEnd($status, $haveIssues);
$this->scanController->completeStage(wfScanner::STAGE_PUBLIC_FILES, $haveIssues);
private function scan_posts_init() {
$this->statusIDX['posts'] = wfIssues::statusStart(__('Scanning posts for URLs on a domain blocklist', 'wordfence'));
$this->scanController->startStage(wfScanner::STAGE_CONTENT_SAFETY);
$blogsToScan = self::getBlogsToScan('posts');
$this->hoover = new wordfenceURLHoover($this->apiKey, $this->wp_version);
foreach ($blogsToScan as $blog) {
$q1 = $wfdb->querySelect("select ID from " . $blog['table'] . " where post_type IN ('page', 'post') and post_status = 'publish'");
foreach ($q1 as $idRow) {
$this->scanQueue .= pack('LL', $blog['blog_id'], $idRow['ID']);
private function scan_posts_main() {
while (strlen($this->scanQueue) > 0) {
$segment = substr($this->scanQueue, 0, 8);
$this->scanQueue = substr($this->scanQueue, 8);
$elem = unpack('Lblog/Lpost', $segment);
$queueSize = strlen($this->scanQueue) / 8;
if ($queueSize > 0 && $queueSize % 1000 == 0) {
wordfence::status(2, 'info', sprintf(/* translators: Number of posts left to scan. */ __("Scanning posts with %d left to scan.", 'wordfence'), $queueSize));
$this->scanController->incrementSummaryItem(wfScanner::SUMMARY_SCANNED_POSTS);
$blogs = self::getBlogsToScan('posts', $blogID);
$blog = array_shift($blogs);
$table = wfDB::blogTable('posts', $blogID);
$row = $wfdb->querySingleRec("select ID, post_title, post_type, post_date, post_content from {$table} where ID = %d", $postID);
$found = $this->hoover->hoover($blogID . '-' . $row['ID'], $row['post_title'] . ' ' . $row['post_content'], wordfenceURLHoover::standardExcludedHosts());
$this->scanController->incrementSummaryItem(wfScanner::SUMMARY_SCANNED_URLS, $found);
if (preg_match('/(?:<[\s\n\r\t]*script[\r\s\n\t]+.*>|<[\s\n\r\t]*meta.*refresh)/i', $row['post_title'])) {
__("Post title contains suspicious code", 'wordfence'),
__("This post contains code that is suspicious. Please check the title of the post and confirm that the code in the title is not malicious.", 'wordfence'),
'postTitle' => $row['post_title'],
'permalink' => get_permalink($postID),
'editPostLink' => get_edit_post_link($postID),
'type' => $row['post_type'],
'postDate' => $row['post_date'],
'isMultisite' => $blog['isMultisite'],
'domain' => $blog['domain'],
'blog_id' => $blog['blog_id']
private function scan_posts_finish() {
$this->status(2, 'info', __("Examining URLs found in posts we scanned for dangerous websites", 'wordfence'));
$hooverResults = $this->hoover->getBaddies();
$this->status(2, 'info', __("Done examining URLs", 'wordfence'));
if ($this->hoover->errorMsg) {
wfIssues::statusEndErr();
throw new Exception($this->hoover->errorMsg);
$this->hoover->cleanup();
$haveIssues = wfIssues::STATUS_SECURE;
foreach ($hooverResults as $idString => $hresults) {
$arr = explode('-', $idString);
$table = wfDB::blogTable('posts', $blogID);
foreach ($hresults as $result) {
if ($result['badList'] != 'goog-malware-shavar' && $result['badList'] != 'googpub-phish-shavar' && $result['badList'] != 'wordfence-dbl') {
continue; //A list type that may be new and the plugin has not been upgraded yet.
$blogs = self::getBlogsToScan('posts', $blogID);
$blog = array_shift($blogs);
$post = $wfdb->querySingleRec("select ID, post_title, post_type, post_date, post_content from {$table} where ID = %d", $postID);
$type = $post['post_type'] ? $post['post_type'] : 'comment';
$uctype = ucfirst($type);
$postDate = $post['post_date'];
$title = $post['post_title'];
$contentMD5 = md5($post['post_content']);
if ($result['badList'] == 'goog-malware-shavar') {
/* translators: 1. WordPress Post type. 2. URL. */
__('%1$s contains a suspected malware URL: %2$s', 'wordfence'),
/* translators: 1. WordPress Post type. 2. URL. 3. URL. */
__('This %1$s contains a suspected malware URL listed on Google\'s list of malware sites. The URL is: %2$s - More info available at <a href="http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=%3$s&client=googlechrome&hl=en-US" target="_blank" rel="noopener noreferrer">Google Safe Browsing diagnostic page<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>.', 'wordfence'),
esc_html($result['URL']),
urlencode($result['URL'])
} else if ($result['badList'] == 'googpub-phish-shavar') {
$shortMsg = sprintf(/* translators: 1. WordPress Post type. 2. URL. */ __('%1$s contains a suspected phishing site URL: %2$s', 'wordfence'), $uctype, esc_html($title));
/* translators: 1. WordPress Post type. 2. URL. */
__('This %1$s contains a URL that is a suspected phishing site that is currently listed on Google\'s list of known phishing sites. The URL is: %2$s', 'wordfence'),
} else if ($result['badList'] == 'wordfence-dbl') {
$shortMsg = sprintf(/* translators: 1. WordPress Post type. 2. URL. */ __('%1$s contains a suspected malware URL: %2$s', 'wordfence'), $uctype, esc_html($title));
/* translators: 1. WordPress Post type. 2. URL. */
__('This %1$s contains a URL that is currently listed on Wordfence\'s domain blocklist. The URL is: %2$s', 'wordfence'),
//A list type that may be new and the plugin has not been upgraded yet.
$this->status(2, 'info', sprintf(/* translators: Scan result description. */ __('Adding issue: %1$s', 'wordfence'), $shortMsg));
$ignoreC = $idString . $contentMD5;
$added = $this->addIssue('postBadURL', wfIssues::SEVERITY_HIGH, $ignoreP, $ignoreC, $shortMsg, $longMsg, array(
'badURL' => $result['URL'],
'permalink' => get_permalink($postID),
'editPostLink' => get_edit_post_link($postID),
'isMultisite' => $blog['isMultisite'],
'domain' => $blog['domain'],
if ($added == wfIssues::ISSUE_ADDED || $added == wfIssues::ISSUE_UPDATED) {
$haveIssues = wfIssues::STATUS_PROBLEM;
} else if ($haveIssues != wfIssues::STATUS_PROBLEM && ($added == wfIssues::ISSUE_IGNOREP || $added == wfIssues::ISSUE_IGNOREC)) {
$haveIssues = wfIssues::STATUS_IGNORED;
wfIssues::statusEnd($this->statusIDX['posts'], $haveIssues);
$this->scanController->completeStage(wfScanner::STAGE_CONTENT_SAFETY, $haveIssues);
private function scan_comments_init() {
$this->statusIDX['comments'] = wfIssues::statusStart(__('Scanning comments for URLs on a domain blocklist', 'wordfence'));
$this->scanController->startStage(wfScanner::STAGE_CONTENT_SAFETY);
$this->scanData = array();
$this->hoover = new wordfenceURLHoover($this->apiKey, $this->wp_version);
$blogsToScan = self::getBlogsToScan('comments');
foreach ($blogsToScan as $blog) {
$q1 = $wfdb->querySelect("select comment_ID from " . $blog['table'] . " where comment_approved=1 and not comment_type = 'order_note'");
foreach ($q1 as $idRow) {
$this->scanQueue .= pack('LL', $blog['blog_id'], $idRow['comment_ID']);
private function scan_comments_main() {
while (strlen($this->scanQueue) > 0) {
$segment = substr($this->scanQueue, 0, 8);
$this->scanQueue = substr($this->scanQueue, 8);
$elem = unpack('Lblog/Lcomment', $segment);
$queueSize = strlen($this->scanQueue) / 8;
if ($queueSize > 0 && $queueSize % 1000 == 0) {
wordfence::status(2, 'info', sprintf(/* translators: Number of comments left to scan. */ __("Scanning comments with %d left to scan.", 'wordfence'), $queueSize));
$this->scanController->incrementSummaryItem(wfScanner::SUMMARY_SCANNED_COMMENTS);
$commentID = $elem['comment'];
$table = wfDB::blogTable('comments', $blogID);
$row = $wfdb->querySingleRec("select comment_ID, comment_date, comment_type, comment_author, comment_author_url, comment_content from {$table} where comment_ID=%d", $commentID);
$found = $this->hoover->hoover($blogID . '-' . $row['comment_ID'], $row['comment_author_url'] . ' ' . $row['comment_author'] . ' ' . $row['comment_content'], wordfenceURLHoover::standardExcludedHosts());
$this->scanController->incrementSummaryItem(wfScanner::SUMMARY_SCANNED_URLS, $found);
private function scan_comments_finish() {
$hooverResults = $this->hoover->getBaddies();
if ($this->hoover->errorMsg) {
wfIssues::statusEndErr();
throw new Exception($this->hoover->errorMsg);
$this->hoover->cleanup();
$haveIssues = wfIssues::STATUS_SECURE;
foreach ($hooverResults as $idString => $hresults) {
$arr = explode('-', $idString);
foreach ($hresults as $result) {
if ($result['badList'] != 'goog-malware-shavar' && $result['badList'] != 'googpub-phish-shavar' && $result['badList'] != 'wordfence-dbl') {
continue; //A list type that may be new and the plugin has not been upgraded yet.
$blogs = self::getBlogsToScan('comments', $blogID);
$blog = array_shift($blogs);
$comment = $wfdb->querySingleRec("select comment_ID, comment_date, comment_type, comment_author, comment_author_url, comment_content from " . $blog['table'] . " where comment_ID=%d", $commentID);
$type = $comment['comment_type'] ? $comment['comment_type'] : 'comment';
$uctype = ucfirst($type);
$author = $comment['comment_author'];
$date = $comment['comment_date'];
$contentMD5 = md5($comment['comment_content'] . $comment['comment_author'] . $comment['comment_author_url']);
if ($result['badList'] == 'goog-malware-shavar') {
/* translators: 1. WordPress post type. 2. WordPress author username. */
__('%1$s with author %2$s contains a suspected malware URL.', 'wordfence'), $uctype, esc_html($author));
/* translators: 1. WordPress post type. 2. URL. 3. URL. */
__('This %1$s contains a suspected malware URL listed on Google\'s list of malware sites. The URL is: %2$s - More info available at <a href="http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=%3$s&client=googlechrome&hl=en-US" target="_blank" rel="noopener noreferrer">Google Safe Browsing diagnostic page<span class="screen-reader-text"> (' . esc_html__('opens in new tab', 'wordfence') . ')</span></a>.', 'wordfence'),
esc_html($result['URL']),
urlencode($result['URL'])
} else if ($result['badList'] == 'googpub-phish-shavar') {
$shortMsg = sprintf(/* translators: WordPress post type. */ __("%s contains a suspected phishing site URL.", 'wordfence'), $uctype);
/* translators: 1. WordPress post type. 2. URL. */
__('This %1$s contains a URL that is a suspected phishing site that is currently listed on Google\'s list of known phishing sites. The URL is: %2$s', 'wordfence'),
} else if ($result['badList'] == 'wordfence-dbl') {
$shortMsg = sprintf(/* translators: URL. */ __("%s contains a suspected malware URL.", 'wordfence'), $uctype);
/* translators: 1. WordPress post type. 2. URL. */