diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0492315c5eacd..ca07217037c1a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1175,6 +1175,9 @@ importers:
'@wordpress/i18n':
specifier: 5.9.0
version: 5.9.0
+ '@wordpress/icons':
+ specifier: 10.9.0
+ version: 10.9.0(react@18.3.1)
'@wordpress/url':
specifier: 4.9.0
version: 4.9.0
@@ -4207,6 +4210,9 @@ importers:
'@automattic/jetpack-connection':
specifier: workspace:*
version: link:../../js-packages/connection
+ '@automattic/jetpack-scan':
+ specifier: workspace:*
+ version: link:../../js-packages/scan
'@tanstack/react-query':
specifier: 5.20.5
version: 5.20.5(react@18.3.1)
@@ -7827,12 +7833,6 @@ packages:
peerDependencies:
react: ^18.0.0
- '@wordpress/dataviews@4.4.4':
- resolution: {integrity: sha512-b+2DTP8uPznxpnD0khRHDUeuj3U5Cy32amr3vwiN9xqV9hl51fzSe+ELAUTHrFKlMaQNkH/0c8cH81fU0JIeuw==}
- engines: {node: '>=18.12.0', npm: '>=8.19.2'}
- peerDependencies:
- react: ^18.0.0
-
'@wordpress/dataviews@4.5.0':
resolution: {integrity: sha512-3vZN6jFR6gFDvuAitpS/0D80ByWYkhRfuTAAmzptq6rC9CkC4VNRbIJZbxMsKEt2qh44T7TVYVR6yVm2p+8+oQ==}
engines: {node: '>=18.12.0', npm: '>=8.19.2'}
@@ -18747,28 +18747,6 @@ snapshots:
rememo: 4.0.2
use-memo-one: 1.1.3(react@18.3.1)
- '@wordpress/dataviews@4.4.4(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@ariakit/react': 0.4.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@babel/runtime': 7.24.7
- '@wordpress/components': 28.9.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@wordpress/compose': 7.9.0(react@18.3.1)
- '@wordpress/data': 10.9.0(react@18.3.1)
- '@wordpress/element': 6.9.0
- '@wordpress/i18n': 5.9.0
- '@wordpress/icons': 10.9.0(react@18.3.1)
- '@wordpress/primitives': 4.9.0(react@18.3.1)
- '@wordpress/private-apis': 1.9.0
- '@wordpress/warning': 3.9.0
- clsx: 2.1.1
- react: 18.3.1
- remove-accents: 0.5.0
- transitivePeerDependencies:
- - '@emotion/is-prop-valid'
- - '@types/react'
- - react-dom
- - supports-color
-
'@wordpress/dataviews@4.5.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@ariakit/react': 0.4.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -19372,9 +19350,9 @@ snapshots:
'@wordpress/icons@10.9.0(react@18.3.1)':
dependencies:
- '@babel/runtime': 7.24.7
+ '@babel/runtime': 7.25.7
'@wordpress/element': 6.9.0
- '@wordpress/primitives': 4.9.0(react@18.3.1)
+ '@wordpress/primitives': 4.10.0(react@18.3.1)
react: 18.3.1
'@wordpress/interactivity-router@2.9.0':
diff --git a/projects/js-packages/components/components/threats-data-view/constants.ts b/projects/js-packages/components/components/threats-data-view/constants.ts
index e9b2fea3e1db5..a3b8f4449f885 100644
--- a/projects/js-packages/components/components/threats-data-view/constants.ts
+++ b/projects/js-packages/components/components/threats-data-view/constants.ts
@@ -2,11 +2,12 @@ import { __ } from '@wordpress/i18n';
export const PAID_PLUGIN_SUPPORT_URL = 'https://jetpack.com/contact-support/?rel=support';
-export const THREAT_STATUSES = [
- { value: 'current', label: __( 'Active', 'jetpack' ) },
- { value: 'fixed', label: __( 'Fixed', 'jetpack' ) },
- { value: 'ignored', label: __( 'Ignored', 'jetpack' ) },
-];
+export const THREAT_STATUSES: { value: string; label: string; variant?: 'success' | 'warning' }[] =
+ [
+ { value: 'current', label: __( 'Active', 'jetpack' ), variant: 'warning' },
+ { value: 'fixed', label: __( 'Fixed', 'jetpack' ), variant: 'success' },
+ { value: 'ignored', label: __( 'Ignored', 'jetpack' ) },
+ ];
export const THREAT_TYPES = [
{ value: 'plugin', label: __( 'Plugin', 'jetpack' ) },
diff --git a/projects/js-packages/components/components/threats-data-view/index.tsx b/projects/js-packages/components/components/threats-data-view/index.tsx
index 2377e7a3a12cd..5087632270ad8 100644
--- a/projects/js-packages/components/components/threats-data-view/index.tsx
+++ b/projects/js-packages/components/components/threats-data-view/index.tsx
@@ -3,12 +3,14 @@ import {
Action,
DataViews,
Field,
+ FieldType,
Filter,
filterSortAndPaginate,
SortDirection,
SupportedLayouts,
type View,
} from '@wordpress/dataviews';
+import { dateI18n } from '@wordpress/date';
import { __, _x } from '@wordpress/i18n';
import { useCallback, useMemo, useState } from 'react';
import Badge from '../badge';
@@ -62,7 +64,7 @@ export default function ThreatsDataView( {
search: '',
filters: filters || [],
page: 1,
- perPage: 25,
+ perPage: 20,
};
/**
@@ -209,6 +211,15 @@ export default function ThreatsDataView( {
THREAT_STATUSES.find( ( { value } ) => value === item.status )?.value ?? item.status
);
},
+ render( { item }: { item: DataViewThreat } ) {
+ if ( item.status ) {
+ const status = THREAT_STATUSES.find( ( { value } ) => value === item.status );
+ if ( status ) {
+ return { status?.label };
+ }
+ }
+ return { __( 'Active', 'jetpack' ) };
+ },
},
{
id: 'extension',
@@ -322,6 +333,44 @@ export default function ThreatsDataView( {
},
]
: [] ),
+ ...( dataFields.includes( 'firstDetected' )
+ ? [
+ {
+ id: 'first-detected',
+ label: __( 'First Detected', 'jetpack' ),
+ type: 'datetime' as FieldType,
+ getValue( { item }: { item: DataViewThreat } ) {
+ return new Date( item.firstDetected );
+ },
+ render( { item }: { item: DataViewThreat } ) {
+ return (
+
+ { dateI18n( 'F j Y', item.firstDetected, false ) }
+
+ );
+ },
+ },
+ ]
+ : [] ),
+ ...( dataFields.includes( 'fixedOn' )
+ ? [
+ {
+ id: 'fixed-on',
+ label: __( 'Fixed On', 'jetpack' ),
+ type: 'datetime' as FieldType,
+ getValue( { item }: { item: DataViewThreat } ) {
+ return new Date( item.firstDetected );
+ },
+ render( { item }: { item: DataViewThreat } ) {
+ return (
+
+ { dateI18n( 'F j Y', item.firstDetected, false ) }
+
+ );
+ },
+ },
+ ]
+ : [] ),
];
return result;
@@ -351,6 +400,9 @@ export default function ThreatsDataView( {
return !! item.fixable;
},
icon: 'check',
+ // RenderModal: item => {
+ // return
Hello World!
;
+ // },
} );
}
@@ -411,6 +463,7 @@ export default function ThreatsDataView( {
* @see https://github.com/WordPress/gutenberg/blob/trunk/packages/dataviews/src/filter-and-sort-data-view.ts
*/
const { data: processedData, paginationInfo } = useMemo( () => {
+ // to do: secondary sort for status, detected on, etc.
return filterSortAndPaginate( data, view, fields );
}, [ data, view, fields ] );
diff --git a/projects/js-packages/components/components/threats-data-view/styles.module.scss b/projects/js-packages/components/components/threats-data-view/styles.module.scss
index 5cf22e93b8a4e..f0be5ebc495c7 100644
--- a/projects/js-packages/components/components/threats-data-view/styles.module.scss
+++ b/projects/js-packages/components/components/threats-data-view/styles.module.scss
@@ -98,6 +98,15 @@
font-size: 12px;
}
+.threat__fixedOn,
+.threat__firstDetected {
+ white-space: nowrap;
+}
+
+.threat__fixedOn {
+ color: var( --jp-green-70 );
+}
+
.icon-spinner {
svg {
margin: 0;
diff --git a/projects/js-packages/scan/package.json b/projects/js-packages/scan/package.json
index 480b5a2e424e6..91c53c80921af 100644
--- a/projects/js-packages/scan/package.json
+++ b/projects/js-packages/scan/package.json
@@ -57,6 +57,7 @@
"@wordpress/api-fetch": "7.9.0",
"@wordpress/element": "6.9.0",
"@wordpress/i18n": "5.9.0",
+ "@wordpress/icons": "10.9.0",
"@wordpress/url": "4.9.0",
"debug": "4.3.4",
"react": "^18.2.0",
diff --git a/projects/js-packages/scan/src/index.ts b/projects/js-packages/scan/src/index.ts
index e69de29bb2d1d..9be8099fb951c 100644
--- a/projects/js-packages/scan/src/index.ts
+++ b/projects/js-packages/scan/src/index.ts
@@ -0,0 +1 @@
+export * from './utils.js';
diff --git a/projects/js-packages/scan/src/types/fixers.d.ts b/projects/js-packages/scan/src/types/fixers.d.ts
new file mode 100644
index 0000000000000..b99a93def29a4
--- /dev/null
+++ b/projects/js-packages/scan/src/types/fixers.d.ts
@@ -0,0 +1,40 @@
+export type FixerStatus = 'not_started' | 'in_progress' | 'fixed' | 'not_fixed';
+
+/**
+ * Threat Fix Status
+ *
+ * Individual fixer status for a threat.
+ */
+export type ThreatFixStatusError = {
+ error: string;
+};
+
+export type ThreatFixStatusSuccess = {
+ status: FixerStatus;
+ last_updated: string;
+};
+
+export type ThreatFixStatus = ThreatFixStatusError | ThreatFixStatusSuccess;
+
+/**
+ * Fixers Status
+ *
+ * Overall status of all fixers.
+ */
+type FixersStatusBase = {
+ ok: boolean; // Discriminator for overall success
+};
+
+export type FixersStatusError = FixersStatusBase & {
+ ok: false;
+ error: string;
+};
+
+export type FixersStatusSuccess = FixersStatusBase & {
+ ok: true;
+ threats: {
+ [ key: number ]: ThreatFixStatus;
+ };
+};
+
+export type FixersStatus = FixersStatusSuccess | FixersStatusError;
diff --git a/projects/js-packages/scan/src/types/threat.d.ts b/projects/js-packages/scan/src/types/threat.d.ts
index 757503972fa0c..09bf2a43f6082 100644
--- a/projects/js-packages/scan/src/types/threat.d.ts
+++ b/projects/js-packages/scan/src/types/threat.d.ts
@@ -56,4 +56,12 @@ export type Threat = {
/** The diff showing the threat's modified file contents. */
diff?: string;
+
+ /** The affected extension. */
+ extension?: {
+ slug: string;
+ name: string;
+ version: string;
+ type: 'plugin' | 'theme' | 'core';
+ };
};
diff --git a/projects/js-packages/scan/src/utils.ts b/projects/js-packages/scan/src/utils.ts
new file mode 100644
index 0000000000000..16c222a6d510a
--- /dev/null
+++ b/projects/js-packages/scan/src/utils.ts
@@ -0,0 +1,77 @@
+import { code, color, grid, plugins, shield, wordpress } from '@wordpress/icons';
+import { ThreatFixStatus } from './types/fixers.js';
+import { Threat } from './types/threat.js';
+
+export const getThreatIcon = ( threat: Threat ) => {
+ const type = getThreatType( threat );
+
+ switch ( type ) {
+ case 'plugin':
+ return plugins;
+ case 'theme':
+ return color;
+ case 'core':
+ return wordpress;
+ case 'file':
+ return code;
+ case 'database':
+ return grid;
+ default:
+ return shield;
+ }
+};
+
+export const getThreatType = ( threat: Threat ) => {
+ if ( threat.signature === 'Vulnerable.WP.Core' ) {
+ return 'core';
+ }
+ if ( threat.extension ) {
+ return threat.extension.type;
+ }
+ if ( threat.filename ) {
+ return 'file';
+ }
+ if ( threat.table ) {
+ return 'database';
+ }
+
+ return null;
+};
+
+export const getThreatSubtitle = ( threat: Threat ) => {
+ const type = getThreatType( threat );
+
+ switch ( type ) {
+ case 'plugin':
+ case 'theme':
+ return `${ threat.extension?.name } (${ threat.extension?.version })`;
+ case 'core':
+ return 'WordPress Core';
+ case 'file':
+ // Trim leading slash
+ if ( threat.filename.startsWith( '/' ) ) {
+ return threat.filename.slice( 1 );
+ }
+ return threat.filename;
+ case 'database':
+ return threat.table;
+ default:
+ return '';
+ }
+};
+
+const FIXER_IS_STALE_THRESHOLD = 1000 * 60 * 60 * 24; // 24 hours
+
+export const fixerTimestampIsStale = ( lastUpdatedTimestamp: string ) => {
+ const now = new Date();
+ const lastUpdated = new Date( lastUpdatedTimestamp );
+ return now.getTime() - lastUpdated.getTime() >= FIXER_IS_STALE_THRESHOLD;
+};
+
+export const fixerStatusIsStale = ( fixerStatus: ThreatFixStatus ) => {
+ return (
+ 'status' in fixerStatus &&
+ fixerStatus.status === 'in_progress' &&
+ fixerTimestampIsStale( fixerStatus.last_updated )
+ );
+};
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats-status.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats-status.tsx
index 4349419dc95f3..ddb14a3915fe5 100644
--- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats-status.tsx
+++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/scan-threats-status.tsx
@@ -24,28 +24,14 @@ export const ScanAndThreatStatus = () => {
const {
protect: { scanData },
} = getMyJetpackWindowInitialState();
- const { plugins, themes, num_threats: numThreats = 0 } = scanData || {};
+ const numThreats = scanData.threats.length;
const criticalScanThreatCount = useMemo( () => {
- const { core, database, files, num_plugins_threats, num_themes_threats } = scanData || {};
- const pluginsThreats = num_plugins_threats
- ? plugins.reduce( ( accum, plugin ) => accum.concat( plugin.threats ), [] )
- : [];
- const themesThreats = num_themes_threats
- ? themes.reduce( ( accum, theme ) => accum.concat( theme.threats ), [] )
- : [];
- const allThreats = [
- ...pluginsThreats,
- ...themesThreats,
- ...( core?.threats ?? [] ),
- ...database,
- ...files,
- ];
- return allThreats.reduce(
+ return scanData.threats.reduce(
( accum, threat ) => ( threat.severity >= 5 ? ( accum += 1 ) : accum ),
0
);
- }, [ plugins, themes, scanData ] );
+ }, [ scanData.threats ] );
if ( isPluginActive && isSiteConnected ) {
if ( hasProtectPaidPlan ) {
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-last-scan-text.ts b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-last-scan-text.ts
index 1cf61f6ce0edf..0eb498144465a 100644
--- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-last-scan-text.ts
+++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-last-scan-text.ts
@@ -13,14 +13,10 @@ export const useLastScanText = () => {
themes,
protect: { scanData },
} = getMyJetpackWindowInitialState();
- const {
- plugins: fromScanPlugins,
- themes: fromScanThemes,
- last_checked: lastScanTime = null,
- } = scanData || {};
+ const { last_checked: lastScanTime = null } = scanData || {};
- const pluginsCount = fromScanPlugins.length || Object.keys( plugins ).length;
- const themesCount = fromScanThemes.length || Object.keys( themes ).length;
+ const pluginsCount = Object.keys( plugins ).length;
+ const themesCount = Object.keys( themes ).length;
const timeSinceLastScan = lastScanTime ? timeSince( Date.parse( lastScanTime ) ) : false;
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts
index 6f95e251ea099..f2e2b48fd5901 100644
--- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts
+++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts
@@ -35,19 +35,15 @@ export function useProtectTooltipCopy(): TooltipContent {
themes,
protect: { scanData, wafConfig: wafData },
} = getMyJetpackWindowInitialState();
- const {
- plugins: fromScanPlugins,
- themes: fromScanThemes,
- num_threats: numThreats = 0,
- } = scanData || {};
+ const numThreats = scanData.threats.length;
const {
jetpack_waf_automatic_rules: isAutoFirewallEnabled,
blocked_logins: blockedLoginsCount,
brute_force_protection: hasBruteForceProtection,
} = wafData || {};
- const pluginsCount = fromScanPlugins.length || Object.keys( plugins ).length;
- const themesCount = fromScanThemes.length || Object.keys( themes ).length;
+ const pluginsCount = Object.keys( plugins ).length;
+ const themesCount = Object.keys( themes ).length;
const settingsLink = useMemo( () => {
if ( isProtectPluginActive ) {
diff --git a/projects/packages/my-jetpack/changelog/protect-status-compat b/projects/packages/my-jetpack/changelog/protect-status-compat
new file mode 100644
index 0000000000000..14eba53e0fcfc
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/protect-status-compat
@@ -0,0 +1,5 @@
+Significance: patch
+Type: changed
+Comment: Package compatibility updates, no functional changes.
+
+
diff --git a/projects/packages/my-jetpack/global.d.ts b/projects/packages/my-jetpack/global.d.ts
index f302d8567e765..cb8794181c659 100644
--- a/projects/packages/my-jetpack/global.d.ts
+++ b/projects/packages/my-jetpack/global.d.ts
@@ -48,6 +48,12 @@ type ThreatItem = {
fixed_in: string;
description: string | null;
source: string | null;
+ extension: {
+ slug: string;
+ name: string;
+ version: string;
+ type: 'plugin' | 'theme' | 'core';
+ };
// Scan API properties (paid plan)
context: string | null;
filename: string | null;
@@ -58,15 +64,6 @@ type ThreatItem = {
status: number | null;
};
-type ScanItem = {
- checked: boolean;
- name: string;
- slug: string;
- threats: ThreatItem[];
- type: string;
- version: string;
-};
-
interface Window {
myJetpackInitialState?: {
siteSuffix: string;
@@ -211,22 +208,15 @@ interface Window {
};
protect: {
scanData: {
- core: ScanItem;
+ threats: ThreatItem[];
current_progress?: string;
data_source: string;
- database: string[];
error: boolean;
error_code?: string;
error_message?: string;
- files: string[];
has_unchecked_items: boolean;
last_checked: string;
- num_plugins_threats: number;
- num_themes_threats: number;
- num_threats: number;
- plugins: ScanItem[];
status: string;
- themes: ScanItem[];
};
wafConfig: {
automatic_rules_available: boolean;
diff --git a/projects/packages/protect-models/changelog/update-protect-threats-data b/projects/packages/protect-models/changelog/update-protect-threats-data
new file mode 100644
index 0000000000000..1cb2d3079cb4d
--- /dev/null
+++ b/projects/packages/protect-models/changelog/update-protect-threats-data
@@ -0,0 +1,4 @@
+Significance: major
+Type: changed
+
+Changed the formatting of threat data.
diff --git a/projects/packages/protect-models/src/class-extension-model.php b/projects/packages/protect-models/src/class-extension-model.php
index 95a49c8e5b7c3..60185c973b4ed 100644
--- a/projects/packages/protect-models/src/class-extension-model.php
+++ b/projects/packages/protect-models/src/class-extension-model.php
@@ -33,13 +33,6 @@ class Extension_Model {
*/
public $version;
- /**
- * A collection of threats related to this version of the extension.
- *
- * @var array
- */
- public $threats = array();
-
/**
* Whether the extension has been checked for threats.
*
@@ -77,34 +70,4 @@ public function __construct( $extension = array() ) {
}
}
}
-
- /**
- * Set Threats
- *
- * @param array $threats An array of threat data to add to the extension.
- */
- public function set_threats( $threats ) {
- if ( ! is_array( $threats ) ) {
- $this->threats = array();
- return;
- }
-
- // convert each provided threat item into an instance of Threat_Model
- $threats = array_map(
- function ( $threat ) {
- if ( is_a( $threat, 'Threat_Model' ) ) {
- return $threat;
- }
-
- if ( is_object( $threat ) ) {
- $threat = (array) $threat;
- }
-
- return new Threat_Model( $threat );
- },
- $threats
- );
-
- $this->threats = $threats;
- }
}
diff --git a/projects/packages/protect-models/src/class-history-model.php b/projects/packages/protect-models/src/class-history-model.php
index ff10ae4bf468b..1de243a3a22f2 100644
--- a/projects/packages/protect-models/src/class-history-model.php
+++ b/projects/packages/protect-models/src/class-history-model.php
@@ -18,68 +18,12 @@ class History_Model {
*/
public $last_checked;
- /**
- * The number of threats.
- *
- * @var int
- */
- public $num_threats;
-
- /**
- * The number of core threats.
- *
- * @var int
- */
- public $num_core_threats;
-
- /**
- * The number of plugin threats.
- *
- * @var int
- */
- public $num_plugins_threats;
-
- /**
- * The number of theme threats.
- *
- * @var int
- */
- public $num_themes_threats;
-
- /**
- * WordPress core.
- *
- * @var array
- */
- public $core = array();
-
- /**
- * Status themes.
- *
- * @var array
- */
- public $themes = array();
-
- /**
- * Status plugins.
- *
- * @var array
- */
- public $plugins = array();
-
- /**
- * File threats.
- *
- * @var array
- */
- public $files = array();
-
/**
* Database threats.
*
- * @var array
+ * @var array
*/
- public $database = array();
+ public $threats = array();
/**
* Whether there was an error loading the history.
diff --git a/projects/packages/protect-models/src/class-status-model.php b/projects/packages/protect-models/src/class-status-model.php
index 73bec9dd0f4de..a719364edddfe 100644
--- a/projects/packages/protect-models/src/class-status-model.php
+++ b/projects/packages/protect-models/src/class-status-model.php
@@ -25,27 +25,6 @@ class Status_Model {
*/
public $last_checked;
- /**
- * The number of threats.
- *
- * @var int
- */
- public $num_threats;
-
- /**
- * The number of plugin threats.
- *
- * @var int
- */
- public $num_plugins_threats;
-
- /**
- * The number of theme threats.
- *
- * @var int
- */
- public $num_themes_threats;
-
/**
* The current report status.
*
@@ -61,39 +40,11 @@ class Status_Model {
public $fixable_threat_ids = array();
/**
- * WordPress core status.
- *
- * @var object
- */
- public $core;
-
- /**
- * Status themes.
- *
- * @var array
- */
- public $themes = array();
-
- /**
- * Status plugins.
- *
- * @var array
- */
- public $plugins = array();
-
- /**
- * File threats.
- *
- * @var array
- */
- public $files = array();
-
- /**
- * Database threats.
+ * Threats.
*
- * @var array
+ * @var array
*/
- public $database = array();
+ public $threats = array();
/**
* Whether the site includes items that have not been checked.
@@ -136,9 +87,6 @@ class Status_Model {
* @param array $status The status data to load into the class instance.
*/
public function __construct( $status = array() ) {
- // set status defaults
- $this->core = new \stdClass();
-
foreach ( $status as $property => $value ) {
if ( property_exists( $this, $property ) ) {
$this->$property = $value;
diff --git a/projects/packages/protect-models/src/class-threat-model.php b/projects/packages/protect-models/src/class-threat-model.php
index d85e1b97cc686..5335edd058a67 100644
--- a/projects/packages/protect-models/src/class-threat-model.php
+++ b/projects/packages/protect-models/src/class-threat-model.php
@@ -103,6 +103,13 @@ class Threat_Model {
*/
public $source;
+ /**
+ * The threat's extension information.
+ *
+ * @var null|Extension_Model
+ */
+ public $extension;
+
/**
* Threat Constructor
*
@@ -114,6 +121,10 @@ public function __construct( $threat ) {
}
foreach ( $threat as $property => $value ) {
+ if ( 'extension' === $property ) {
+ $this->extension = new Extension_Model( $value );
+ continue;
+ }
if ( property_exists( $this, $property ) ) {
$this->$property = $value;
}
diff --git a/projects/packages/protect-models/tests/php/test-extension-model.php b/projects/packages/protect-models/tests/php/test-extension-model.php
index 8e7c37c89c937..e491a7d0281ed 100644
--- a/projects/packages/protect-models/tests/php/test-extension-model.php
+++ b/projects/packages/protect-models/tests/php/test-extension-model.php
@@ -10,55 +10,20 @@
* @package automattic/jetpack-protect
*/
class Test_Extension_Model extends BaseTestCase {
-
- /**
- * Get a sample threat
- *
- * @param int|string $id The sample threat's unique identifier.
- * @return array
- */
- private static function get_sample_threat( $id = 0 ) {
- return array(
- 'id' => "test-threat-$id",
- 'signature' => 'Test.Threat',
- 'title' => "Test Threat $id",
- 'description' => 'This is a test threat.',
- );
- }
-
/**
* Tests for extension model's __construct() method.
*/
public function test_extension_model_construct() {
- $test_data = array(
- 'name' => 'Test Extension',
- 'slug' => 'test-extension',
+ $test_data = array(
+ 'slug' => 'test-extension-1',
+ 'name' => 'Test Extension 1',
'version' => '1.0.0',
- 'threats' => array(
- self::get_sample_threat( 0 ),
- self::get_sample_threat( 1 ),
- self::get_sample_threat( 2 ),
- ),
- 'checked' => true,
- 'type' => 'plugins',
- );
-
- // Initialize multiple instances of Extension_Model to test varying initial params
- $test_extensions = array(
- new Extension_Model( $test_data ),
- new Extension_Model( (object) $test_data ),
+ 'type' => 'plugin',
);
+ $test_extension = new Extension_Model( $test_data );
- foreach ( $test_extensions as $extension ) {
- foreach ( $extension->threats as $loop_index => $threat ) {
- // Validate the threat data is converted into Threat_Models
- $this->assertSame( 'Automattic\Jetpack\Protect_Models\Threat_Model', get_class( $threat ) );
-
- // Validate the threat data is set properly
- foreach ( self::get_sample_threat( $loop_index ) as $key => $value ) {
- $this->assertSame( $value, $threat->{ $key } );
- }
- }
+ foreach ( $test_data as $key => $value ) {
+ $this->assertSame( $value, $test_extension->$key );
}
}
}
diff --git a/projects/packages/protect-models/tests/php/test-threat-model.php b/projects/packages/protect-models/tests/php/test-threat-model.php
index 02cd1face2f66..b972ccea5f790 100644
--- a/projects/packages/protect-models/tests/php/test-threat-model.php
+++ b/projects/packages/protect-models/tests/php/test-threat-model.php
@@ -15,34 +15,63 @@ class Test_Threat_Model extends BaseTestCase {
* Tests for threat model's __construct() method.
*/
public function test_threat_model_construct() {
+ // Initialize multiple instances of Extension_Threat to test varying initial params
$test_data = array(
- 'id' => 'abc-123-abc-123',
- 'signature' => 'Test.Threat',
- 'title' => 'Test Threat',
- 'description' => 'This is a test threat.',
- 'first_detected' => '2022-01-01T00:00:00.000Z',
- 'fixed_in' => '1.0.1',
- 'severity' => 4,
- 'fixable' => (object) array(
- 'fixer' => 'update',
- 'target' => '1.0.1',
- 'extension_status' => 'active',
+ array(
+ 'id' => 'test-threat-1',
+ 'signature' => 'Test.Threat',
+ 'title' => 'Test Threat 1',
+ 'description' => 'This is a test threat.',
+ 'extension' => array(
+ 'slug' => 'test-extension-1',
+ 'name' => 'Test Extension 1',
+ 'version' => '1.0.0',
+ 'type' => 'plugin',
+ ),
+ ),
+ array(
+ 'id' => 'test-threat-2',
+ 'signature' => 'Test.Threat',
+ 'title' => 'Test Threat 2',
+ 'description' => 'This is a test threat.',
+ 'extension' => array(
+ 'slug' => 'test-extension-2',
+ 'name' => 'Test Extension 2',
+ 'version' => '1.0.0',
+ 'type' => 'theme',
+ ),
+ ),
+ array(
+ 'id' => 'test-threat-3',
+ 'signature' => 'Test.Threat',
+ 'title' => 'Test Threat 3',
+ 'description' => 'This is a test threat.',
),
- 'status' => 'current',
- 'filename' => '/srv/htdocs/wp-content/uploads/threat.jpg.php',
- 'context' => (object) array(),
);
- // Initialize multiple instances of Threat_Model to test varying initial params
- $test_threats = array(
- new Threat_Model( $test_data ),
- new Threat_Model( (object) $test_data ),
+ $test_threats = array_map(
+ function ( $threat_data ) {
+ return new Threat_Model( $threat_data );
+ },
+ $test_data
);
- foreach ( $test_threats as $threat ) {
+ foreach ( $test_threats as $loop_index => $threat ) {
+ // Validate the threat data is normalized into model classes
+ $this->assertSame( 'Automattic\Jetpack\Protect_Models\Threat_Model', get_class( $threat ) );
+ if ( isset( $threat->extension ) ) {
+ $this->assertSame( 'Automattic\Jetpack\Protect_Models\Extension_Model', get_class( $threat->extension ) );
+ }
+
// Validate the threat data is set properly
- foreach ( $test_data as $key => $value ) {
- $this->assertSame( $value, $threat->{ $key } );
+ foreach ( $test_data[ $loop_index ] as $key => $value ) {
+ if ( 'extension' === $key ) {
+ foreach ( $value as $extension_key => $extension_value ) {
+ $this->assertSame( $extension_value, $threat->extension->$extension_key );
+ }
+ continue;
+ }
+ $this->assertSame( $value, $threat->$key );
}
}
}
diff --git a/projects/packages/protect-status/changelog/update-protect-threats-data b/projects/packages/protect-status/changelog/update-protect-threats-data
new file mode 100644
index 0000000000000..1cb2d3079cb4d
--- /dev/null
+++ b/projects/packages/protect-status/changelog/update-protect-threats-data
@@ -0,0 +1,4 @@
+Significance: major
+Type: changed
+
+Changed the formatting of threat data.
diff --git a/projects/packages/protect-status/src/class-protect-status.php b/projects/packages/protect-status/src/class-protect-status.php
index 832b1cde58964..bd4912c9ae7c3 100644
--- a/projects/packages/protect-status/src/class-protect-status.php
+++ b/projects/packages/protect-status/src/class-protect-status.php
@@ -132,130 +132,113 @@ public static function fetch_from_server() {
* @return Status_Model
*/
protected static function normalize_protect_report_data( $report_data ) {
+ global $wp_version;
+
$status = new Status_Model();
$status->data_source = 'protect_report';
- // map report data properties directly into the Status_Model
- $status->status = isset( $report_data->status ) ? $report_data->status : null;
- $status->last_checked = isset( $report_data->last_checked ) ? $report_data->last_checked : null;
- $status->num_threats = isset( $report_data->num_vulnerabilities ) ? $report_data->num_vulnerabilities : null;
- $status->num_themes_threats = isset( $report_data->num_themes_vulnerabilities ) ? $report_data->num_themes_vulnerabilities : null;
- $status->num_plugins_threats = isset( $report_data->num_plugins_vulnerabilities ) ? $report_data->num_plugins_vulnerabilities : null;
+ $status->status = isset( $report_data->status ) ? $report_data->status : null;
+ $status->last_checked = isset( $report_data->last_checked ) ? $report_data->last_checked : null;
- // merge plugins from report with all installed plugins before mapping into the Status_Model
+ // Plugin Vulnerabilities
$installed_plugins = Plugins_Installer::get_plugins();
$last_report_plugins = isset( $report_data->plugins ) ? $report_data->plugins : new \stdClass();
- $status->plugins = self::merge_installed_and_checked_lists( $installed_plugins, $last_report_plugins, array( 'type' => 'plugins' ) );
-
- // merge themes from report with all installed plugins before mapping into the Status_Model
- $installed_themes = Sync_Functions::get_themes();
- $last_report_themes = isset( $report_data->themes ) ? $report_data->themes : new \stdClass();
- $status->themes = self::merge_installed_and_checked_lists( $installed_themes, $last_report_themes, array( 'type' => 'themes' ) );
-
- // normalize WordPress core report data and map into Status_Model
- $status->core = self::normalize_core_information( isset( $report_data->core ) ? $report_data->core : new \stdClass() );
-
- // check if any installed items (themes, plugins, or core) have not been checked in the report
- $all_items = array_merge( $status->plugins, $status->themes, array( $status->core ) );
- $unchecked_items = array_filter(
- $all_items,
- function ( $item ) {
- return ! isset( $item->checked ) || ! $item->checked;
+ foreach ( $installed_plugins as $installed_slug => $installed_plugin ) {
+ // Skip vulnerabilities for plugins that are not installed
+ if ( ! isset( $last_report_plugins->{ $installed_slug } ) ) {
+ continue;
}
- );
- $status->has_unchecked_items = ! empty( $unchecked_items );
-
- return $status;
- }
- /**
- * Merges the list of installed extensions with the list of extensions that were checked for known vulnerabilities and return a normalized list to be used in the UI
- *
- * @param array $installed The list of installed extensions, where each attribute key is the extension slug.
- * @param object $checked The list of checked extensions.
- * @param array $append Additional data to append to each result in the list.
- * @return array Normalized list of extensions.
- */
- protected static function merge_installed_and_checked_lists( $installed, $checked, $append ) {
- $new_list = array();
- foreach ( array_keys( $installed ) as $slug ) {
+ $report_plugin = $last_report_plugins->{ $installed_slug };
- $checked = (object) $checked;
+ // Skip vulnerabilities for plugins with a mismatched version
+ if ( $report_plugin->version !== $installed_plugin['Version'] ) {
+ continue;
+ }
- $extension = new Extension_Model(
- array_merge(
+ foreach ( $report_plugin->vulnerabilities as $report_vulnerability ) {
+ $status->threats[] = new Threat_Model(
array(
- 'name' => $installed[ $slug ]['Name'],
- 'version' => $installed[ $slug ]['Version'],
- 'slug' => $slug,
- 'threats' => array(),
- 'checked' => false,
- ),
- $append
- )
- );
-
- if ( isset( $checked->{ $slug } ) && $checked->{ $slug }->version === $installed[ $slug ]['Version'] ) {
- $extension->version = $checked->{ $slug }->version;
- $extension->checked = true;
-
- if ( is_array( $checked->{ $slug }->vulnerabilities ) ) {
- foreach ( $checked->{ $slug }->vulnerabilities as $threat ) {
- $extension->threats[] = new Threat_Model(
+ 'id' => $report_vulnerability->id,
+ 'title' => $report_vulnerability->title,
+ 'fixed_in' => $report_vulnerability->fixed_in,
+ 'description' => isset( $report_vulnerability->description ) ? $report_vulnerability->description : null,
+ 'source' => isset( $report_vulnerability->id ) ? Redirect::get_url( 'jetpack-protect-vul-info', array( 'path' => $report_vulnerability->id ) ) : null,
+ 'extension' => new Extension_Model(
array(
- 'id' => $threat->id,
- 'title' => $threat->title,
- 'fixed_in' => $threat->fixed_in,
- 'description' => isset( $threat->description ) ? $threat->description : null,
- 'source' => isset( $threat->id ) ? Redirect::get_url( 'jetpack-protect-vul-info', array( 'path' => $threat->id ) ) : null,
+ 'slug' => $installed_slug,
+ 'name' => $installed_plugin['Name'],
+ 'version' => $installed_plugin['Version'],
+ 'type' => 'plugin',
)
- );
- }
- }
+ ),
+ )
+ );
}
-
- $new_list[] = $extension;
-
}
- $new_list = parent::sort_threats( $new_list );
+ // Theme Vulnerabilities
+ $installed_themes = Sync_Functions::get_themes();
+ $last_report_themes = isset( $report_data->themes ) ? $report_data->themes : new \stdClass();
+ foreach ( $installed_themes as $installed_slug => $installed_theme ) {
+ // Skip vulnerabilities for themes that are not installed
+ if ( ! isset( $last_report_themes->{ $installed_slug } ) ) {
+ continue;
+ }
- return $new_list;
- }
+ $report_theme = $last_report_themes->{ $installed_slug };
- /**
- * Check if the WordPress version that was checked matches the current installed version.
- *
- * @param object $core_check The object returned by Protect wpcom endpoint.
- * @return object The object representing the current status of core checks.
- */
- protected static function normalize_core_information( $core_check ) {
- global $wp_version;
+ // Skip vulnerabilities for themes with a mismatched version
+ if ( $report_theme->version !== $installed_theme['Version'] ) {
+ continue;
+ }
- $core = new Extension_Model(
- array(
- 'type' => 'core',
- 'name' => 'WordPress',
- 'version' => $wp_version,
- 'checked' => false,
- )
- );
+ foreach ( $report_theme->vulnerabilities as $report_vulnerability ) {
+ $status->threats[] = new Threat_Model(
+ array(
+ 'id' => $report_vulnerability->id,
+ 'title' => $report_vulnerability->title,
+ 'fixed_in' => $report_vulnerability->fixed_in,
+ 'description' => isset( $report_vulnerability->description ) ? $report_vulnerability->description : null,
+ 'source' => isset( $report_vulnerability->id ) ? Redirect::get_url( 'jetpack-protect-vul-info', array( 'path' => $report_vulnerability->id ) ) : null,
+ 'extension' => new Extension_Model(
+ array(
+ 'slug' => $installed_slug,
+ 'name' => $installed_theme['Name'],
+ 'version' => $installed_theme['Version'],
+ 'type' => 'theme',
+ )
+ ),
+ )
+ );
+ }
+ }
- if ( isset( $core_check->version ) && $core_check->version === $wp_version ) {
- if ( is_array( $core_check->vulnerabilities ) ) {
- $core->checked = true;
- $core->set_threats(
+ // WordPress Core Vulnerabilities
+ $last_report_core = isset( $report_data->core ) ? $report_data->core : new \stdClass();
+ if ( isset( $last_report_core->version ) && $last_report_core->version === $wp_version ) {
+ if ( is_array( $last_report_core->vulnerabilities ) ) {
+ $core_threats =
array_map(
- function ( $vulnerability ) {
- $vulnerability->source = isset( $vulnerability->id ) ? Redirect::get_url( 'jetpack-protect-vul-info', array( 'path' => $vulnerability->id ) ) : null;
- return $vulnerability;
+ function ( $vulnerability ) use ( $last_report_core ) {
+ $threat = new Threat_Model( $vulnerability );
+ $threat->source = isset( $threat->id ) ? Redirect::get_url( 'jetpack-protect-vul-info', array( 'path' => $threat->id ) ) : null;
+ $threat->extension = new Extension_Model(
+ array(
+ 'slug' => 'wordpress',
+ 'name' => 'WordPress',
+ 'version' => $last_report_core->version,
+ 'type' => 'core',
+ )
+ );
+ return $threat;
},
- $core_check->vulnerabilities
- )
- );
+ $last_report_core->vulnerabilities
+ );
+ $status->threats = array_merge( $status->threats, $core_threats );
}
}
- return $core;
+ return $status;
}
}
diff --git a/projects/packages/protect-status/src/class-scan-status.php b/projects/packages/protect-status/src/class-scan-status.php
index 0ed447f3b8fd3..7518db880349b 100644
--- a/projects/packages/protect-status/src/class-scan-status.php
+++ b/projects/packages/protect-status/src/class-scan-status.php
@@ -9,11 +9,9 @@
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
-use Automattic\Jetpack\Plugins_Installer;
use Automattic\Jetpack\Protect_Models\Extension_Model;
use Automattic\Jetpack\Protect_Models\Status_Model;
use Automattic\Jetpack\Protect_Models\Threat_Model;
-use Automattic\Jetpack\Sync\Functions as Sync_Functions;
use Jetpack_Options;
use WP_Error;
@@ -145,9 +143,6 @@ private static function normalize_api_data( $scan_data ) {
$status = new Status_Model();
$status->data_source = 'scan_api';
$status->status = isset( $scan_data->state ) ? $scan_data->state : null;
- $status->num_threats = 0;
- $status->num_themes_threats = 0;
- $status->num_plugins_threats = 0;
$status->has_unchecked_items = false;
$status->current_progress = isset( $scan_data->current->progress ) ? $scan_data->current->progress : null;
@@ -158,109 +153,52 @@ private static function normalize_api_data( $scan_data ) {
}
}
- $status->core = new Extension_Model(
- array(
- 'type' => 'core',
- 'name' => 'WordPress',
- 'version' => $wp_version,
- 'checked' => true, // to do: default to false once Scan API has manifest
- )
- );
-
if ( isset( $scan_data->threats ) && is_array( $scan_data->threats ) ) {
foreach ( $scan_data->threats as $threat ) {
if ( isset( $threat->fixable ) && $threat->fixable ) {
$status->fixable_threat_ids[] = $threat->id;
}
+ // Plugin and Theme Threats
if ( isset( $threat->extension->type ) ) {
- if ( 'plugin' === $threat->extension->type ) {
- // add the extension if it does not yet exist in the status
- if ( ! isset( $status->plugins[ $threat->extension->slug ] ) ) {
- $status->plugins[ $threat->extension->slug ] = new Extension_Model(
- array(
- 'name' => isset( $threat->extension->name ) ? $threat->extension->name : null,
- 'slug' => isset( $threat->extension->slug ) ? $threat->extension->slug : null,
- 'version' => isset( $threat->extension->version ) ? $threat->extension->version : null,
- 'type' => 'plugin',
- 'checked' => true,
- 'threats' => array(),
- )
- );
- }
-
- $status->plugins[ $threat->extension->slug ]->threats[] = new Threat_Model(
- array(
- 'id' => isset( $threat->id ) ? $threat->id : null,
- 'signature' => isset( $threat->signature ) ? $threat->signature : null,
- 'title' => isset( $threat->title ) ? $threat->title : null,
- 'description' => isset( $threat->description ) ? $threat->description : null,
- 'vulnerability_description' => isset( $threat->vulnerability_description ) ? $threat->vulnerability_description : null,
- 'fix_description' => isset( $threat->fix_description ) ? $threat->fix_description : null,
- 'payload_subtitle' => isset( $threat->payload_subtitle ) ? $threat->payload_subtitle : null,
- 'payload_description' => isset( $threat->payload_description ) ? $threat->payload_description : null,
- 'first_detected' => isset( $threat->first_detected ) ? $threat->first_detected : null,
- 'fixed_in' => isset( $threat->fixer->fixer ) && 'update' === $threat->fixer->fixer ? $threat->fixer->target : null,
- 'severity' => isset( $threat->severity ) ? $threat->severity : null,
- 'fixable' => isset( $threat->fixer ) ? $threat->fixer : null,
- 'status' => isset( $threat->status ) ? $threat->status : null,
- 'filename' => isset( $threat->filename ) ? $threat->filename : null,
- 'context' => isset( $threat->context ) ? $threat->context : null,
- 'source' => isset( $threat->source ) ? $threat->source : null,
- )
- );
- ++$status->num_threats;
- ++$status->num_plugins_threats;
- continue;
- }
-
- if ( 'theme' === $threat->extension->type ) {
- // add the extension if it does not yet exist in the status
- if ( ! isset( $status->themes[ $threat->extension->slug ] ) ) {
- $status->themes[ $threat->extension->slug ] = new Extension_Model(
+ $status->threats[] = new Threat_Model(
+ array(
+ 'id' => isset( $threat->id ) ? $threat->id : null,
+ 'signature' => isset( $threat->signature ) ? $threat->signature : null,
+ 'title' => isset( $threat->title ) ? $threat->title : null,
+ 'description' => isset( $threat->description ) ? $threat->description : null,
+ 'vulnerability_description' => isset( $threat->vulnerability_description ) ? $threat->vulnerability_description : null,
+ 'fix_description' => isset( $threat->fix_description ) ? $threat->fix_description : null,
+ 'payload_subtitle' => isset( $threat->payload_subtitle ) ? $threat->payload_subtitle : null,
+ 'payload_description' => isset( $threat->payload_description ) ? $threat->payload_description : null,
+ 'first_detected' => isset( $threat->first_detected ) ? $threat->first_detected : null,
+ 'fixed_in' => isset( $threat->fixer->fixer ) && 'update' === $threat->fixer->fixer ? $threat->fixer->target : null,
+ 'severity' => isset( $threat->severity ) ? $threat->severity : null,
+ 'fixable' => isset( $threat->fixer ) ? $threat->fixer : null,
+ 'status' => isset( $threat->status ) ? $threat->status : null,
+ 'filename' => isset( $threat->filename ) ? $threat->filename : null,
+ 'context' => isset( $threat->context ) ? $threat->context : null,
+ 'source' => isset( $threat->source ) ? $threat->source : null,
+ 'extension' => new Extension_Model(
array(
'name' => isset( $threat->extension->name ) ? $threat->extension->name : null,
'slug' => isset( $threat->extension->slug ) ? $threat->extension->slug : null,
'version' => isset( $threat->extension->version ) ? $threat->extension->version : null,
- 'type' => 'theme',
- 'checked' => true,
- 'threats' => array(),
+ 'type' => $threat->extension->type,
)
- );
- }
-
- $status->themes[ $threat->extension->slug ]->threats[] = new Threat_Model(
- array(
- 'id' => isset( $threat->id ) ? $threat->id : null,
- 'signature' => isset( $threat->signature ) ? $threat->signature : null,
- 'title' => isset( $threat->title ) ? $threat->title : null,
- 'description' => isset( $threat->description ) ? $threat->description : null,
- 'vulnerability_description' => isset( $threat->vulnerability_description ) ? $threat->vulnerability_description : null,
- 'fix_description' => isset( $threat->fix_description ) ? $threat->fix_description : null,
- 'payload_subtitle' => isset( $threat->payload_subtitle ) ? $threat->payload_subtitle : null,
- 'payload_description' => isset( $threat->payload_description ) ? $threat->payload_description : null,
- 'first_detected' => isset( $threat->first_detected ) ? $threat->first_detected : null,
- 'fixed_in' => isset( $threat->fixer->fixer ) && 'update' === $threat->fixer->fixer ? $threat->fixer->target : null,
- 'severity' => isset( $threat->severity ) ? $threat->severity : null,
- 'fixable' => isset( $threat->fixer ) ? $threat->fixer : null,
- 'status' => isset( $threat->status ) ? $threat->status : null,
- 'filename' => isset( $threat->filename ) ? $threat->filename : null,
- 'context' => isset( $threat->context ) ? $threat->context : null,
- 'source' => isset( $threat->source ) ? $threat->source : null,
- )
- );
- ++$status->num_threats;
- ++$status->num_themes_threats;
- continue;
- }
+ ),
+ )
+ );
+ continue;
}
+ // WordPress Core Threats
if ( isset( $threat->signature ) && 'Vulnerable.WP.Core' === $threat->signature ) {
if ( $threat->version !== $wp_version ) {
continue;
}
- $status->core->threats[] = new Threat_Model(
+ $status->threats[] = new Threat_Model(
array(
'id' => $threat->id,
'signature' => $threat->signature,
@@ -268,99 +206,34 @@ private static function normalize_api_data( $scan_data ) {
'description' => $threat->description,
'first_detected' => $threat->first_detected,
'severity' => $threat->severity,
+ 'extension' => new Extension_Model(
+ array(
+ 'name' => 'WordPress',
+ 'slug' => 'wordpress',
+ 'version' => $wp_version,
+ 'type' => 'core',
+ )
+ ),
)
);
- ++$status->num_threats;
continue;
}
+ // File Threats
if ( ! empty( $threat->filename ) ) {
- $status->files[] = new Threat_Model( $threat );
- ++$status->num_threats;
+ $status->threats[] = new Threat_Model( $threat );
continue;
}
+ // Database Threats
if ( ! empty( $threat->table ) ) {
- $status->database[] = new Threat_Model( $threat );
- ++$status->num_threats;
+ $status->threats[] = new Threat_Model( $threat );
continue;
}
}
}
- $installed_plugins = Plugins_Installer::get_plugins();
- $status->plugins = self::merge_installed_and_checked_lists( $installed_plugins, $status->plugins, array( 'type' => 'plugins' ), true );
-
- $installed_themes = Sync_Functions::get_themes();
- $status->themes = self::merge_installed_and_checked_lists( $installed_themes, $status->themes, array( 'type' => 'themes' ), true );
-
- foreach ( array_merge( $status->themes, $status->plugins ) as $extension ) {
- if ( ! $extension->checked ) {
- $status->has_unchecked_items = true;
- break;
- }
- }
-
return $status;
}
-
- /**
- * Merges the list of installed extensions with the list of extensions that were checked for known vulnerabilities and return a normalized list to be used in the UI
- *
- * @param array $installed The list of installed extensions, where each attribute key is the extension slug.
- * @param object $checked The list of checked extensions.
- * @param array $append Additional data to append to each result in the list.
- * @return array Normalized list of extensions.
- */
- protected static function merge_installed_and_checked_lists( $installed, $checked, $append ) {
- $new_list = array();
- $checked = (object) $checked;
-
- foreach ( array_keys( $installed ) as $slug ) {
- /**
- * Extension Type Map
- *
- * @var array $extension_type_map Key value pairs of extension types and their corresponding
- * identifier used by the Scan API data source.
- */
- $extension_type_map = array(
- 'themes' => 'r1',
- 'plugins' => 'r2',
- );
-
- $version = $installed[ $slug ]['Version'];
- $short_slug = str_replace( '.php', '', explode( '/', $slug )[0] );
- $scanifest_slug = $extension_type_map[ $append['type'] ] . ":$short_slug@$version";
-
- $extension = new Extension_Model(
- array_merge(
- array(
- 'name' => $installed[ $slug ]['Name'],
- 'version' => $version,
- 'slug' => $slug,
- 'threats' => array(),
- 'checked' => false,
- ),
- $append
- )
- );
-
- if ( ! isset( $checked->extensions ) // no extension data available from Scan API
- || is_array( $checked->extensions ) && in_array( $scanifest_slug, $checked->extensions, true ) // extension data matches Scan API
- ) {
- $extension->checked = true;
- if ( isset( $checked->{ $short_slug }->threats ) ) {
- $extension->threats = $checked->{ $short_slug }->threats;
- }
- }
-
- $new_list[] = $extension;
-
- }
-
- $new_list = parent::sort_threats( $new_list );
-
- return $new_list;
- }
}
diff --git a/projects/packages/protect-status/src/class-status.php b/projects/packages/protect-status/src/class-status.php
index df547b88e528b..b1a7c520fe17d 100644
--- a/projects/packages/protect-status/src/class-status.php
+++ b/projects/packages/protect-status/src/class-status.php
@@ -7,7 +7,6 @@
namespace Automattic\Jetpack\Protect_Status;
-use Automattic\Jetpack\Protect_Models\Extension_Model;
use Automattic\Jetpack\Protect_Models\Status_Model;
/**
@@ -163,7 +162,7 @@ public static function has_threats() {
*/
public static function get_total_threats() {
$status = static::get_status();
- return isset( $status->num_threats ) && is_int( $status->num_threats ) ? $status->num_threats : 0;
+ return isset( $status->threats ) && is_array( $status->threats ) ? count( $status->threats ) : 0;
}
/**
@@ -172,140 +171,7 @@ public static function get_total_threats() {
* @return array
*/
public static function get_all_threats() {
- return array_merge(
- self::get_wordpress_threats(),
- self::get_themes_threats(),
- self::get_plugins_threats(),
- self::get_files_threats(),
- self::get_database_threats()
- );
- }
-
- /**
- * Get threats found for WordPress core
- *
- * @return array
- */
- public static function get_wordpress_threats() {
- return self::get_threats( 'core' );
- }
-
- /**
- * Get threats found for themes
- *
- * @return array
- */
- public static function get_themes_threats() {
- return self::get_threats( 'themes' );
- }
-
- /**
- * Get threats found for plugins
- *
- * @return array
- */
- public static function get_plugins_threats() {
- return self::get_threats( 'plugins' );
- }
-
- /**
- * Get threats found for files
- *
- * @return array
- */
- public static function get_files_threats() {
- return self::get_threats( 'files' );
- }
-
- /**
- * Get threats found for plugins
- *
- * @return array
- */
- public static function get_database_threats() {
- return self::get_threats( 'database' );
- }
-
- /**
- * Get the threats for one type of extension or core
- *
- * @param string $type What threats you want to get. Possible values are 'core', 'themes' and 'plugins'.
- *
- * @return array
- */
- public static function get_threats( $type ) {
$status = static::get_status();
-
- if ( 'core' === $type ) {
- return isset( $status->$type ) && ! empty( $status->$type->threats ) ? $status->$type->threats : array();
- }
-
- if ( 'files' === $type || 'database' === $type ) {
- return isset( $status->$type ) && ! empty( $status->$type ) ? $status->$type : array();
- }
-
- $threats = array();
- if ( isset( $status->$type ) ) {
- foreach ( (array) $status->$type as $item ) {
- if ( ! empty( $item->threats ) ) {
- $threats = array_merge( $threats, $item->threats );
- }
- }
- }
- return $threats;
- }
-
- /**
- * Check if the WordPress version that was checked matches the current installed version.
- *
- * @param object $core_check The object returned by Protect wpcom endpoint.
- * @return object The object representing the current status of core checks.
- */
- protected static function normalize_core_information( $core_check ) {
- global $wp_version;
-
- $core = new Extension_Model(
- array(
- 'type' => 'core',
- 'name' => 'WordPress',
- 'version' => $wp_version,
- 'checked' => false,
- )
- );
-
- if ( isset( $core_check->version ) && $core_check->version === $wp_version ) {
- if ( is_array( $core_check->vulnerabilities ) ) {
- $core->checked = true;
- $core->set_threats( $core_check->vulnerabilities );
- }
- }
-
- return $core;
- }
-
- /**
- * Sort By Threats
- *
- * @param array