diff --git a/projects/plugins/protect/src/class-rest-controller.php b/projects/plugins/protect/src/class-rest-controller.php
index 6da77263e808c..a1ae1b67ba654 100644
--- a/projects/plugins/protect/src/class-rest-controller.php
+++ b/projects/plugins/protect/src/class-rest-controller.php
@@ -55,6 +55,18 @@ public static function register_rest_endpoints() {
)
);
+ register_rest_route(
+ 'jetpack-protect/v1',
+ 'unignore-threat',
+ array(
+ 'methods' => \WP_REST_Server::EDITABLE,
+ 'callback' => __CLASS__ . '::api_unignore_threat',
+ 'permission_callback' => function () {
+ return current_user_can( 'manage_options' );
+ },
+ )
+ );
+
register_rest_route(
'jetpack-protect/v1',
'fix-threats',
@@ -233,6 +245,27 @@ public static function api_ignore_threat( $request ) {
return new WP_REST_Response( 'Threat ignored.' );
}
+ /**
+ * Unignores a threat for the API endpoint
+ *
+ * @param WP_REST_Request $request The request object.
+ *
+ * @return WP_REST_Response
+ */
+ public static function api_unignore_threat( $request ) {
+ if ( ! $request['threat_id'] ) {
+ return new WP_REST_Response( 'Missing threat ID.', 400 );
+ }
+
+ $threat_ignored = Threats::unignore_threat( $request['threat_id'] );
+
+ if ( ! $threat_ignored ) {
+ return new WP_REST_Response( 'An error occured while attempting to unignore the threat.', 500 );
+ }
+
+ return new WP_REST_Response( 'Threat unignored.' );
+ }
+
/**
* Fixes threats for the API endpoint
*
diff --git a/projects/plugins/protect/src/class-threats.php b/projects/plugins/protect/src/class-threats.php
index 63a20f9ef3c89..f2a80999f65f9 100644
--- a/projects/plugins/protect/src/class-threats.php
+++ b/projects/plugins/protect/src/class-threats.php
@@ -81,6 +81,17 @@ public static function ignore_threat( $threat_id ) {
return self::update_threat( $threat_id, array( 'ignore' => true ) );
}
+ /**
+ * Unignore Threat
+ *
+ * @param string $threat_id The threat ID.
+ *
+ * @return bool
+ */
+ public static function unignore_threat( $threat_id ) {
+ return self::update_threat( $threat_id, array( 'unignore' => true ) );
+ }
+
/**
* Fix Threats
*
diff --git a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx
index e20974d9cd8c2..a2e222867d2e9 100644
--- a/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx
+++ b/projects/plugins/protect/src/js/components/ignore-threat-modal/index.jsx
@@ -52,7 +52,7 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => {
{ createInterpolateElement(
__(
- 'By ignoring this threat you confirm that you have reviewed the detected code and assume the risks of keeping a potentially malicious or vulnerable file on your site. If you are unsure please request an estimate with Codeable.',
+ 'By choosing to ignore this threat, you acknowledge that you have reviewed the detected code. You are accepting the risks of maintaining a potentially malicious or vulnerable file on your site. If you are unsure, please request an estimate with Codeable.',
'jetpack-protect'
),
{
diff --git a/projects/plugins/protect/src/js/components/modal/index.jsx b/projects/plugins/protect/src/js/components/modal/index.jsx
index c21f39babe9d8..1f629fc955989 100644
--- a/projects/plugins/protect/src/js/components/modal/index.jsx
+++ b/projects/plugins/protect/src/js/components/modal/index.jsx
@@ -7,10 +7,12 @@ import FixAllThreatsModal from '../fix-all-threats-modal';
import FixThreatModal from '../fix-threat-modal';
import IgnoreThreatModal from '../ignore-threat-modal';
import StandaloneModeModal from '../standalone-mode-modal';
+import UnignoreThreatModal from '../unignore-threat-modal';
import styles from './styles.module.scss';
const MODAL_COMPONENTS = {
IGNORE_THREAT: IgnoreThreatModal,
+ UNIGNORE_THREAT: UnignoreThreatModal,
FIX_THREAT: FixThreatModal,
FIX_ALL_THREATS: FixAllThreatsModal,
CREDENTIALS_NEEDED: CredentialsNeededModal,
diff --git a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx b/projects/plugins/protect/src/js/components/paid-accordion/index.jsx
index b922738dd6c83..adfc8ddad213c 100644
--- a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx
+++ b/projects/plugins/protect/src/js/components/paid-accordion/index.jsx
@@ -33,6 +33,14 @@ const ScanHistoryDetails = ( { detectedAt, fixedOn, status } ) => {
>
) }
+ { 'ignored' === status && (
+ <>
+
+
+ { __( 'Threat ignored', 'jetpack-protect' ) }
+
+ >
+ ) }
) }
{ ( 'fixed' === status || 'ignored' === status ) && (
diff --git a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx b/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx
index c25695ee67412..adfcc75153bd9 100644
--- a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx
+++ b/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx
@@ -50,6 +50,16 @@ const ThreatAccordionItem = ( {
};
};
+ const handleUnignoreThreatClick = () => {
+ return event => {
+ event.preventDefault();
+ setModal( {
+ type: 'UNIGNORE_THREAT',
+ props: { id, label, title, icon, severity },
+ } );
+ };
+ };
+
const handleFixThreatClick = () => {
return event => {
event.preventDefault();
@@ -126,15 +136,30 @@ const ThreatAccordionItem = ( {
) }
{ ! description &&
{ learnMoreButton }
}
- { status !== 'ignored' && status !== 'fixed' && (
-
diff --git a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx
new file mode 100644
index 0000000000000..ab7612e23351d
--- /dev/null
+++ b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx
@@ -0,0 +1,66 @@
+import { Button, Text } from '@automattic/jetpack-components';
+import { useDispatch, useSelect } from '@wordpress/data';
+import { __ } from '@wordpress/i18n';
+import { Icon } from '@wordpress/icons';
+import { STORE_ID } from '../../state/store';
+import ThreatSeverityBadge from '../severity';
+import UserConnectionGate from '../user-connection-gate';
+import styles from './styles.module.scss';
+
+const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => {
+ const { setModal, unignoreThreat } = useDispatch( STORE_ID );
+ const threatsUpdating = useSelect( select => select( STORE_ID ).getThreatsUpdating() );
+
+ const handleCancelClick = () => {
+ return event => {
+ event.preventDefault();
+ setModal( { type: null } );
+ };
+ };
+
+ const handleUnignoreClick = () => {
+ return async event => {
+ event.preventDefault();
+ unignoreThreat( id, () => {
+ setModal( { type: null } );
+ } );
+ };
+ };
+
+ return (
+
+
+ { __( 'Do you really want to unignore this threat?', 'jetpack-protect' ) }
+
+ { __( 'Jetpack will unignore the threat:', 'jetpack-protect' ) }
+
+
+
+
+
+ { label }
+
+ { title }
+
+
+
+
+
+
+
+
+ { __( 'Cancel', 'jetpack-protect' ) }
+
+
+ { __( 'Unignore threat', 'jetpack-protect' ) }
+
+
+
+ );
+};
+
+export default UnignoreThreatModal;
diff --git a/projects/plugins/protect/src/js/components/unignore-threat-modal/styles.module.scss b/projects/plugins/protect/src/js/components/unignore-threat-modal/styles.module.scss
new file mode 100644
index 0000000000000..d936392c844e5
--- /dev/null
+++ b/projects/plugins/protect/src/js/components/unignore-threat-modal/styles.module.scss
@@ -0,0 +1,40 @@
+.threat {
+ border: 1px solid var( --jp-gray );
+ border-radius: var( --jp-border-radius );
+ padding: calc( var( --spacing-base ) * 2 ); // 16px
+ display: flex;
+ margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px
+
+ &__icon {
+ margin-right: calc( var( --spacing-base ) * 2 ); // 16px
+ min-width: 24px;
+ }
+
+ &__summary {
+ width: 100%;
+ }
+
+ &__summary__label {
+ font-size: 18px;
+ line-height: 24px;
+ font-weight: 600;
+ margin-bottom: 0;
+ }
+
+ &__summary__title {
+ font-size: 14px;
+ line-height: 21px;
+ color: var( --jp-gray-80 );
+ }
+
+ &__severity {
+ align-self: center;
+ margin-left: calc( var( --spacing-base ) * 2 ); // 16px
+ margin-right: var( --spacing-base ); // 8px
+ }
+}
+
+.footer {
+ display: flex;
+ justify-content: space-between;
+}
diff --git a/projects/plugins/protect/src/js/state/actions.js b/projects/plugins/protect/src/js/state/actions.js
index eb01caa8173f3..67d0893d6046f 100644
--- a/projects/plugins/protect/src/js/state/actions.js
+++ b/projects/plugins/protect/src/js/state/actions.js
@@ -242,6 +242,41 @@ const ignoreThreat =
} );
};
+const unignoreThreat =
+ ( threatId, callback = () => {} ) =>
+ async ( { dispatch } ) => {
+ dispatch( setThreatIsUpdating( threatId, true ) );
+ return await new Promise( () => {
+ return apiFetch( {
+ path: `jetpack-protect/v1/unignore-threat?threat_id=${ threatId }`,
+ method: 'POST',
+ } )
+ .then( () => {
+ return dispatch( refreshScanHistory() );
+ } )
+ .then( () => {
+ return dispatch( refreshStatus() );
+ } )
+ .then( () => {
+ return dispatch(
+ setNotice( { type: 'success', message: __( 'Threat unignored', 'jetpack-protect' ) } )
+ );
+ } )
+ .catch( () => {
+ return dispatch(
+ setNotice( {
+ type: 'error',
+ message: __( 'An error ocurred unignoring the threat.', 'jetpack-protect' ),
+ } )
+ );
+ } )
+ .finally( () => {
+ dispatch( setThreatIsUpdating( threatId, false ) );
+ callback();
+ } );
+ } );
+ };
+
const getFixThreatsStatus =
threatIds =>
async ( { dispatch } ) => {
@@ -455,6 +490,7 @@ const actions = {
setwpVersion,
setJetpackScan,
ignoreThreat,
+ unignoreThreat,
setModal,
setNotice,
clearNotice,