You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

719 lines
25 KiB

<?php
require_once(dirname(__FILE__) . '/wfUtils.php');
class wfIssues {
//Possible responses from `addIssue`
const ISSUE_ADDED = 'a';
const ISSUE_UPDATED = 'u';
const ISSUE_DUPLICATE = 'd';
const ISSUE_IGNOREP = 'ip';
const ISSUE_IGNOREC = 'ic';
//Possible status message states
const STATUS_NONE = 'n'; //Default state before running
const STATUS_SKIPPED = 's'; //The scan job was skipped because it didn't need to run
const STATUS_IGNORED = 'i'; //The scan job found an issue, but it matched an entry in the ignore list
const STATUS_PROBLEM = 'p'; //The scan job found an issue
const STATUS_SECURE = 'r'; //The scan job found no issues
const STATUS_FAILED = 'f'; //The scan job failed
const STATUS_SUCCESS = 'c'; //The scan job succeeded
const STATUS_PAIDONLY = 'x';
//Possible scan failure types
const SCAN_FAILED_GENERAL = 'general';
const SCAN_FAILED_TIMEOUT = 'timeout';
const SCAN_FAILED_DURATION_REACHED = 'duration';
const SCAN_FAILED_VERSION_CHANGE = 'versionchange';
const SCAN_FAILED_FORK_FAILED = 'forkfailed';
const SCAN_FAILED_CALLBACK_TEST_FAILED = 'callbackfailed';
const SCAN_FAILED_START_TIMEOUT = 'starttimeout';
const SCAN_FAILED_API_SSL_UNAVAILABLE = 'sslunavailable';
const SCAN_FAILED_API_CALL_FAILED = 'apifailed';
const SCAN_FAILED_API_INVALID_RESPONSE = 'apiinvalid';
const SCAN_FAILED_API_ERROR_RESPONSE = 'apierror';
const SEVERITY_NONE = 0;
const SEVERITY_LOW = 25;
const SEVERITY_MEDIUM = 50;
const SEVERITY_HIGH = 75;
const SEVERITY_CRITICAL = 100;
private $db = false;
//Properties that are serialized on sleep:
private $updateCalled = false;
private $issuesTable = '';
private $pendingIssuesTable = '';
private $maxIssues = 0;
private $newIssues = array();
public $totalIssues = 0;
public $totalIgnoredIssues = 0;
private $totalIssuesBySeverity = array();
public static $issueSeverities = array(
'checkGSB' => wfIssues::SEVERITY_CRITICAL,
'checkSpamIP' => wfIssues::SEVERITY_HIGH,
'spamvertizeCheck' => wfIssues::SEVERITY_CRITICAL,
'commentBadURL' => wfIssues::SEVERITY_LOW,
'postBadTitle' => wfIssues::SEVERITY_HIGH,
'postBadURL' => wfIssues::SEVERITY_HIGH,
'file' => wfIssues::SEVERITY_CRITICAL,
'timelimit' => wfIssues::SEVERITY_HIGH,
'checkHowGetIPs' => wfIssues::SEVERITY_HIGH,
'diskSpace' => wfIssues::SEVERITY_HIGH,
'wafStatus' => wfIssues::SEVERITY_CRITICAL,
'configReadable' => wfIssues::SEVERITY_CRITICAL,
'wfPluginVulnerable' => wfIssues::SEVERITY_HIGH,
'coreUnknown' => wfIssues::SEVERITY_HIGH,
'easyPasswordWeak' => wfIssues::SEVERITY_HIGH,
'knownfile' => wfIssues::SEVERITY_HIGH,
'optionBadURL' => wfIssues::SEVERITY_HIGH,
'publiclyAccessible' => wfIssues::SEVERITY_HIGH,
'suspiciousAdminUsers' => wfIssues::SEVERITY_HIGH,
'wfPluginAbandoned' => wfIssues::SEVERITY_MEDIUM,
'wfPluginRemoved' => wfIssues::SEVERITY_CRITICAL,
'wfPluginUpgrade' => wfIssues::SEVERITY_MEDIUM,
'wfThemeUpgrade' => wfIssues::SEVERITY_MEDIUM,
'wfUpgrade' => wfIssues::SEVERITY_HIGH,
'wpscan_directoryList' => wfIssues::SEVERITY_HIGH,
'wpscan_fullPathDiscl' => wfIssues::SEVERITY_HIGH,
);
public static function validIssueTypes() {
return array('checkHowGetIPs', 'checkSpamIP', 'commentBadURL', 'configReadable', 'coreUnknown', 'database', 'diskSpace', 'wafStatus', 'easyPassword', 'file', 'geoipSupport', 'knownfile', 'optionBadURL', 'postBadTitle', 'postBadURL', 'publiclyAccessible', 'spamvertizeCheck', 'suspiciousAdminUsers', 'timelimit', 'wfPluginAbandoned', 'wfPluginRemoved', 'wfPluginUpgrade', 'wfPluginVulnerable', 'wfThemeUpgrade', 'wfUpgrade', 'wpscan_directoryList', 'wpscan_fullPathDiscl');
}
public static function statusPrep(){
wfConfig::set_ser('wfStatusStartMsgs', array());
wordfence::status(10, 'info', "SUM_PREP:Preparing a new scan.");
wfIssues::updateScanStillRunning();
}
public static function statusStart($message) {
$statusStartMsgs = wfConfig::get_ser('wfStatusStartMsgs', array());
$statusStartMsgs[] = $message;
wfConfig::set_ser('wfStatusStartMsgs', $statusStartMsgs);
wordfence::status(10, 'info', 'SUM_START:' . $message);
wfIssues::updateScanStillRunning();
return count($statusStartMsgs) - 1;
}
public static function statusEnd($index, $state) {
$statusStartMsgs = wfConfig::get_ser('wfStatusStartMsgs', array());
if ($state == self::STATUS_SKIPPED) {
wordfence::status(10, 'info', 'SUM_ENDSKIPPED:' . $statusStartMsgs[$index]);
}
else if ($state == self::STATUS_IGNORED) {
wordfence::status(10, 'info', 'SUM_ENDIGNORED:' . $statusStartMsgs[$index]);
}
else if ($state == self::STATUS_PROBLEM) {
wordfence::status(10, 'info', 'SUM_ENDBAD:' . $statusStartMsgs[$index]);
}
else if ($state == self::STATUS_SECURE) {
wordfence::status(10, 'info', 'SUM_ENDOK:' . $statusStartMsgs[$index]);
}
else if ($state == self::STATUS_FAILED) {
wordfence::status(10, 'info', 'SUM_ENDFAILED:' . $statusStartMsgs[$index]);
}
else if ($state == self::STATUS_SUCCESS) {
wordfence::status(10, 'info', 'SUM_ENDSUCCESS:' . $statusStartMsgs[$index]);
}
wfIssues::updateScanStillRunning();
$statusStartMsgs[$index] = '';
wfConfig::set_ser('wfStatusStartMsgs', $statusStartMsgs);
}
public static function statusEndErr() {
$statusStartMsgs = wfConfig::get_ser('wfStatusStartMsgs', array());
for ($i = 0; $i < count($statusStartMsgs); $i++) {
if (empty($statusStartMsgs[$i]) === false) {
wordfence::status(10, 'info', 'SUM_ENDERR:' . $statusStartMsgs[$i]);
$statusStartMsgs[$i] = '';
}
}
wfIssues::updateScanStillRunning();
}
public static function statusPaidOnly($message) {
wordfence::status(10, 'info', "SUM_PAIDONLY:" . $message);
wfIssues::updateScanStillRunning();
}
public static function statusDisabled($message) {
wordfence::status(10, 'info', "SUM_DISABLED:" . $message);
wfIssues::updateScanStillRunning();
}
public static function updateScanStillRunning($running = true) {
$timestamp = time();
if (!$running) {
$timestamp = 0;
}
wfConfig::set('wf_scanLastStatusTime', $timestamp);
}
/**
* Returns false if the scan has not been detected as failed. If it has, returns a constant corresponding to the reason.
*
* @return bool|string
*/
public static function hasScanFailed() {
$lastStatusUpdate = self::lastScanStatusUpdate();
if ($lastStatusUpdate !== false && wfScanner::shared()->isRunning()) {
$threshold = WORDFENCE_SCAN_FAILURE_THRESHOLD;
if (time() - $lastStatusUpdate > $threshold) {
return self::SCAN_FAILED_TIMEOUT;
}
}
$scanStartAttempt = wfConfig::get('scanStartAttempt', 0);
if ($scanStartAttempt && time() - $scanStartAttempt > WORDFENCE_SCAN_START_FAILURE_THRESHOLD) {
return self::SCAN_FAILED_START_TIMEOUT;
}
$recordedFailure = wfConfig::get('lastScanFailureType');
switch ($recordedFailure) {
case self::SCAN_FAILED_GENERAL:
case self::SCAN_FAILED_DURATION_REACHED:
case self::SCAN_FAILED_VERSION_CHANGE:
case self::SCAN_FAILED_FORK_FAILED:
case self::SCAN_FAILED_CALLBACK_TEST_FAILED:
case self::SCAN_FAILED_API_SSL_UNAVAILABLE:
case self::SCAN_FAILED_API_CALL_FAILED:
case self::SCAN_FAILED_API_INVALID_RESPONSE:
case self::SCAN_FAILED_API_ERROR_RESPONSE:
return $recordedFailure;
}
return false;
}
/**
* Returns false if the scan has not been detected as timed out. If it has, it returns the timestamp of the last status update.
*
* @return bool|int
*/
public static function lastScanStatusUpdate() {
if (wfConfig::get('wf_scanLastStatusTime', 0) === 0) {
return false;
}
$threshold = WORDFENCE_SCAN_FAILURE_THRESHOLD;
return (time() > wfConfig::get('wf_scanLastStatusTime', 0) + $threshold) ? wfConfig::get('wf_scanLastStatusTime', 0) : false;
}
/**
* Returns the singleton wfIssues.
*
* @return wfIssues
*/
public static function shared() {
static $_issues = null;
if ($_issues === null) {
$_issues = new wfIssues();
}
return $_issues;
}
public function __sleep(){ //Same order here as vars above
return array('updateCalled', 'issuesTable', 'pendingIssuesTable', 'maxIssues', 'newIssues', 'totalIssues', 'totalIgnoredIssues', 'totalIssuesBySeverity');
}
public function __construct(){
$this->issuesTable = wfDB::networkTable('wfIssues');
$this->pendingIssuesTable = wfDB::networkTable('wfPendingIssues');
$this->maxIssues = wfConfig::get('scan_maxIssues', 0);
}
public function __wakeup(){
$this->db = new wfDB();
}
public function addIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData, $alreadyHashed = false) {
return $this->_addIssue('issue', $type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData, $alreadyHashed);
}
public function addPendingIssue($type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData) {
return $this->_addIssue('pending', $type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData);
}
/**
* Create a new issue
*
* @param string $group The issue type (e.g., issue or pending
* @param string $type
* @param int $severity
* @param string $ignoreP string to compare against for permanent ignores
* @param string $ignoreC string to compare against for ignoring until something changes
* @param string $shortMsg
* @param string $longMsg
* @param array $templateData
* @param bool $alreadyHashed If true, don't re-hash $ignoreP and $ignoreC
* @return string One of the ISSUE_ constants
*/
private function _addIssue($group, $type, $severity, $ignoreP, $ignoreC, $shortMsg, $longMsg, $templateData, $alreadyHashed = false) {
if ($group == 'pending') {
$table = $this->pendingIssuesTable;
}
else {
$table = $this->issuesTable;
}
if (!$alreadyHashed) {
$ignoreP = md5($ignoreP);
$ignoreC = md5($ignoreC);
}
$results = $this->getDB()->querySelect("SELECT id, status, ignoreP, ignoreC FROM {$table} WHERE (ignoreP = '%s' OR ignoreC = '%s')", $ignoreP, $ignoreC);
foreach ($results as $row) {
if ($row['status'] == 'new' && ($row['ignoreC'] == $ignoreC || $row['ignoreP'] == $ignoreP)) {
if ($type != 'file' && $type != 'database') { //Filter out duplicate new issues except for infected files because we want to see all infections even if file contents are identical
return self::ISSUE_DUPLICATE;
}
}
if ($row['status'] == 'ignoreP' && $row['ignoreP'] == $ignoreP) { $this->totalIgnoredIssues++; return self::ISSUE_IGNOREP; } //Always ignore
else if ($row['status'] == 'ignoreC' && $row['ignoreC'] == $ignoreC) { $this->totalIgnoredIssues++; return self::ISSUE_IGNOREC; } //Unchanged, ignore
else if ($row['status'] == 'ignoreC') {
$updateID = $row['id']; //Re-use the existing issue row
break;
}
}
if ($group != 'pending') {
if (!array_key_exists($severity, $this->totalIssuesBySeverity)) {
$this->totalIssuesBySeverity[$severity] = 0;
}
$this->totalIssuesBySeverity[$severity]++;
$this->totalIssues++;
if (empty($this->maxIssues) || $this->totalIssues <= $this->maxIssues)
{
$this->newIssues[] = array(
'type' => $type,
'severity' => $severity,
'ignoreP' => $ignoreP,
'ignoreC' => $ignoreC,
'shortMsg' => $shortMsg,
'longMsg' => $longMsg,
'tmplData' => $templateData
);
}
}
if (isset($updateID)) {
if ($group !== 'pending' && wfCentral::isConnected()) {
wfCentral::sendIssue(array(
'id' => $updateID,
'lastUpdated' => time(),
'type' => $type,
'severity' => $severity,
'ignoreP' => $ignoreP,
'ignoreC' => $ignoreC,
'shortMsg' => $shortMsg,
'longMsg' => $longMsg,
'data' => $templateData,
));
}
$this->getDB()->queryWrite(
"UPDATE {$table} SET lastUpdated = UNIX_TIMESTAMP(), status = '%s', type = '%s', severity = %d, ignoreP = '%s', ignoreC = '%s', shortMsg = '%s', longMsg = '%s', data = '%s' WHERE id = %d",
'new',
$type,
$severity,
$ignoreP,
$ignoreC,
$shortMsg,
$longMsg,
serialize($templateData),
$updateID);
return self::ISSUE_UPDATED;
}
$this->getDB()->queryWrite("INSERT INTO {$table} (time, lastUpdated, status, type, severity, ignoreP, ignoreC, shortMsg, longMsg, data) VALUES (UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s')",
'new',
$type,
$severity,
$ignoreP,
$ignoreC,
$shortMsg,
$longMsg,
serialize($templateData));
if ($group !== 'pending' && wfCentral::isConnected()) {
global $wpdb;
wfCentral::sendIssue(array(
'id' => $wpdb->insert_id,
'status' => 'new',
'time' => time(),
'lastUpdated' => time(),
'type' => $type,
'severity' => $severity,
'ignoreP' => $ignoreP,
'ignoreC' => $ignoreC,
'shortMsg' => $shortMsg,
'longMsg' => $longMsg,
'data' => $templateData,
));
}
return self::ISSUE_ADDED;
}
public function deleteIgnored(){
if (wfCentral::isConnected()) {
$result = $this->getDB()->querySelect("SELECT id from " . $this->issuesTable . " where status='ignoreP' or status='ignoreC'");
$issues = array();
foreach ($result as $row) {
$issues[] = $row['id'];
}
wfCentral::deleteIssues($issues);
}
$this->getDB()->queryWrite("delete from " . $this->issuesTable . " where status='ignoreP' or status='ignoreC'");
}
public function deleteNew($types = null) {
if (!is_array($types)) {
if (wfCentral::isConnected()) {
wfCentral::deleteNewIssues();
}
$this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new'");
}
else {
if (wfCentral::isConnected()) {
wfCentral::deleteIssueTypes($types, 'new');
}
$query = "DELETE FROM {$this->issuesTable} WHERE status = 'new' AND type IN (" . implode(',', array_fill(0, count($types), "'%s'")) . ")";
array_unshift($types, $query);
call_user_func_array(array($this->getDB(), 'queryWrite'), $types);
}
}
public function ignoreAllNew(){
if (wfCentral::isConnected()) {
$issues = $this->getDB()->querySelect('SELECT * FROM ' . $this->issuesTable . ' WHERE status=\'new\'');
if ($issues) {
wfCentral::sendIssues($issues);
}
}
$this->getDB()->queryWrite("update " . $this->issuesTable . " set status='ignoreC' where status='new'");
}
public function emailNewIssues($timeLimitReached = false, $scanController = false){
$level = wfConfig::getAlertLevel();
$emails = wfConfig::getAlertEmails();
if (!count($emails)) {
return;
}
$shortSiteURL = preg_replace('/^https?:\/\//i', '', site_url());
$subject = "[Wordfence Alert] Problems found on $shortSiteURL";
if(sizeof($emails) < 1){ return; }
if($level < 1){ return; }
$needsToAlert = false;
foreach ($this->totalIssuesBySeverity as $issueSeverity => $totalIssuesBySeverity) {
if ($issueSeverity >= $level && $totalIssuesBySeverity > 0) {
$needsToAlert = true;
break;
}
}
if (!$needsToAlert) {
return;
}
$emailedIssues = wfConfig::get_ser('emailedIssuesList', array());
if(! is_array($emailedIssues)){
$emailedIssues = array();
}
$overflowCount = $this->totalIssues - count($this->newIssues);
$finalIssues = array();
foreach($this->newIssues as $newIssue){
$alreadyEmailed = false;
foreach($emailedIssues as $emailedIssue){
if($newIssue['ignoreP'] == $emailedIssue['ignoreP'] || $newIssue['ignoreC'] == $emailedIssue['ignoreC']){
$alreadyEmailed = true;
break;
}
}
if(! $alreadyEmailed){
$finalIssues[] = $newIssue;
}
else {
$overflowCount--;
}
}
if(sizeof($finalIssues) < 1){ return; }
$this->newIssues = array();
$this->totalIssues = 0;
$totals = array();
foreach($finalIssues as $i){
$emailedIssues[] = array( 'ignoreC' => $i['ignoreC'], 'ignoreP' => $i['ignoreP'] );
if (!array_key_exists($i['severity'], $totals)) {
$totals[$i['severity']] = 0;
}
$totals[$i['severity']]++;
}
wfConfig::set_ser('emailedIssuesList', $emailedIssues);
$needsToAlert = false;
foreach ($totals as $issueSeverity => $totalIssuesBySeverity) {
if ($issueSeverity >= $level && $totalIssuesBySeverity > 0) {
$needsToAlert = true;
break;
}
}
if (!$needsToAlert) {
return;
}
$content = wfUtils::tmpl('email_newIssues.php', array(
'isPaid' => wfConfig::get('isPaid'),
'issues' => $finalIssues,
'totals' => $totals,
'level' => $level,
'issuesNotShown' => $overflowCount,
'adminURL' => get_admin_url(),
'timeLimitReached' => $timeLimitReached,
'scanController' => ($scanController ? $scanController : wfScanner::shared()),
));
foreach ($emails as $email) {
$uniqueContent = str_replace('<!-- ##UNSUBSCRIBE## -->', sprintf(__('No longer an administrator for this site? <a href="%s" target="_blank">Click here</a> to stop receiving security alerts.', 'wordfence'), wfUtils::getSiteBaseURL() . '?_wfsf=removeAlertEmail&jwt=' . wfUtils::generateJWT(array('email' => $email))), $content);
wp_mail($email, $subject, $uniqueContent, 'Content-type: text/html');
}
}
public function deleteIssue($id){
$this->getDB()->queryWrite("delete from " . $this->issuesTable . " where id=%d", $id);
if (wfCentral::isConnected()) {
wfCentral::deleteIssue($id);
}
}
public function deleteUpdateIssues($type)
{
$this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new' AND type = '%s'", $type);
if (wfCentral::isConnected()) {
wfCentral::deleteIssueTypes(array($type));
}
}
public function deleteAllUpdateIssues()
{
$this->getDB()->queryWrite("DELETE FROM {$this->issuesTable} WHERE status = 'new' AND (type = 'wfUpgrade' OR type = 'wfPluginUpgrade' OR type = 'wfThemeUpgrade')");
if (wfCentral::isConnected()) {
wfCentral::deleteIssueTypes(array('wfUpgrade', 'wfPluginUpgrade', 'wfThemeUpgrade'));
}
}
public function updateIssue($id, $status){ //ignoreC, ignoreP, delete or new
if($status == 'delete'){
if (wfCentral::isConnected()) {
wfCentral::deleteIssue($id);
}
$this->getDB()->queryWrite("delete from " . $this->issuesTable . " where id=%d", $id);
} else if($status == 'ignoreC' || $status == 'ignoreP' || $status == 'new'){
$this->getDB()->queryWrite("update " . $this->issuesTable . " set status='%s' where id=%d", $status, $id);
if (wfCentral::isConnected()) {
$issue = $this->getDB()->querySelect('SELECT * FROM ' . $this->issuesTable . ' where id=%d', $id);
if ($issue) {
wfCentral::sendIssues($issue);
}
}
}
}
public function getIssueByID($id){
$rec = $this->getDB()->querySingleRec("select * from " . $this->issuesTable . " where id=%d", $id);
$rec['data'] = unserialize($rec['data']);
return $rec;
}
public function getIssueCounts() {
global $wpdb;
$counts = $wpdb->get_results('SELECT COUNT(*) AS c, status FROM ' . $this->issuesTable . ' WHERE status = "new" OR status = "ignoreP" OR status = "ignoreC" GROUP BY status', ARRAY_A);
$result = array();
foreach ($counts as $row) {
$result[$row['status']] = $row['c'];
}
return $result;
}
public function getIssues($offset = 0, $limit = 100, $ignoredOffset = 0, $ignoredLimit = 100) {
/** @var wpdb $wpdb */
global $wpdb;
$siteCleaningTypes = array('file', 'checkGSB', 'checkSpamIP', 'commentBadURL', 'knownfile', 'optionBadURL', 'postBadTitle', 'postBadURL', 'spamvertizeCheck', 'suspiciousAdminUsers');
$sortTagging = 'CASE';
foreach ($siteCleaningTypes as $index => $t) {
$sortTagging .= ' WHEN type = \'' . esc_sql($t) . '\' THEN ' . ((int) $index);
}
$sortTagging .= ' ELSE 999 END';
$ret = array(
'new' => array(),
'ignored' => array()
);
$userIni = ini_get('user_ini.filename');
$q1 = $this->getDB()->querySelect("SELECT *, {$sortTagging} AS sortTag FROM " . $this->issuesTable . " WHERE status = 'new' ORDER BY severity DESC, sortTag ASC, type ASC, time DESC LIMIT %d,%d", $offset, $limit);
$q2 = $this->getDB()->querySelect("SELECT *, {$sortTagging} AS sortTag FROM " . $this->issuesTable . " WHERE status = 'ignoreP' OR status = 'ignoreC' ORDER BY severity DESC, sortTag ASC, type ASC, time DESC LIMIT %d,%d", $ignoredOffset, $ignoredLimit);
$q = array_merge($q1, $q2);
foreach($q as $i){
$i['data'] = unserialize($i['data']);
$i['timeAgo'] = wfUtils::makeTimeAgo(time() - $i['time']);
$i['displayTime'] = wfUtils::formatLocalTime(get_option('date_format') . ' ' . get_option('time_format'), $i['time']);
$i['longMsg'] = wp_kses($i['longMsg'], 'post');
if($i['status'] == 'new'){
$ret['new'][] = $i;
} else if($i['status'] == 'ignoreP' || $i['status'] == 'ignoreC'){
$ret['ignored'][] = $i;
} else {
error_log("Issue has bad status: " . $i['status']);
continue;
}
}
foreach($ret as $status => &$issueList){
for($i = 0; $i < sizeof($issueList); $i++){
if ($issueList[$i]['type'] == 'file' || $issueList[$i]['type'] == 'knownfile') {
$localFile = $issueList[$i]['data']['file'];
if ($localFile != '.htaccess' && $localFile != $userIni) {
$localFile = ABSPATH . '/' . preg_replace('/^[\.\/]+/', '', $localFile);
}
else {
$localFile = ABSPATH . '/' . $localFile;
}
if(file_exists($localFile)){
$issueList[$i]['data']['fileExists'] = true;
} else {
$issueList[$i]['data']['fileExists'] = '';
}
}
if ($issueList[$i]['type'] == 'database') {
$issueList[$i]['data']['optionExists'] = false;
if (!empty($issueList[$i]['data']['site_id'])) {
$table_options = wfDB::blogTable('options', $issueList[$i]['data']['site_id']);
$issueList[$i]['data']['optionExists'] = $wpdb->get_var($wpdb->prepare("SELECT count(*) FROM {$table_options} WHERE option_name = %s", $issueList[$i]['data']['option_name'])) > 0;
}
}
$issueList[$i]['issueIDX'] = $i;
if (isset($issueList[$i]['data']['cType'])) {
$issueList[$i]['data']['ucType'] = ucwords($issueList[$i]['data']['cType']);
}
}
}
return $ret; //array of lists of issues by status
}
public function getPendingIssues($offset = 0, $limit = 100){
/** @var wpdb $wpdb */
global $wpdb;
$issues = $this->getDB()->querySelect("SELECT * FROM {$this->pendingIssuesTable} ORDER BY id ASC LIMIT %d,%d", $offset, $limit);
foreach($issues as &$i){
$i['data'] = unserialize($i['data']);
}
return $issues;
}
public function getFixableIssueCount() {
global $wpdb;
$issues = $this->getDB()->querySelect("SELECT * FROM {$this->issuesTable} WHERE data LIKE '%s:6:\"canFix\";b:1;%'");
$count = 0;
foreach ($issues as $i) {
$i['data'] = unserialize($i['data']);
if (isset($i['data']['canFix']) && $i['data']['canFix']) {
$count++;
}
}
return $count;
}
public function getDeleteableIssueCount() {
global $wpdb;
$issues = $this->getDB()->querySelect("SELECT * FROM {$this->issuesTable} WHERE data LIKE '%s:9:\"canDelete\";b:1;%'");
$count = 0;
foreach ($issues as $i) {
$i['data'] = unserialize($i['data']);
if (isset($i['data']['canDelete']) && $i['data']['canDelete']) {
$count++;
}
}
return $count;
}
public function getIssueCount() {
return (int) $this->getDB()->querySingle("select COUNT(*) from " . $this->issuesTable . " WHERE status = 'new'");
}
public function getPendingIssueCount() {
return (int) $this->getDB()->querySingle("select COUNT(*) from " . $this->pendingIssuesTable . " WHERE status = 'new'");
}
public function getLastIssueUpdateTimestamp() {
return (int) $this->getDB()->querySingle("select MAX(lastUpdated) from " . $this->issuesTable);
}
public function reconcileUpgradeIssues($report = null, $useCachedValued = false) {
if ($report === null) {
$report = new wfActivityReport();
}
$updatesNeeded = $report->getUpdatesNeeded($useCachedValued);
if ($updatesNeeded) {
if (!$updatesNeeded['core']) {
$this->deleteUpdateIssues('wfUpgrade');
}
if ($updatesNeeded['plugins']) {
$upgradeNames = array();
foreach ($updatesNeeded['plugins'] as $p) {
$name = $p['Name'];
$upgradeNames[$name] = 1;
}
$upgradeIssues = $this->getDB()->querySelect("SELECT * FROM {$this->issuesTable} WHERE status = 'new' AND type = 'wfPluginUpgrade'");
foreach ($upgradeIssues as $issue) {
$data = unserialize($issue['data']);
$name = $data['Name'];
if (!isset($upgradeNames[$name])) { //Some plugins don't have a slug associated with them, so we anchor on the name
$this->deleteIssue($issue['id']);
}
}
}
else {
$this->deleteUpdateIssues('wfPluginUpgrade');
}
if ($updatesNeeded['themes']) {
$upgradeNames = array();
foreach ($updatesNeeded['themes'] as $t) {
$name = $t['Name'];
$upgradeNames[$name] = 1;
}
$upgradeIssues = $this->getDB()->querySelect("SELECT * FROM {$this->issuesTable} WHERE status = 'new' AND type = 'wfThemeUpgrade'");
foreach ($upgradeIssues as $issue) {
$data = unserialize($issue['data']);
$name = $data['Name'];
if (!isset($upgradeNames[$name])) { //Some themes don't have a slug associated with them, so we anchor on the name
$this->deleteIssue($issue['id']);
}
}
}
else {
$this->deleteUpdateIssues('wfThemeUpgrade');
}
}
else {
$this->deleteAllUpdateIssues();
}
wfScanEngine::refreshScanNotification($this);
}
private function getDB(){
if(! $this->db){
$this->db = new wfDB();
}
return $this->db;
}
/**
* @return string
*/
public function getIssuesTable() {
return $this->issuesTable;
}
}