From 4358286fdfa51562e2e87fac675c8d15af39172a Mon Sep 17 00:00:00 2001 From: dkmyta Date: Sun, 27 Oct 2024 13:28:54 -0600 Subject: [PATCH 1/2] Protect: update threats data format and integrate threatsdataviews Remove database type handling --- pnpm-lock.yaml | 324 ++---------------- projects/js-packages/components/package.json | 2 +- .../scan/changelog/add-types-and-utils | 4 + projects/js-packages/scan/jest.config.cjs | 11 - projects/js-packages/scan/package.json | 8 +- projects/js-packages/scan/src/types/.gitkeep | 0 projects/js-packages/scan/src/types/fixers.ts | 23 ++ .../scan/src/types/status.ts} | 34 +- projects/js-packages/scan/src/utils/index.ts | 40 ++- projects/js-packages/scan/webpack.config.cjs | 53 +++ .../protect-card/scan-threats-status.tsx | 20 +- .../protect-card/use-last-scan-text.ts | 10 +- .../protect-card/use-protect-tooltip-copy.ts | 10 +- .../changelog/protect-status-compat | 5 + projects/packages/my-jetpack/global.d.ts | 24 +- .../changelog/update-protect-threats-data | 4 + .../src/class-extension-model.php | 37 -- .../src/class-history-model.php | 60 +--- .../protect-models/src/class-status-model.php | 58 +--- .../protect-models/src/class-threat-model.php | 11 + .../tests/php/test-extension-model.php | 49 +-- .../tests/php/test-threat-model.php | 71 ++-- .../changelog/update-protect-threats-data | 4 + .../src/class-protect-status.php | 183 +++++----- .../protect-status/src/class-scan-status.php | 205 +++-------- .../protect-status/src/class-status.php | 138 +------- .../tests/php/test-scan-status.php | 138 ++------ .../protect-status/tests/php/test-status.php | 235 +++++-------- .../changelog/move-components-to-package | 5 + projects/plugins/protect/package.json | 1 + .../protect/src/class-scan-history.php | 77 +---- .../js/components/fix-threat-modal/index.jsx | 9 +- .../js/components/free-accordion/index.jsx | 64 ---- .../free-accordion/stories/index.stories.jsx | 120 ------- .../free-accordion/styles.module.scss | 79 ----- .../components/ignore-threat-modal/index.jsx | 12 +- .../js/components/paid-accordion/index.jsx | 187 ---------- .../stories/broken/index.stories.jsx | 120 ------- .../paid-accordion/styles.module.scss | 191 ----------- .../src/js/components/pricing-table/index.jsx | 4 +- .../js/components/threat-fix-header/index.jsx | 3 +- .../src/js/components/threats-list/empty.jsx | 140 -------- .../js/components/threats-list/free-list.jsx | 125 ------- .../src/js/components/threats-list/index.jsx | 194 ----------- .../js/components/threats-list/navigation.jsx | 130 ------- .../js/components/threats-list/pagination.jsx | 142 -------- .../js/components/threats-list/paid-list.jsx | 253 -------------- .../threats-list/styles.module.scss | 129 ------- .../threats-list/use-threats-list.js | 158 --------- .../unignore-threat-modal/index.jsx | 18 +- .../src/js/data/scan/use-scan-status-query.ts | 2 +- .../src/js/data/use-connection-mutation.ts | 2 +- .../src/js/hooks/use-protect-data/index.ts | 174 ---------- projects/plugins/protect/src/js/index.tsx | 7 +- .../history/history-admin-section-hero.tsx | 86 ----- .../src/js/routes/scan/history/index.jsx | 301 ---------------- .../js/routes/scan/history/status-filters.jsx | 44 --- .../js/routes/scan/history/styles.module.scss | 45 --- .../protect/src/js/routes/scan/index.jsx | 51 ++- .../routes/scan/scan-admin-section-hero.tsx | 13 +- .../js/routes/scan/scan-results-data-view.tsx | 56 +++ .../src/js/routes/scan/styles.module.scss | 9 +- .../plugins/protect/src/js/types/global.d.ts | 2 +- .../plugins/protect/src/js/types/threats.ts | 8 + projects/plugins/protect/tsconfig.json | 2 +- projects/plugins/protect/webpack.config.js | 7 +- 66 files changed, 635 insertions(+), 4096 deletions(-) create mode 100644 projects/js-packages/scan/changelog/add-types-and-utils delete mode 100644 projects/js-packages/scan/jest.config.cjs delete mode 100644 projects/js-packages/scan/src/types/.gitkeep rename projects/{plugins/protect/src/js/types/scans.ts => js-packages/scan/src/types/status.ts} (65%) create mode 100644 projects/js-packages/scan/webpack.config.cjs create mode 100644 projects/packages/my-jetpack/changelog/protect-status-compat create mode 100644 projects/packages/protect-models/changelog/update-protect-threats-data create mode 100644 projects/packages/protect-status/changelog/update-protect-threats-data create mode 100644 projects/plugins/protect/changelog/move-components-to-package delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/free-accordion/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx delete mode 100644 projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/threats-list/empty.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/free-list.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/index.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/navigation.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/pagination.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/paid-list.jsx delete mode 100644 projects/plugins/protect/src/js/components/threats-list/styles.module.scss delete mode 100644 projects/plugins/protect/src/js/components/threats-list/use-threats-list.js delete mode 100644 projects/plugins/protect/src/js/hooks/use-protect-data/index.ts delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/index.jsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx delete mode 100644 projects/plugins/protect/src/js/routes/scan/history/styles.module.scss create mode 100644 projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e162445a37d70..b9f1bc952d48a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -318,8 +318,8 @@ importers: specifier: 10.9.0 version: 10.9.0(react@18.3.1) '@wordpress/dataviews': - specifier: 4.6.0 - version: 4.6.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 4.5.0 + version: 4.5.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/date': specifier: 5.9.0 version: 5.9.0 @@ -1172,6 +1172,9 @@ importers: '@automattic/jetpack-base-styles': specifier: workspace:* version: link:../base-styles + '@automattic/jetpack-webpack-config': + specifier: workspace:* + version: link:../webpack-config '@wordpress/api-fetch': specifier: 7.9.0 version: 7.9.0 @@ -1181,6 +1184,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 @@ -1193,10 +1199,13 @@ importers: react-dom: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) + webpack: + specifier: 5.94.0 + version: 5.94.0(webpack-cli@4.9.1) + webpack-cli: + specifier: 4.9.1 + version: 4.9.1(webpack@5.94.0) devDependencies: - '@automattic/jetpack-webpack-config': - specifier: workspace:* - version: link:../webpack-config '@babel/core': specifier: 7.26.0 version: 7.26.0 @@ -4222,6 +4231,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) @@ -7701,10 +7713,6 @@ packages: webpack-dev-server: optional: true - '@wordpress/a11y@4.10.0': - resolution: {integrity: sha512-fp7C0Ofy95a+jzK126CxAXV+uXSFQs2Clm6PCeE9Y1BowUiL9l7juUCh1R+2NDAKATxH4r3QplxVftDqn+ctUw==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/a11y@4.9.0': resolution: {integrity: sha512-OfM/wnB8ItmGM5I/u+4E4aJdqvy98kg24zrS+CqPLgq3eYG6MNkIQJZov/I3XcsyxGjLkkLsybEM5xEYUN0ZtA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -7780,13 +7788,6 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - '@wordpress/components@28.10.0': - resolution: {integrity: sha512-w5mteCe9qOBMgD8d80QBVOPk0YAquUkMD9o3jDvdqUwiTcVgxn4QSKjh65NGYotvMhDsgsMTq+qgifAB+ubepg==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - '@wordpress/components@28.9.0': resolution: {integrity: sha512-/ept6OSWAh4bdZwlhU8TwJe9QM6rqjAXVA08H0wymtjdRbAQiuDsmMfLFKCF1M4hGZeeThAD5YF0ZkBK5iCeCA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -7794,12 +7795,6 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - '@wordpress/compose@7.10.0': - resolution: {integrity: sha512-/j4+wXthaV/KMt0VANvhhRJEJfPc21c7Tq1ZeLxgsbkq4xmi9qXeDT91cvP/U+Ta3phf15K8vdxMr8MqHHiFoQ==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - peerDependencies: - react: ^18.0.0 - '@wordpress/compose@7.9.0': resolution: {integrity: sha512-6f1mZLwMD2woFSMLJ5JaCZQZz1kFD2X4gwT5c4IVnzpm+/9A0OqeTdncAi6I6wHRtKN9DzvaMQPuZitQz7HmNA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -7820,12 +7815,6 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - '@wordpress/data@10.10.0': - resolution: {integrity: sha512-oyYl89p86+U9W6vKDqScKhUGKKzsnETj9rg8zOnT4K9ceOScjGCgdCE+XxcY9exeRg33aSYDjmvnsXXYStBYmA==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - peerDependencies: - react: ^18.0.0 - '@wordpress/data@10.9.0': resolution: {integrity: sha512-hw8VYSPZuEqlEwRnQnKgqzbwCqoGY4U5kLCZA/1McOYspvkIceTVve4qBC17QUJhu2pLEXEc6p4zBpy+SXfToQ==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -7838,16 +7827,6 @@ packages: peerDependencies: react: ^18.0.0 - '@wordpress/dataviews@4.6.0': - resolution: {integrity: sha512-DdlKg1ojGjkgS0z0GHfALOYcsMU7+8Gwzi+GFqRcfGdVOn9SiKY5pMEULFki3RkF3Nh61FEpViRxNczHJhOKCg==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - peerDependencies: - react: ^18.0.0 - - '@wordpress/date@5.10.0': - resolution: {integrity: sha512-TT9HN0H72Eqhlaiy+XMDyZBlTBf2iZ936Q2tJdxsB4qBlG2ntLT3PviIPa+G44QYYxLomrUqTEYQ6FBxiJaNHg==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/date@5.9.0': resolution: {integrity: sha512-Iywz1bga3cPSrf7k4dh2mYVsACqzu0GXYhfu57ElAM9robGjcUxJdzgbWUZw90v473NOp2UpVYsWCuDEqNDcdw==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -7858,26 +7837,14 @@ packages: peerDependencies: webpack: ^5.0.0 - '@wordpress/deprecated@4.10.0': - resolution: {integrity: sha512-lktJKX3AxrskTuLbJuKY/Mzg9De6MYcOzEEL+RUHxfIx8wMtiDnVTAf7epur9XuHVOmdgCCRT6D44I23MoS0sw==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/deprecated@4.9.0': resolution: {integrity: sha512-1PMCLULxTlI0iatsHxpPgtogMfvd/wvAqAOLGHUdkdbBtUEquGrRMo/h+TLU/ne2JDf5JKMA4ntQV6zDNO4+eg==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/dom-ready@4.10.0': - resolution: {integrity: sha512-qpadyGMRvLf7zOe4XtoIo409ZRJ7IrBI36fdEXjRWV8E+Cmcx3ldr5/2iLKJ2cqYg9geQWXDeiykSWOClNJx+w==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/dom-ready@4.9.0': resolution: {integrity: sha512-Kas5YaRl+HebAxFfv9ctB8bdmjbhISIBo747nXCK6KqojQ/Zn2Bctv2XTypR3GMb7OS7KqVMeyCJhjEpuc8Wlw==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/dom@4.10.0': - resolution: {integrity: sha512-1ZRCrDB2TV44GLwaUH9HRGQGQqXcawSEmzVPABQwfwzkUKijfbRdsWqpHrTLqlSZRImHEdp6oSON+1JmCNhXSw==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/dom@4.9.0': resolution: {integrity: sha512-g9jRTxOpSfygEbKNGwYwx21b5GktI2SkwQSAPKpG4mmFAvLbqIzjVc2nkudRO914DKgPWrBsfKsc4Smbtpbkig==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -7949,38 +7916,19 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - '@wordpress/hooks@4.10.0': - resolution: {integrity: sha512-LcorV5Z9XoJCKyj5Ulgw1HPHyM2mxsSInC7wl5cuIgDFmuwPTfRndUDGWz/v86GX1GnUIB0h/ggd53vx1HiW4A==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/hooks@4.9.0': resolution: {integrity: sha512-nan2w5imPhTaJwWdKjm/0ZMDbWR3P6Vhl4OqnBZZcJqOyNSfwsnJ98I+BWjq0U6SmiCnZQERjN0SjVdmD1tCjw==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/html-entities@4.10.0': - resolution: {integrity: sha512-Bnop0k3yjtRhm4CedbsGG22OMLEeob4mYmTR9z0g0QP7OofEw1TINspizr+kQbOu4n1ubJ6YVC8T13Z2va1j0g==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/html-entities@4.9.0': resolution: {integrity: sha512-RaiMecK8Igqb4yreJncSZEBl6DR1eAj8M3mHwrJASJLiiBLcaWg0oi8iiabUEmgGCsIu2pCeXmV+8WO0FRDO5Q==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/i18n@5.10.0': - resolution: {integrity: sha512-HZ6UcMHsjOocDI0zVAuP4JIl97LRmpGo/lVxzVIreaLoYitmYVDUzji02u1o7sEdRWc1Hpkm2/oO/9275rJg1w==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - hasBin: true - '@wordpress/i18n@5.9.0': resolution: {integrity: sha512-pKFV9S/l0TFlm0mlWLW51hAoRDNmZPGnfEpNXq43VKZkm1cco3Z1E54PHMGk8HdCECHqYNiJuQJOBOlqcYmnVQ==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} hasBin: true - '@wordpress/icons@10.10.0': - resolution: {integrity: sha512-41HaxMtq0WZF37mpZ1RQ1s1J3ia5gHFUd/uGhP9p1dhzEFYALxKVTB0Gy3cJhT0CslKeEwYx2XQIP1ZaCKNakQ==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - peerDependencies: - react: ^18 - '@wordpress/icons@10.9.0': resolution: {integrity: sha512-mAkqhlbbPiuR6yBOczunqyxQ2Pez1XB7gAZnnsP5DlTKsYnJQ12N0Ql4Oh8f1LI+UeF18VMtHes12sWK/1LQHQ==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8002,10 +7950,6 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - '@wordpress/is-shallow-equal@5.10.0': - resolution: {integrity: sha512-KOkZzOnmjpH7hzPiaXUjhUlfKIGTzL7qUdNHBC1SFDOYpnRUSw8f1AtWxRpPBHl5dieYVx0x1qjOWjm/DtTOXg==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/is-shallow-equal@5.9.0': resolution: {integrity: sha512-cKqgI6RQ27ZZRo4Zc/jioG3qInDKQqcT3xg5YxsduX2f1b6vQV42p0L4waLFeJZQ8WDUsgsR53AQivdInkO8gA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8022,10 +7966,6 @@ packages: peerDependencies: react: ^18.0.0 - '@wordpress/keycodes@4.10.0': - resolution: {integrity: sha512-2i+N90HBMqQegtGqeVB8pJz8ZgKAY1eZmQegE9MXczYVac85DDOoxhY/41c44s6Kwl3waJ2Zght6UXE0OUFMxw==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/keycodes@4.9.0': resolution: {integrity: sha512-WO4MPlO+uGaDP5jYB9f4hn0NgBwvlaUvj4MLOIDcQGE0ljElLGFeXvqjVH0KVtnZkIKiZNPK7eoQxTWnxWaTjw==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8085,10 +8025,6 @@ packages: peerDependencies: react: ^18.0.0 - '@wordpress/priority-queue@3.10.0': - resolution: {integrity: sha512-Gjbw5NmRLrZ9KkiROJlL4I/s96bMlpd7gGkQbcCyyeLIZduGxQDzI4Jih5s0Xrm7Gj8WFd57wRDe/voZJR0ZsQ==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/priority-queue@3.9.0': resolution: {integrity: sha512-QVfELUL4ei5Uf0DNG9wMVNBILasGWWWogVjVeP1xXqmfSDoeFpPzXpfL9zfANndE2S49DJP9ZoZsCaJHtMrYzA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8101,12 +8037,6 @@ packages: resolution: {integrity: sha512-hZbKVSlo5yOpssMshXwNlUyk83Ev55ZKMfJMVU5nWxiIM9bMCuhpwU+AXQ0GKxOzn2oMayVmtJ00FRbJFg+AMg==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/redux-routine@5.10.0': - resolution: {integrity: sha512-oDqZDjz8H/bt02IEoIZCwsUsL17UOEnMg/heV0PoJxo3k5MTrvqJqzgBLoSC0PFzx/pwOo4TwvwCL+kjjm5gCQ==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - peerDependencies: - redux: '>=4' - '@wordpress/redux-routine@5.9.0': resolution: {integrity: sha512-eEb/otHMhOwVUydb5kErV3X+1R8qQ2hrLmlWIh+kiiKwFJVCl3ge/xN8Tiy1kEBEqgGRgPqxuLvNPZDd0ySpNg==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8120,12 +8050,6 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 - '@wordpress/rich-text@7.10.0': - resolution: {integrity: sha512-2sl/KPRq2ygAiRcs/La733OguL9xIT4uKRA5XpCIWNAqTX7f2kzY5YRn05iJfCxDC+GcDKcHl0JX4ZbFxZn5SA==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - peerDependencies: - react: ^18.0.0 - '@wordpress/rich-text@7.9.0': resolution: {integrity: sha512-GN2SGz8bVkdCVVskvJSgul4wKyq/qaXRmEJSrk3LMHuAbxHL5FFkwRHaOhnHScNz+P1bdEehCqgP8DB3yv+IEw==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -8161,10 +8085,6 @@ packages: resolution: {integrity: sha512-mnwh8WcA9//DVwwZVfEaFHfIK1RaNQ8QvhD1fOtjJWjheS12QsQwjdlGTgvBVl66ErP65GdK7BM1pTv6NI0GwA==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/undo-manager@1.10.0': - resolution: {integrity: sha512-WaLwZ+AlfXQm9PhLf6kwCBaD5DoKaIqelRsgAaqa4APjgMBlxktQ1dadime0CO9+e8R2kLwAE3rxQXhGjicRMw==} - engines: {node: '>=18.12.0', npm: '>=8.19.2'} - '@wordpress/undo-manager@1.9.0': resolution: {integrity: sha512-JLrcmeCTqITbChkJy+PeXcE03+6ZgIfQ22cBg1+0mzLQxglx1gndTnhRcnCSebvsXnmOVmxvE4HmJ84lv7liCQ==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -17878,12 +17798,6 @@ snapshots: dependencies: webpack-cli: 4.9.1(webpack@5.94.0) - '@wordpress/a11y@4.10.0': - dependencies: - '@babel/runtime': 7.25.7 - '@wordpress/dom-ready': 4.10.0 - '@wordpress/i18n': 5.10.0 - '@wordpress/a11y@4.9.0': dependencies: '@babel/runtime': 7.26.0 @@ -18319,60 +18233,6 @@ snapshots: - '@types/react-dom' - supports-color - '@wordpress/components@28.10.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) - '@babel/runtime': 7.25.7 - '@emotion/cache': 11.13.1 - '@emotion/css': 11.13.4 - '@emotion/react': 11.13.3(@types/react@18.3.3)(react@18.3.1) - '@emotion/serialize': 1.3.2 - '@emotion/styled': 11.13.0(@emotion/react@11.13.3(@types/react@18.3.3)(react@18.3.1))(@types/react@18.3.3)(react@18.3.1) - '@emotion/utils': 1.4.1 - '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@types/gradient-parser': 0.1.3 - '@types/highlight-words-core': 1.2.1 - '@use-gesture/react': 10.3.1(react@18.3.1) - '@wordpress/a11y': 4.10.0 - '@wordpress/compose': 7.10.0(react@18.3.1) - '@wordpress/date': 5.10.0 - '@wordpress/deprecated': 4.10.0 - '@wordpress/dom': 4.10.0 - '@wordpress/element': 6.10.0 - '@wordpress/escape-html': 3.10.0 - '@wordpress/hooks': 4.10.0 - '@wordpress/html-entities': 4.10.0 - '@wordpress/i18n': 5.10.0 - '@wordpress/icons': 10.10.0(react@18.3.1) - '@wordpress/is-shallow-equal': 5.10.0 - '@wordpress/keycodes': 4.10.0 - '@wordpress/primitives': 4.10.0(react@18.3.1) - '@wordpress/private-apis': 1.10.0 - '@wordpress/rich-text': 7.10.0(react@18.3.1) - '@wordpress/warning': 3.10.0 - change-case: 4.1.2 - clsx: 2.1.1 - colord: 2.9.3 - date-fns: 3.6.0 - deepmerge: 4.3.1 - fast-deep-equal: 3.1.3 - framer-motion: 11.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - gradient-parser: 0.1.5 - highlight-words-core: 1.2.3 - is-plain-object: 5.0.0 - memize: 2.1.0 - path-to-regexp: 6.3.0 - re-resizable: 6.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-colorful: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-dom: 18.3.1(react@18.3.1) - remove-accents: 0.5.0 - uuid: 9.0.1 - transitivePeerDependencies: - - '@emotion/is-prop-valid' - - '@types/react' - - supports-color - '@wordpress/components@28.9.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) @@ -18481,23 +18341,6 @@ snapshots: - '@types/react' - supports-color - '@wordpress/compose@7.10.0(react@18.3.1)': - dependencies: - '@babel/runtime': 7.25.7 - '@types/mousetrap': 1.6.15 - '@wordpress/deprecated': 4.10.0 - '@wordpress/dom': 4.10.0 - '@wordpress/element': 6.10.0 - '@wordpress/is-shallow-equal': 5.10.0 - '@wordpress/keycodes': 4.10.0 - '@wordpress/priority-queue': 3.10.0 - '@wordpress/undo-manager': 1.10.0 - change-case: 4.1.2 - clipboard: 2.0.11 - mousetrap: 1.6.5 - react: 18.3.1 - use-memo-one: 1.1.3(react@18.3.1) - '@wordpress/compose@7.9.0(react@18.2.0)': dependencies: '@babel/runtime': 7.26.0 @@ -18686,25 +18529,6 @@ snapshots: - supports-color - utf-8-validate - '@wordpress/data@10.10.0(react@18.3.1)': - dependencies: - '@babel/runtime': 7.25.7 - '@wordpress/compose': 7.10.0(react@18.3.1) - '@wordpress/deprecated': 4.10.0 - '@wordpress/element': 6.10.0 - '@wordpress/is-shallow-equal': 5.10.0 - '@wordpress/priority-queue': 3.10.0 - '@wordpress/private-apis': 1.10.0 - '@wordpress/redux-routine': 5.10.0(redux@4.2.1) - deepmerge: 4.3.1 - equivalent-key-map: 0.2.2 - is-plain-object: 5.0.0 - is-promise: 4.0.0 - react: 18.3.1 - redux: 4.2.1 - rememo: 4.0.2 - use-memo-one: 1.1.3(react@18.3.1) - '@wordpress/data@10.9.0(react@18.2.0)': dependencies: '@babel/runtime': 7.26.0 @@ -18754,8 +18578,8 @@ snapshots: '@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 + '@wordpress/private-apis': 1.10.0 + '@wordpress/warning': 3.10.0 clsx: 2.1.1 react: 18.3.1 remove-accents: 0.5.0 @@ -18776,28 +18600,6 @@ snapshots: '@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.6.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) - '@babel/runtime': 7.25.7 - '@wordpress/components': 28.10.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@wordpress/compose': 7.10.0(react@18.3.1) - '@wordpress/data': 10.10.0(react@18.3.1) - '@wordpress/element': 6.10.0 - '@wordpress/i18n': 5.10.0 - '@wordpress/icons': 10.10.0(react@18.3.1) - '@wordpress/primitives': 4.10.0(react@18.3.1) '@wordpress/private-apis': 1.10.0 '@wordpress/warning': 3.10.0 clsx: 2.1.1 @@ -18809,13 +18611,6 @@ snapshots: - react-dom - supports-color - '@wordpress/date@5.10.0': - dependencies: - '@babel/runtime': 7.25.7 - '@wordpress/deprecated': 4.10.0 - moment: 2.29.4 - moment-timezone: 0.5.46 - '@wordpress/date@5.9.0': dependencies: '@babel/runtime': 7.26.0 @@ -18828,29 +18623,15 @@ snapshots: json2php: 0.0.7 webpack: 5.94.0(webpack-cli@4.9.1) - '@wordpress/deprecated@4.10.0': - dependencies: - '@babel/runtime': 7.25.7 - '@wordpress/hooks': 4.10.0 - '@wordpress/deprecated@4.9.0': dependencies: '@babel/runtime': 7.26.0 '@wordpress/hooks': 4.9.0 - '@wordpress/dom-ready@4.10.0': - dependencies: - '@babel/runtime': 7.25.7 - '@wordpress/dom-ready@4.9.0': dependencies: '@babel/runtime': 7.26.0 - '@wordpress/dom@4.10.0': - dependencies: - '@babel/runtime': 7.25.7 - '@wordpress/deprecated': 4.10.0 - '@wordpress/dom@4.9.0': dependencies: '@babel/runtime': 7.26.0 @@ -19303,31 +19084,14 @@ snapshots: - '@types/react-dom' - supports-color - '@wordpress/hooks@4.10.0': - dependencies: - '@babel/runtime': 7.25.7 - '@wordpress/hooks@4.9.0': dependencies: '@babel/runtime': 7.26.0 - '@wordpress/html-entities@4.10.0': - dependencies: - '@babel/runtime': 7.25.7 - '@wordpress/html-entities@4.9.0': dependencies: '@babel/runtime': 7.26.0 - '@wordpress/i18n@5.10.0': - dependencies: - '@babel/runtime': 7.25.7 - '@wordpress/hooks': 4.10.0 - gettext-parser: 1.4.0 - memize: 2.1.0 - sprintf-js: 1.1.3 - tannin: 1.2.0 - '@wordpress/i18n@5.9.0': dependencies: '@babel/runtime': 7.26.0 @@ -19337,18 +19101,11 @@ snapshots: sprintf-js: 1.1.2 tannin: 1.2.0 - '@wordpress/icons@10.10.0(react@18.3.1)': - dependencies: - '@babel/runtime': 7.25.7 - '@wordpress/element': 6.10.0 - '@wordpress/primitives': 4.10.0(react@18.3.1) - react: 18.3.1 - '@wordpress/icons@10.9.0(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 '@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': @@ -19407,10 +19164,6 @@ snapshots: - '@types/react' - supports-color - '@wordpress/is-shallow-equal@5.10.0': - dependencies: - '@babel/runtime': 7.25.7 - '@wordpress/is-shallow-equal@5.9.0': dependencies: '@babel/runtime': 7.26.0 @@ -19429,11 +19182,6 @@ snapshots: '@wordpress/keycodes': 4.9.0 react: 18.3.1 - '@wordpress/keycodes@4.10.0': - dependencies: - '@babel/runtime': 7.25.7 - '@wordpress/i18n': 5.10.0 - '@wordpress/keycodes@4.9.0': dependencies: '@babel/runtime': 7.26.0 @@ -19633,11 +19381,6 @@ snapshots: clsx: 2.1.1 react: 18.3.1 - '@wordpress/priority-queue@3.10.0': - dependencies: - '@babel/runtime': 7.25.7 - requestidlecallback: 0.3.0 - '@wordpress/priority-queue@3.9.0': dependencies: '@babel/runtime': 7.26.0 @@ -19651,14 +19394,6 @@ snapshots: dependencies: '@babel/runtime': 7.26.0 - '@wordpress/redux-routine@5.10.0(redux@4.2.1)': - dependencies: - '@babel/runtime': 7.25.7 - is-plain-object: 5.0.0 - is-promise: 4.0.0 - redux: 4.2.1 - rungen: 0.3.2 - '@wordpress/redux-routine@5.9.0(redux@4.2.1)': dependencies: '@babel/runtime': 7.26.0 @@ -19739,20 +19474,6 @@ snapshots: - supports-color - utf-8-validate - '@wordpress/rich-text@7.10.0(react@18.3.1)': - dependencies: - '@babel/runtime': 7.25.7 - '@wordpress/a11y': 4.10.0 - '@wordpress/compose': 7.10.0(react@18.3.1) - '@wordpress/data': 10.10.0(react@18.3.1) - '@wordpress/deprecated': 4.10.0 - '@wordpress/element': 6.10.0 - '@wordpress/escape-html': 3.10.0 - '@wordpress/i18n': 5.10.0 - '@wordpress/keycodes': 4.10.0 - memize: 2.1.0 - react: 18.3.1 - '@wordpress/rich-text@7.9.0(react@18.3.1)': dependencies: '@babel/runtime': 7.26.0 @@ -19847,11 +19568,6 @@ snapshots: dependencies: '@babel/runtime': 7.26.0 - '@wordpress/undo-manager@1.10.0': - dependencies: - '@babel/runtime': 7.25.7 - '@wordpress/is-shallow-equal': 5.10.0 - '@wordpress/undo-manager@1.9.0': dependencies: '@babel/runtime': 7.26.0 diff --git a/projects/js-packages/components/package.json b/projects/js-packages/components/package.json index 603592a2dbb21..00e5f8ca0c20d 100644 --- a/projects/js-packages/components/package.json +++ b/projects/js-packages/components/package.json @@ -22,7 +22,7 @@ "@wordpress/components": "28.9.0", "@wordpress/compose": "7.9.0", "@wordpress/data": "10.9.0", - "@wordpress/dataviews": "4.6.0", + "@wordpress/dataviews": "4.5.0", "@wordpress/date": "5.9.0", "@wordpress/element": "6.9.0", "@wordpress/i18n": "5.9.0", diff --git a/projects/js-packages/scan/changelog/add-types-and-utils b/projects/js-packages/scan/changelog/add-types-and-utils new file mode 100644 index 0000000000000..959ed2c934abe --- /dev/null +++ b/projects/js-packages/scan/changelog/add-types-and-utils @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add threat types and scan utility functions diff --git a/projects/js-packages/scan/jest.config.cjs b/projects/js-packages/scan/jest.config.cjs deleted file mode 100644 index 057829a25a0f1..0000000000000 --- a/projects/js-packages/scan/jest.config.cjs +++ /dev/null @@ -1,11 +0,0 @@ -const baseConfig = require( 'jetpack-js-tools/jest/config.base.js' ); - -module.exports = { - ...baseConfig, - transform: { - ...baseConfig.transform, - '\\.[jt]sx?$': require( 'jetpack-js-tools/jest/babel-jest-config-factory.js' )( - require.resolve - ), - }, -}; diff --git a/projects/js-packages/scan/package.json b/projects/js-packages/scan/package.json index 3dbaf42c96077..388548539c4df 100644 --- a/projects/js-packages/scan/package.json +++ b/projects/js-packages/scan/package.json @@ -15,7 +15,7 @@ "license": "GPL-2.0-or-later", "author": "Automattic", "scripts": { - "build": "pnpm run clean && pnpm run compile-ts", + "build": "pnpm run clean && webpack", "clean": "rm -rf build/", "compile-ts": "tsc --pretty", "test": "jest", @@ -53,13 +53,17 @@ "dependencies": { "@automattic/jetpack-api": "workspace:*", "@automattic/jetpack-base-styles": "workspace:*", + "@automattic/jetpack-webpack-config": "workspace:*", "@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", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "webpack": "5.94.0", + "webpack-cli": "4.9.1" }, "peerDependencies": { "@wordpress/i18n": "5.9.0", diff --git a/projects/js-packages/scan/src/types/.gitkeep b/projects/js-packages/scan/src/types/.gitkeep deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/projects/js-packages/scan/src/types/fixers.ts b/projects/js-packages/scan/src/types/fixers.ts index 6991e02fba167..6a77dd90f3190 100644 --- a/projects/js-packages/scan/src/types/fixers.ts +++ b/projects/js-packages/scan/src/types/fixers.ts @@ -15,3 +15,26 @@ export type ThreatFixStatusSuccess = { }; 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/plugins/protect/src/js/types/scans.ts b/projects/js-packages/scan/src/types/status.ts similarity index 65% rename from projects/plugins/protect/src/js/types/scans.ts rename to projects/js-packages/scan/src/types/status.ts index 98cf04de3fb3a..7da336170cebb 100644 --- a/projects/plugins/protect/src/js/types/scans.ts +++ b/projects/js-packages/scan/src/types/status.ts @@ -1,4 +1,4 @@ -import { Threat } from './threats'; +import { type Threat } from '..'; export type ExtensionStatus = { /** The name of the extension. */ @@ -39,14 +39,8 @@ export type ScanStatus = { /** The time the last scan was checked, in YYYY-MM-DD HH:MM:SS format. */ lastChecked: string | null; - /** The number of plugin threats found in the latest status. */ - numPluginsThreats: number; - - /** The number of theme threats found in the latest status. */ - numThemesThreats: number; - - /** The total number of threats found in the latest status. */ - numThreats: number; + /** The security threats identified in the latest scan. */ + threats: Threat[]; /** Whether there was an error in the scan results. */ error: boolean | null; @@ -56,26 +50,4 @@ export type ScanStatus = { /** The error message. */ errorMessage: string | null; - - /** WordPress Core Status */ - core: { - checked: boolean; - name: string; - slug: string; - threats: Threat[]; - type: 'core'; - version: string; - } | null; - - /** Plugins Status */ - plugins: ExtensionStatus[]; - - /** Themes Status */ - themes: ExtensionStatus[]; - - /** File Threats */ - files: Threat[]; - - /** Database Threats */ - database: Threat[]; }; diff --git a/projects/js-packages/scan/src/utils/index.ts b/projects/js-packages/scan/src/utils/index.ts index 1c727460eff03..376d96883d358 100644 --- a/projects/js-packages/scan/src/utils/index.ts +++ b/projects/js-packages/scan/src/utils/index.ts @@ -1,4 +1,5 @@ -import { Threat, ThreatFixStatus, FIXER_IS_STALE_THRESHOLD } from '..'; +import { code, color, plugins, shield, wordpress } from '@wordpress/icons'; +import { type Threat, type ThreatFixStatus, FIXER_IS_STALE_THRESHOLD } from '..'; export const getThreatType = ( threat: Threat ) => { if ( threat.signature === 'Vulnerable.WP.Core' ) { @@ -14,6 +15,43 @@ export const getThreatType = ( threat: Threat ) => { 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; + default: + return ''; + } +}; + +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; + default: + return shield; + } +}; + export const fixerTimestampIsStale = ( lastUpdatedTimestamp: string ) => { const now = new Date(); const lastUpdated = new Date( lastUpdatedTimestamp ); diff --git a/projects/js-packages/scan/webpack.config.cjs b/projects/js-packages/scan/webpack.config.cjs new file mode 100644 index 0000000000000..2fa9b2cfcf027 --- /dev/null +++ b/projects/js-packages/scan/webpack.config.cjs @@ -0,0 +1,53 @@ +const path = require( 'path' ); +const jetpackWebpackConfig = require( '@automattic/jetpack-webpack-config/webpack' ); + +module.exports = [ + { + entry: { + index: './src/index.ts', + }, + mode: jetpackWebpackConfig.mode, + devtool: jetpackWebpackConfig.devtool, + output: { + ...jetpackWebpackConfig.output, + path: path.resolve( './build' ), + }, + optimization: { + ...jetpackWebpackConfig.optimization, + }, + resolve: { + ...jetpackWebpackConfig.resolve, + }, + node: false, + plugins: [ ...jetpackWebpackConfig.StandardPlugins() ], + module: { + strictExportPresence: true, + rules: [ + // Transpile JavaScript + jetpackWebpackConfig.TranspileRule( { + exclude: /node_modules\//, + } ), + + // Transpile @automattic/jetpack-* in node_modules too. + jetpackWebpackConfig.TranspileRule( { + includeNodeModules: [ '@automattic/jetpack-' ], + } ), + + // Handle CSS. + jetpackWebpackConfig.CssRule( { + extensions: [ 'css', 'sass', 'scss' ], + extraLoaders: [ 'sass-loader' ], + } ), + + // Handle images. + jetpackWebpackConfig.FileRule(), + ], + }, + externals: { + ...jetpackWebpackConfig.externals, + jetpackConfig: JSON.stringify( { + consumer_slug: 'my_jetpack', + } ), + }, + }, +]; 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 49d4563fabc82..6bd135a920508 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; @@ -212,22 +209,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 bbb43ddb88521..642e8d0b8cd24 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 $threats Array of threats to sort. - * - * @return array The sorted $threats array. - */ - protected static function sort_threats( $threats ) { - usort( - $threats, - function ( $a, $b ) { - // sort primarily based on the presence of threats - $ret = empty( $a->threats ) <=> empty( $b->threats ); - - // sort secondarily on whether the item has been checked - if ( ! $ret ) { - $ret = $a->checked <=> $b->checked; - } - - return $ret; - } - ); - - return $threats; + return isset( $status->threats ) && is_array( $status->threats ) ? $status->threats : array(); } } diff --git a/projects/packages/protect-status/tests/php/test-scan-status.php b/projects/packages/protect-status/tests/php/test-scan-status.php index 0777de7c7bd94..062b6430e41cf 100644 --- a/projects/packages/protect-status/tests/php/test-scan-status.php +++ b/projects/packages/protect-status/tests/php/test-scan-status.php @@ -124,8 +124,6 @@ public function get_sample_response() { * @return object */ public function get_sample_status() { - global $wp_version; - return new Status_Model( array( 'data_source' => 'scan_api', @@ -135,53 +133,7 @@ public function get_sample_status() { 'num_themes_threats' => 0, 'status' => 'idle', 'fixable_threat_ids' => array( '69353714' ), - 'plugins' => array( - new Extension_Model( - array( - 'version' => '3.0.0', - 'name' => 'Woocommerce', - 'checked' => true, - 'type' => 'plugins', - 'threats' => array( - new Threat_Model( - array( - 'id' => '71625245', - 'signature' => 'Vulnerable.WP.Extension', - 'description' => 'The plugin WooCommerce (version 3.0.0) has a known vulnerability. ', - 'first_detected' => '2022-07-27T17:22:16.000Z', - 'severity' => 3, - 'fixable' => null, - 'status' => 'current', - 'source' => 'https://wpvulndb.com/vulnerabilities/10220', - ) - ), - ), - 'slug' => 'woocommerce', - ) - ), - ), - 'themes' => array( - new Extension_Model( - array( - 'name' => 'Sample Theme', - 'slug' => 'theme-1', - 'version' => '1.0.2', - 'type' => 'themes', - 'threats' => array(), - 'checked' => true, - ) - ), - ), - 'core' => new Extension_Model( - array( - 'version' => $wp_version, - 'threats' => array(), - 'checked' => true, - 'name' => 'WordPress', - 'type' => 'core', - ) - ), - 'files' => array( + 'threats' => array( new Threat_Model( array( 'id' => 71626681, @@ -199,6 +151,26 @@ public function get_sample_status() { ), ) ), + new Threat_Model( + array( + 'id' => '71625245', + 'signature' => 'Vulnerable.WP.Extension', + 'description' => 'The plugin WooCommerce (version 3.0.0) has a known vulnerability. ', + 'first_detected' => '2022-07-27T17:22:16.000Z', + 'severity' => 3, + 'fixable' => null, + 'status' => 'current', + 'source' => 'https://wpvulndb.com/vulnerabilities/10220', + 'extension' => new Extension_Model( + array( + 'slug' => 'woocommerce', + 'version' => '3.0.0', + 'name' => 'WooCommerce', + 'type' => 'plugin', + ) + ), + ) + ), new Threat_Model( array( 'id' => 69353714, @@ -217,8 +189,6 @@ public function get_sample_status() { ) ), ), - 'database' => array(), - 'has_unchecked_items' => false, ) ); } @@ -364,72 +334,6 @@ public function test_get_total_threats() { $this->assertSame( 3, $status ); } - /** - * Test get all threats - */ - public function test_get_all_threats() { - $this->mock_connection(); - - $expected = array( - new Threat_Model( - array( - 'id' => '71625245', - 'signature' => 'Vulnerable.WP.Extension', - 'description' => 'The plugin WooCommerce (version 3.0.0) has a known vulnerability. ', - 'first_detected' => '2022-07-27T17:22:16.000Z', - 'severity' => 3, - 'fixable' => null, - 'status' => 'current', - 'source' => 'https://wpvulndb.com/vulnerabilities/10220', - ) - ), - new Threat_Model( - array( - 'id' => 71626681, - 'signature' => 'EICAR_AV_Test_Critical', - 'description' => 'This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don\'t expect it to, contact Jetpack support for some help.', - 'first_detected' => '2022-07-27T17 => 49 => 35.000Z', - 'severity' => 5, - 'fixer' => null, - 'status' => 'current', - 'filename' => '/var/www/html/wp-content/uploads/jptt_eicar.php', - 'context' => (object) array( - '15' => 'echo <<', - '17' => 'HTML;', - 'marks' => new \stdClass(), - ), - ) - ), - new Threat_Model( - array( - 'id' => 69353714, - 'signature' => 'Core.File.Modification', - 'description' => 'Core WordPress files are not normally changed. If you did not make these changes you should review the code.', - 'first_detected' => '2022-06-23T18:42:29.000Z', - 'severity' => 4, - 'status' => 'current', - 'fixable' => (object) array( - 'fixer' => 'replace', - 'file' => '/var/www/html/wp-admin/index.php', - 'extensionStatus' => '', - ), - 'filename' => '/var/www/html/wp-admin/index.php', - 'diff' => "--- /tmp/wordpress/6.0-en_US/wordpress/wp-admin/index.php\t2021-11-03 03:16:57.000000000 +0000\n+++ /tmp/6299071296/core-file-23271BW6i4wLCe3T7\t2022-06-23 18:42:29.087377846 +0000\n@@ -209,3 +209,4 @@\n wp_print_community_events_templates();\n \n require_once ABSPATH . 'wp-admin/admin-footer.php';\n+if ( true === false ) exit();\n\\ No newline at end of file\n", - ) - ), - ); - - add_filter( 'pre_http_request', array( $this, 'return_sample_response' ) ); - add_filter( 'all_plugins', array( $this, 'return_sample_plugins' ) ); - add_filter( 'jetpack_sync_get_themes_callable', array( $this, 'return_sample_themes' ) ); - $all_threats = Scan_Status::get_all_threats(); - remove_filter( 'pre_http_request', array( $this, 'return_sample_response' ) ); - remove_filter( 'all_plugins', array( $this, 'return_sample_plugins' ) ); - remove_filter( 'jetpack_sync_get_themes_callable', array( $this, 'return_sample_themes' ) ); - - $this->assertEquals( $expected, $all_threats ); - } - /** * Data provider for test_is_cache_expired */ diff --git a/projects/packages/protect-status/tests/php/test-status.php b/projects/packages/protect-status/tests/php/test-status.php index b437e9bc48002..e38cc5f4c3350 100644 --- a/projects/packages/protect-status/tests/php/test-status.php +++ b/projects/packages/protect-status/tests/php/test-status.php @@ -13,7 +13,6 @@ use Automattic\Jetpack\Protect_Models\Extension_Model; use Automattic\Jetpack\Protect_Models\Status_Model; use Automattic\Jetpack\Protect_Models\Threat_Model; -use Automattic\Jetpack\Redirect; use Jetpack_Options; use WorDBless\BaseTestCase; @@ -29,104 +28,6 @@ protected function set_up() { Protect_Status::$status = null; } - /** - * Get a sample checked theme result - * - * @param string $id The unique theme ID. - * @param bool $with_threats Whether the sample should include a vulnerability. - * @return object - */ - public function get_sample_theme( $id, $with_threats = true ) { - $item = (object) array( - 'version' => '1.0.2', - 'name' => 'Sample Theme', - 'checked' => true, - 'type' => 'themes', - 'threats' => array(), - 'slug' => "theme-$id", - ); - if ( $with_threats ) { - $item->threats[] = $this->get_sample_threat(); - } - return $item; - } - - /** - * Get a sample checked plugin result - * - * @param string $id The unique plugin ID. - * @param bool $with_threats Whether the sample should include a vulnerability. - * @return object - */ - public function get_sample_plugin( $id, $with_threats = true ) { - $item = (object) array( - 'version' => '1.0.2', - 'name' => 'Sample Plugin', - 'checked' => true, - 'type' => 'plugins', - 'threats' => array(), - 'slug' => "plugin-$id", - ); - if ( $with_threats ) { - $item->threats[] = $this->get_sample_threat(); - } - return $item; - } - - /** - * Get a sample checked core result - * - * @param bool $with_threats Whether the sample should include a vulnerability. - * @return object - */ - public function get_sample_core( $with_threats = true ) { - global $wp_version; - - $item = (object) array( - 'version' => $wp_version, - 'threats' => array(), - 'checked' => true, - 'name' => 'WordPress', - 'type' => 'core', - ); - if ( $with_threats ) { - $item->threats[] = $this->get_sample_threat(); - } - - return $item; - } - - /** - * Get a sample vulnerabilty - * - * @return object - */ - public function get_sample_vul() { - return (object) array( - 'id' => 'asdasdasd-123123-asdasd', - 'title' => 'Sample Vul', - 'fixed_in' => '2.0.0', - ); - } - - /** - * Get a sample threat - * - * @return object - */ - public function get_sample_threat() { - $id = 'asdasdasd-123123-asdasd'; - - return new Threat_Model( - array( - 'id' => $id, - 'title' => 'Sample Vul', - 'fixed_in' => '2.0.0', - 'source' => Redirect::get_url( 'jetpack-protect-vul-info', array( 'path' => $id ) ), - ) - ); - } - /** * Get a sample empty response * @@ -163,29 +64,37 @@ public function get_sample_response() { 'num_themes_vulnerabilities' => 1, 'num_plugins_vulnerabilities' => 1, 'themes' => (object) array( - 'theme-1' => (object) array( - 'slug' => 'theme-1', - 'name' => 'Sample Theme', + 'example-theme-1' => (object) array( + 'slug' => 'example-theme', + 'name' => 'Example Theme', 'version' => '1.0.2', 'checked' => true, 'vulnerabilities' => array( - $this->get_sample_vul(), + (object) array( + 'id' => 'example-theme-threat', + 'title' => 'Example Theme Threat', + 'fixed_in' => '2.0.0', + ), ), ), ), 'plugins' => (object) array( - 'plugin-1' => (object) array( - 'slug' => 'plugin-1', - 'name' => 'Sample Plugin', + 'example-plugin-1' => (object) array( + 'slug' => 'example-plugin', + 'name' => 'Example Plugin', 'version' => '1.0.2', 'checked' => true, 'vulnerabilities' => array( - $this->get_sample_vul(), + (object) array( + 'id' => 'example-plugin-threat', + 'title' => 'Example Plugin Threat', + 'fixed_in' => '2.0.0', + ), ), ), - 'plugin-2' => (object) array( - 'slug' => 'plugin-2', - 'name' => 'Sample Plugin', + 'example-plugin-2' => (object) array( + 'slug' => 'example-plugin-2', + 'name' => 'Example Plugin 2', 'version' => '1.0.2', 'checked' => true, 'vulnerabilities' => array(), @@ -195,7 +104,10 @@ public function get_sample_response() { 'version' => $wp_version, 'checked' => true, 'vulnerabilities' => array( - $this->get_sample_vul(), + (object) array( + 'id' => 'example-core-threat', + 'title' => 'Example Core Threat', + ), ), 'name' => 'WordPress', ), @@ -208,23 +120,61 @@ public function get_sample_response() { * @return object */ public function get_sample_status() { + global $wp_version; + return new Status_Model( array( - 'data_source' => 'protect_report', - 'plugins' => array( - new Extension_Model( $this->get_sample_plugin( '1' ) ), - new Extension_Model( $this->get_sample_plugin( '2', false ) ), - ), - 'themes' => array( - new Extension_Model( $this->get_sample_theme( '1' ) ), + 'data_source' => 'protect_report', + 'threats' => array( + new Threat_Model( + array( + 'id' => 'example-plugin-threat', + 'title' => 'Example Plugin Threat', + 'fixed_in' => '2.0.0', + 'source' => 'https://jetpack.com/redirect/?source=jetpack-protect-vul-info&site=example.org&path=example-plugin-threat', + 'extension' => new Extension_Model( + array( + 'name' => 'Example Plugin', + 'slug' => 'example-plugin-1', + 'version' => '1.0.2', + 'type' => 'plugin', + ) + ), + ) + ), + new Threat_Model( + array( + 'id' => 'example-theme-threat', + 'title' => 'Example Theme Threat', + 'fixed_in' => '2.0.0', + 'source' => 'https://jetpack.com/redirect/?source=jetpack-protect-vul-info&site=example.org&path=example-theme-threat', + 'extension' => new Extension_Model( + array( + 'name' => 'Example Theme', + 'slug' => 'example-theme-1', + 'version' => '1.0.2', + 'type' => 'theme', + ) + ), + ) + ), + new Threat_Model( + array( + 'id' => 'example-core-threat', + 'title' => 'Example Core Threat', + 'source' => 'https://jetpack.com/redirect/?source=jetpack-protect-vul-info&site=example.org&path=example-core-threat', + 'extension' => new Extension_Model( + array( + 'name' => 'WordPress', + 'slug' => 'wordpress', + 'version' => $wp_version, + 'type' => 'core', + ) + ), + ) + ), ), - 'core' => new Extension_Model( $this->get_sample_core() ), - 'wordpress' => $this->get_sample_core(), - 'last_checked' => '2003-03-03 03:03:03', - 'num_threats' => 3, - 'num_themes_threats' => 1, - 'num_plugins_threats' => 1, - 'has_unchecked_items' => false, + 'last_checked' => '2003-03-03 03:03:03', ) ); } @@ -251,12 +201,12 @@ public function return_sample_response() { */ public function return_sample_plugins() { return array( - 'plugin-1' => array( - 'Name' => 'Sample Plugin', + 'example-plugin-1' => array( + 'Name' => 'Example Plugin', 'Version' => '1.0.2', ), - 'plugin-2' => array( - 'Name' => 'Sample Plugin', + 'example-plugin-2' => array( + 'Name' => 'Example Plugin', 'Version' => '1.0.2', ), ); @@ -269,8 +219,8 @@ public function return_sample_plugins() { */ public function return_sample_themes() { return array( - 'theme-1' => array( - 'Name' => 'Sample Theme', + 'example-theme-1' => array( + 'Name' => 'Example Theme', 'Version' => '1.0.2', ), ); @@ -360,34 +310,15 @@ public function test_get_status() { public function test_get_total_threats() { $this->mock_connection(); - add_filter( 'pre_http_request', array( $this, 'return_sample_response' ) ); - $status = Protect_Status::get_total_threats(); - remove_filter( 'pre_http_request', array( $this, 'return_sample_response' ) ); - - $this->assertSame( 3, $status ); - } - - /** - * Test get all threats - */ - public function test_get_all_threats() { - $this->mock_connection(); - - $expected = array( - $this->get_sample_threat(), - $this->get_sample_threat(), - $this->get_sample_threat(), - ); - add_filter( 'pre_http_request', array( $this, 'return_sample_response' ) ); add_filter( 'all_plugins', array( $this, 'return_sample_plugins' ) ); add_filter( 'jetpack_sync_get_themes_callable', array( $this, 'return_sample_themes' ) ); - $status = Protect_Status::get_all_threats(); + $status = Protect_Status::get_total_threats(); remove_filter( 'pre_http_request', array( $this, 'return_sample_response' ) ); remove_filter( 'all_plugins', array( $this, 'return_sample_plugins' ) ); remove_filter( 'jetpack_sync_get_themes_callable', array( $this, 'return_sample_themes' ) ); - $this->assertEquals( $expected, $status ); + $this->assertSame( 3, $status ); } /** diff --git a/projects/plugins/protect/changelog/move-components-to-package b/projects/plugins/protect/changelog/move-components-to-package new file mode 100644 index 0000000000000..29ead1e0b072a --- /dev/null +++ b/projects/plugins/protect/changelog/move-components-to-package @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Moved components to package + + diff --git a/projects/plugins/protect/package.json b/projects/plugins/protect/package.json index 32df838c09d26..c66e57af33a38 100644 --- a/projects/plugins/protect/package.json +++ b/projects/plugins/protect/package.json @@ -29,6 +29,7 @@ "@automattic/jetpack-base-styles": "workspace:*", "@automattic/jetpack-components": "workspace:*", "@automattic/jetpack-connection": "workspace:*", + "@automattic/jetpack-scan": "workspace:*", "@tanstack/react-query": "5.20.5", "@tanstack/react-query-devtools": "5.20.5", "@wordpress/api-fetch": "7.9.0", diff --git a/projects/plugins/protect/src/class-scan-history.php b/projects/plugins/protect/src/class-scan-history.php index de88b135778cc..7046903af6f26 100644 --- a/projects/plugins/protect/src/class-scan-history.php +++ b/projects/plugins/protect/src/class-scan-history.php @@ -211,91 +211,20 @@ public static function fetch_from_api() { * @return History_Model */ private static function normalize_api_data( $scan_data ) { - $history = new History_Model(); - $history->num_threats = 0; - $history->num_core_threats = 0; - $history->num_plugins_threats = 0; - $history->num_themes_threats = 0; - + $history = new History_Model(); $history->last_checked = $scan_data->last_checked; if ( empty( $scan_data->threats ) || ! is_array( $scan_data->threats ) ) { return $history; } - foreach ( $scan_data->threats as $threat ) { - if ( isset( $threat->extension->type ) ) { - if ( 'plugin' === $threat->extension->type ) { - self::handle_extension_threats( $threat, $history, 'plugin' ); - continue; - } - - if ( 'theme' === $threat->extension->type ) { - self::handle_extension_threats( $threat, $history, 'theme' ); - continue; - } - } - - if ( 'Vulnerable.WP.Core' === $threat->signature ) { - self::handle_core_threats( $threat, $history ); - continue; - } - - self::handle_additional_threats( $threat, $history ); + foreach ( $scan_data->threats as $source_threat ) { + $history->threats[] = new Threat_Model( $source_threat ); } return $history; } - /** - * Handles threats for extensions such as plugins or themes. - * - * @param object $threat The threat object. - * @param object $history The history object. - * @param string $type The type of extension ('plugin' or 'theme'). - * @return void - */ - private static function handle_extension_threats( $threat, $history, $type ) { - $extension_list = $type === 'plugin' ? 'plugins' : 'themes'; - $extensions = &$history->{ $extension_list}; - $found_index = null; - - // Check if the extension does not exist in the array - foreach ( $extensions as $index => $extension ) { - if ( $extension->slug === $threat->extension->slug ) { - $found_index = $index; - break; - } - } - - // Add the extension if it does not yet exist in the history - if ( $found_index === null ) { - $new_extension = new Extension_Model( - array( - 'name' => $threat->extension->name ?? null, - 'slug' => $threat->extension->slug ?? null, - 'version' => $threat->extension->version ?? null, - 'type' => $type, - 'checked' => true, - 'threats' => array(), - ) - ); - $extensions[] = $new_extension; - $found_index = array_key_last( $extensions ); - } - - // Add the threat to the found extension - $extensions[ $found_index ]->threats[] = new Threat_Model( $threat ); - - // Increment the threat counts - ++$history->num_threats; - if ( $type === 'plugin' ) { - ++$history->num_plugins_threats; - } elseif ( $type === 'theme' ) { - ++$history->num_themes_threats; - } - } - /** * Handles core threats * diff --git a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx index e1274e8e29a17..cbb49498c353f 100644 --- a/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/fix-threat-modal/index.jsx @@ -7,7 +7,7 @@ import ThreatFixHeader from '../threat-fix-header'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const FixThreatModal = ( { id, fixable, label, icon, severity } ) => { +const FixThreatModal = ( { threat } ) => { const { setModal } = useModal(); const { fixThreats, isLoading: isFixersLoading } = useFixers(); @@ -21,7 +21,7 @@ const FixThreatModal = ( { id, fixable, label, icon, severity } ) => { const handleFixClick = () => { return async event => { event.preventDefault(); - await fixThreats( [ id ] ); + await fixThreats( [ threat.id ] ); setModal( { type: null } ); }; }; @@ -37,10 +37,7 @@ const FixThreatModal = ( { id, fixable, label, icon, severity } ) => {
- +
diff --git a/projects/plugins/protect/src/js/components/free-accordion/index.jsx b/projects/plugins/protect/src/js/components/free-accordion/index.jsx deleted file mode 100644 index e801d9374fd33..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/index.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { Icon, chevronDown, chevronUp } from '@wordpress/icons'; -import clsx from 'clsx'; -import React, { useState, useCallback, useContext } from 'react'; -import styles from './styles.module.scss'; - -const FreeAccordionContext = React.createContext(); - -export const FreeAccordionItem = ( { id, title, label, icon, children, onOpen } ) => { - const accordionData = useContext( FreeAccordionContext ); - const open = accordionData?.open === id; - const setOpen = accordionData?.setOpen; - - const bodyClassNames = clsx( styles[ 'accordion-body' ], { - [ styles[ 'accordion-body-open' ] ]: open, - [ styles[ 'accordion-body-close' ] ]: ! open, - } ); - - const handleClick = useCallback( () => { - if ( ! open ) { - onOpen?.(); - } - setOpen( current => { - return current === id ? null : id; - } ); - }, [ open, onOpen, setOpen, id ] ); - - return ( -
- -
- { children } -
-
- ); -}; - -const FreeAccordion = ( { children } ) => { - const [ open, setOpen ] = useState(); - - return ( - -
{ children }
-
- ); -}; - -export default FreeAccordion; diff --git a/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx b/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx deleted file mode 100644 index 43ad41e2501eb..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/stories/index.stories.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { wordpress, plugins } from '@wordpress/icons'; -import React from 'react'; -import FreeAccordion, { FreeAccordionItem } from '..'; - -export default { - title: 'Plugins/Protect/Free Accordion', - component: FreeAccordion, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
- -
- ), - ], -}; - -// eslint-disable-next-line no-unused-vars -export const Default = args => ( - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - -); diff --git a/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss b/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss deleted file mode 100644 index 5278f6eff39f4..0000000000000 --- a/projects/plugins/protect/src/js/components/free-accordion/styles.module.scss +++ /dev/null @@ -1,79 +0,0 @@ -.accordion { - border-radius: var( --jp-border-radius ); - border: 1px solid var( --jp-gray ); - - & > *:not(:last-child) { - border-bottom: 1px solid var( --jp-gray ); - } -} - -.accordion-item { - background-color: var( --jp-white ); -} - -.accordion-header { - margin: 0; - display: grid; - grid-template-columns: repeat(9, 1fr); - cursor: pointer; - box-sizing: border-box; - background: none; - border: none; - width: 100%; - align-items: center; - outline-color: var( --jp-black ); - padding: calc( var( --spacing-base ) * 2) calc( var( --spacing-base ) * 3); // 16px | 24px - text-align: start; - - >:first-of-type { - grid-column: 1/8; - } - - >:last-of-type { - grid-column: 9; - } - - &:hover { - background: var( --jp-gray-0 ); - } -} - -.accordion-header-label { - display: flex; - align-items: center; - font-size: var( --font-body-small ); - font-weight: normal; -} - -.accordion-header-label-icon { - margin-right: var( --spacing-base ); // 8px -} - -.accordion-header-description { - font-weight: 600; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-button { - align-items: center; -} - -.accordion-body { - transform-origin: top center; - overflow: hidden; - - &-close { - transition: all .1s; - max-height: 0; - padding: 0; - transform: scaleY(0); - } - - &-open { - transition: max-height .3s, transform .2s; - padding: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 7 ); // 32 px | 56px - max-height: 1000px; - transform: scaleY(1); - } -} 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 7e8113b6f38ab..591e90c6146b6 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 @@ -1,4 +1,5 @@ import { Button, getRedirectUrl, Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; import { createInterpolateElement, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; @@ -7,10 +8,11 @@ import useModal from '../../hooks/use-modal'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => { +const IgnoreThreatModal = ( { threat } ) => { const { setModal } = useModal(); const ignoreThreatMutation = useIgnoreThreatMutation(); const codeableURL = getRedirectUrl( 'jetpack-protect-codeable-referral' ); + const icon = getThreatIcon( threat ); const [ isIgnoring, setIsIgnoring ] = useState( false ); @@ -25,7 +27,7 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => { return async event => { event.preventDefault(); setIsIgnoring( true ); - await ignoreThreatMutation.mutateAsync( id ); + await ignoreThreatMutation.mutateAsync( threat.id ); setModal( { type: null } ); setIsIgnoring( false ); }; @@ -42,12 +44,12 @@ const IgnoreThreatModal = ( { id, title, label, icon, severity } ) => {
- { label } + { getThreatSubtitle( threat ) } - { title } + { threat.title }
- +
diff --git a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx b/projects/plugins/protect/src/js/components/paid-accordion/index.jsx deleted file mode 100644 index d4a6fa5d3f0ba..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/index.jsx +++ /dev/null @@ -1,187 +0,0 @@ -import { - Spinner, - Text, - ThreatSeverityBadge, - useBreakpointMatch, -} from '@automattic/jetpack-components'; -import { ExternalLink } from '@wordpress/components'; -import { dateI18n } from '@wordpress/date'; -import { createInterpolateElement } from '@wordpress/element'; -import { sprintf, __ } from '@wordpress/i18n'; -import { Icon, check, chevronDown, chevronUp, info } from '@wordpress/icons'; -import clsx from 'clsx'; -import React, { useState, useCallback, useContext, useMemo } from 'react'; -import { PAID_PLUGIN_SUPPORT_URL } from '../../constants'; -import useFixers from '../../hooks/use-fixers'; -import IconTooltip from '../icon-tooltip'; -import styles from './styles.module.scss'; - -// Extract context provider for clarity and reusability -const PaidAccordionContext = React.createContext(); - -// Component for displaying threat dates -const ScanHistoryDetails = ( { firstDetected, fixedOn, status } ) => { - const statusText = useMemo( () => { - if ( status === 'fixed' ) { - return sprintf( - /* translators: %s: Fixed on date */ - __( 'Threat fixed %s', 'jetpack-protect' ), - dateI18n( 'M j, Y', fixedOn ) - ); - } - if ( status === 'ignored' ) { - return __( 'Threat ignored', 'jetpack-protect' ); - } - return null; - }, [ status, fixedOn ] ); - - return ( - firstDetected && ( - <> - - { sprintf( - /* translators: %s: First detected date */ - __( 'Threat found %s', 'jetpack-protect' ), - dateI18n( 'M j, Y', firstDetected ) - ) } - { statusText && ( - <> - - { statusText } - - ) } - - { [ 'fixed', 'ignored' ].includes( status ) && } - - ) - ); -}; - -// Badge for displaying the status (fixed or ignored) -const StatusBadge = ( { status } ) => ( -
- { status === 'fixed' - ? __( 'Fixed', 'jetpack-protect' ) - : __( 'Ignored', 'jetpack-protect', /* dummy arg to avoid bad minification */ 0 ) } -
-); - -const renderFixerStatus = ( isActiveFixInProgress, isStaleFixInProgress ) => { - if ( isStaleFixInProgress ) { - return ( - contact support.', - 'jetpack-protect' - ), - { - supportLink: ( - - ), - } - ) } - /> - ); - } - - if ( isActiveFixInProgress ) { - return ; - } - - return ; -}; - -export const PaidAccordionItem = ( { - id, - title, - label, - icon, - fixable, - severity, - children, - firstDetected, - fixedOn, - onOpen, - status, - hideAutoFixColumn = false, -} ) => { - const { open, setOpen } = useContext( PaidAccordionContext ); - const isOpen = open === id; - - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - - const handleClick = useCallback( () => { - if ( ! isOpen ) { - onOpen?.(); - } - setOpen( current => ( current === id ? null : id ) ); - }, [ isOpen, onOpen, setOpen, id ] ); - - const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); - - return ( -
- -
- { children } -
-
- ); -}; - -const PaidAccordion = ( { children } ) => { - const [ open, setOpen ] = useState(); - - return ( - -
{ children }
-
- ); -}; - -export default PaidAccordion; diff --git a/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx b/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx deleted file mode 100644 index 252f22b2bad77..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/stories/broken/index.stories.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { Text } from '@automattic/jetpack-components'; -import { wordpress, plugins } from '@wordpress/icons'; -import React from 'react'; -import PaidAccordion, { PaidAccordionItem } from '..'; - -export default { - title: 'Plugins/Protect/Paid Accordion', - component: PaidAccordion, - parameters: { - layout: 'centered', - }, - decorators: [ - Story => ( -
- -
- ), - ], -}; - -// eslint-disable-next-line no-unused-vars -export const Default = args => ( - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - - - What is the problem? - - - Post authors are able to bypass KSES restrictions in WordPress { '>' }= 5.9 (and or - Gutenberg { '>' }= 9.8.0) due to the order filters are executed, which could allow them to - perform to Stored Cross-Site Scripting attacks - - - How to fix it? - - Update to WordPress 5.9.2 - - -); diff --git a/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss b/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss deleted file mode 100644 index 3e88a5f9f7ccb..0000000000000 --- a/projects/plugins/protect/src/js/components/paid-accordion/styles.module.scss +++ /dev/null @@ -1,191 +0,0 @@ -.accordion { - display: inline-block; - width: 100%; - border-radius: var( --jp-border-radius ); - border: 1px solid var( --jp-gray ); - - & > *:not(:last-child) { - border-bottom: 1px solid var( --jp-gray ); - } -} - -.accordion-item { - background-color: var( --jp-white ); -} - -.accordion-header { - margin: 0; - display: grid; - grid-template-columns: repeat(9, 1fr); - cursor: pointer; - box-sizing: border-box; - background: none; - border: none; - width: 100%; - align-items: center; - outline-color: var( --jp-black ); - padding: calc( var( --spacing-base ) * 2) calc( var( --spacing-base ) * 3); // 16px | 24px - text-align: start; - - >:first-of-type { - grid-column: 1/7; - } - - >:last-of-type { - grid-column: 9; - } - - >:not( :first-child ) { - margin: auto; - } - - &:hover { - background: var( --jp-gray-0 ); - } -} - -.accordion-header-label { - display: flex; - align-items: center; - font-size: var( --font-body-small ); - font-weight: normal; -} - -.accordion-header-label-icon { - margin-right: var( --spacing-base ); // 8px -} - -.accordion-header-description { - font-weight: 600; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-status { - font-size: var( --font-body-small ); - font-weight: normal; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - margin-bottom: var( --spacing-base ); // 8px -} - -.accordion-header-status-separator { - display: inline-block; - height: 4px; - margin: 2px 12px; - width: 4px; - background-color: var( --jp-gray-50 ); -} - -.accordion-header-button { - align-items: center; -} - -.accordion-body { - transform-origin: top center; - overflow: hidden; - - &-close { - transition: all .1s; - max-height: 0; - padding: 0; - transform: scaleY(0); - } - - &-open { - transition: max-height .3s, transform .2s; - padding: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 7 ); // 32 px | 56px - max-height: 1000px; - transform: scaleY(1); - } -} - -.icon-check { - fill: var( --jp-green-40 ); -} - -.icon-info { - fill: var( --jp-red ); -} - -.status-badge { - border-radius: 32px; - flex-shrink: 0; - font-size: 12px; - font-style: normal; - font-weight: 600; - line-height: 16px; - padding: calc( var( --spacing-base ) / 2 ); // 4px - position: relative; - text-align: center; - width: 60px; - margin-left: calc( var( --spacing-base ) * 4 ); // 32px - - &.fixed { - color: var( --jp-white ); - background-color: #008a20; - } - - &.ignored { - color: var( --jp-white ); - background-color: var( --jp-gray-50 ); - } -} - -.is-fixed { - color: #008a20; -} - -.support-link { - color: inherit; - - &:focus, - &:hover { - color: inherit; - box-shadow: none; - } -} - -@media ( max-width: 599px ) { - .accordion-header { - display: grid; - grid-auto-rows: minmax( auto, auto ); - - >:first-child { - grid-column: 1/8; - grid-row: 1; - } - - >:nth-child( 2 ) { - padding-left: calc( var( --spacing-base ) * 4 ); // 32px - grid-row: 2; - } - - >:nth-child( 3 ) { - grid-row: 2; - } - - >:nth-child( 3 ) span { - position: absolute; - margin-top: var( --spacing-base ); // 8px - } - - >:last-child { - grid-column: 10; - grid-row: 1/3; - } - } - - .status-badge { - display: none; - } -} - -@media ( max-width: 1200px ) { - .accordion-header-status { - display: grid; - } - - .accordion-header-status-separator { - display: none; - } -} diff --git a/projects/plugins/protect/src/js/components/pricing-table/index.jsx b/projects/plugins/protect/src/js/components/pricing-table/index.jsx index 3edd7911a0b6a..0f857430d92d8 100644 --- a/projects/plugins/protect/src/js/components/pricing-table/index.jsx +++ b/projects/plugins/protect/src/js/components/pricing-table/index.jsx @@ -11,9 +11,9 @@ import { __ } from '@wordpress/i18n'; import React, { useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import useConnectSiteMutation from '../../data/use-connection-mutation'; +import useProductDataQuery from '../../data/use-product-data-query'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; /** * Product Detail component. @@ -30,7 +30,7 @@ const ConnectedPricingTable = () => { } ); // Access paid protect product data - const { jetpackScan } = useProtectData(); + const { data: jetpackScan } = useProductDataQuery(); const { pricingForUi } = jetpackScan; const { introductoryOffer, currencyCode: currency = 'USD' } = pricingForUi; diff --git a/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx b/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx index bc5e0107cea80..d1cfd7dadd15a 100644 --- a/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx +++ b/projects/plugins/protect/src/js/components/threat-fix-header/index.jsx @@ -1,4 +1,5 @@ import { Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatSubtitle } from '@automattic/jetpack-scan'; import { __, sprintf } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import React, { useState, useCallback } from 'react'; @@ -68,7 +69,7 @@ export default function ThreatFixHeader( { threat, fixAllDialog, onCheckFix } )
- { threat.label } + { getThreatSubtitle( threat ) } { getFixerMessage( threat.fixable ) } diff --git a/projects/plugins/protect/src/js/components/threats-list/empty.jsx b/projects/plugins/protect/src/js/components/threats-list/empty.jsx deleted file mode 100644 index 2d493b11e64a4..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/empty.jsx +++ /dev/null @@ -1,140 +0,0 @@ -import { H3, Text } from '@automattic/jetpack-components'; -import { createInterpolateElement } from '@wordpress/element'; -import { sprintf, __, _n } from '@wordpress/i18n'; -import { useMemo, useState } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; -import OnboardingPopover from '../onboarding-popover'; -import ScanButton from '../scan-button'; -import styles from './styles.module.scss'; - -const ProtectCheck = () => ( - - - - -); - -/** - * Time Since - * - * @param {string} date - The past date to compare to the current date. - * @return {string} - A description of the amount of time between a date and now, i.e. "5 minutes ago". - */ -const timeSince = date => { - const now = new Date(); - const offset = now.getTimezoneOffset() * 60000; - - const seconds = Math.floor( ( new Date( now.getTime() + offset ).getTime() - date ) / 1000 ); - - let interval = seconds / 31536000; // 364 days - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of years i.e. "5 years ago". - _n( '%s year ago', '%s years ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 2592000; // 30 days - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of months i.e. "5 months ago". - _n( '%s month ago', '%s months ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 86400; // 1 day - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of days i.e. "5 days ago". - _n( '%s day ago', '%s days ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 3600; // 1 hour - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of hours i.e. "5 hours ago". - _n( '%s hour ago', '%s hours ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - interval = seconds / 60; // 1 minute - if ( interval > 1 ) { - return sprintf( - // translators: placeholder is a number amount of minutes i.e. "5 minutes ago". - _n( '%s minute ago', '%s minutes ago', Math.floor( interval ), 'jetpack-protect' ), - Math.floor( interval ) - ); - } - - return __( 'a few seconds ago', 'jetpack-protect' ); -}; - -const EmptyList = () => { - const { lastChecked } = useProtectData(); - const { hasPlan } = usePlan(); - const { data: status } = useScanStatusQuery(); - - const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] = - useState( null ); - - const timeSinceLastScan = useMemo( () => { - return lastChecked ? timeSince( Date.parse( lastChecked ) ) : null; - }, [ lastChecked ] ); - - return ( -
- -

- { __( "Don't worry about a thing", 'jetpack-protect' ) } -

- - { timeSinceLastScan - ? createInterpolateElement( - sprintf( - // translators: placeholder is the amount of time since the last scan, i.e. "5 minutes ago". - __( - 'The last Protect scan ran %s and everything looked great.', - 'jetpack-protect' - ), - timeSinceLastScan - ), - { - strong: , - } - ) - : __( 'No threats have been detected by the current scan.', 'jetpack-protect' ) } - - { hasPlan && ( - <> - - { ! isScanInProgress( status ) && ( -
- ); -}; - -export default EmptyList; diff --git a/projects/plugins/protect/src/js/components/threats-list/free-list.jsx b/projects/plugins/protect/src/js/components/threats-list/free-list.jsx deleted file mode 100644 index 88d4a92f9bac5..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/free-list.jsx +++ /dev/null @@ -1,125 +0,0 @@ -import { Text, Button, ContextualUpgradeTrigger } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import usePlan from '../../hooks/use-plan'; -import FreeAccordion, { FreeAccordionItem } from '../free-accordion'; -import Pagination from './pagination'; -import styles from './styles.module.scss'; - -const ThreatAccordionItem = ( { - description, - fixedIn, - icon, - id, - label, - name, - source, - title, - type, -} ) => { - const { recordEvent } = useAnalyticsTracks(); - const { upgradePlan } = usePlan(); - - const getScan = useCallback( () => { - recordEvent( 'jetpack_protect_threat_list_get_scan_link_click' ); - upgradePlan(); - }, [ recordEvent, upgradePlan ] ); - - const learnMoreButton = source ? ( - - ) : null; - - return ( - { - if ( ! [ 'core', 'plugin', 'theme' ].includes( type ) ) { - return; - } - recordEvent( `jetpack_protect_${ type }_threat_open` ); - }, [ recordEvent, type ] ) } - > - { description && ( -
- - { __( 'What is the problem?', 'jetpack-protect' ) } - - { description } - { learnMoreButton } -
- ) } - { fixedIn && ( -
- - { __( 'How to fix it?', 'jetpack-protect' ) } - - - { - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - sprintf( __( 'Update to %1$s %2$s', 'jetpack-protect' ), name, fixedIn ) - } - - -
- ) } - { ! description &&
{ learnMoreButton }
} -
- ); -}; - -const FreeList = ( { list } ) => { - return ( - - { ( { currentItems } ) => ( - - { currentItems.map( - ( { - description, - fixedIn, - icon, - id, - label, - name, - source, - table, - title, - type, - version, - } ) => ( - - ) - ) } - - ) } - - ); -}; - -export default FreeList; diff --git a/projects/plugins/protect/src/js/components/threats-list/index.jsx b/projects/plugins/protect/src/js/components/threats-list/index.jsx deleted file mode 100644 index 2823a804c1412..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/index.jsx +++ /dev/null @@ -1,194 +0,0 @@ -import { - Container, - Col, - Title, - Button, - useBreakpointMatch, - Text, -} from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback, useMemo, useState } from 'react'; -import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; -import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; -import usePlan from '../../hooks/use-plan'; -import OnboardingPopover from '../onboarding-popover'; -import ScanButton from '../scan-button'; -import EmptyList from './empty'; -import FreeList from './free-list'; -import ThreatsNavigation from './navigation'; -import PaidList from './paid-list'; -import styles from './styles.module.scss'; -import useThreatsList from './use-threats-list'; - -const ThreatsList = () => { - const { hasPlan } = usePlan(); - const { item, list, selected, setSelected } = useThreatsList(); - const [ isSm ] = useBreakpointMatch( 'sm' ); - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - - const { data: status } = useScanStatusQuery(); - const scanning = isScanInProgress( status ); - - // List of fixable threats that do not have a fix in progress - const fixableList = useMemo( () => { - return list.filter( threat => { - const threatId = parseInt( threat.id ); - return ( - threat.fixable && ! isThreatFixInProgress( threatId ) && ! isThreatFixStale( threatId ) - ); - } ); - }, [ list, isThreatFixInProgress, isThreatFixStale ] ); - - // Popover anchors - const [ yourScanResultsPopoverAnchor, setYourScanResultsPopoverAnchor ] = useState( null ); - const [ understandSeverityPopoverAnchor, setUnderstandSeverityPopoverAnchor ] = useState( null ); - const [ showAutoFixersPopoverAnchor, setShowAutoFixersPopoverAnchor ] = useState( null ); - const [ dailyAndManualScansPopoverAnchor, setDailyAndManualScansPopoverAnchor ] = - useState( null ); - - const { setModal } = useModal(); - - const handleShowAutoFixersClick = threatList => { - return event => { - event.preventDefault(); - setModal( { - type: 'FIX_ALL_THREATS', - props: { threatList }, - } ); - }; - }; - - const getTitle = useCallback( () => { - switch ( selected ) { - case 'all': - if ( list.length === 1 ) { - return __( 'All threats', 'jetpack-protect' ); - } - return sprintf( - /* translators: placeholder is the amount of threats found on the site. */ - __( 'All %s threats', 'jetpack-protect' ), - list.length - ); - case 'core': - return sprintf( - /* translators: placeholder is the amount of WordPress threats found on the site. */ - __( '%1$s WordPress %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - case 'files': - return sprintf( - /* translators: placeholder is the amount of file threats found on the site. */ - __( '%1$s file %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - case 'database': - return sprintf( - /* translators: placeholder is the amount of database threats found on the site. */ - __( '%1$s database %2$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats' - ); - default: - return sprintf( - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - __( '%1$s %2$s in %3$s %4$s', 'jetpack-protect' ), - list.length, - list.length === 1 ? 'threat' : 'threats', - item?.name, - item?.version - ); - } - }, [ selected, list, item ] ); - - return ( - - -
- -
- { ! scanning && ( - - ) } - - - { list?.length > 0 ? ( - <> -
- { getTitle() } - { hasPlan && ( -
- { fixableList.length > 0 && ( - <> - - { ! scanning && ( -
- ) } -
- { hasPlan ? ( - <> -
- -
- - { __( - 'If you have manually fixed any of the threats listed above, you can run a manual scan now or wait for Jetpack to scan your site later today.', - 'jetpack-protect' - ) } - - -
-
- { ! scanning && ( -
- ); -}; - -export default ThreatsList; diff --git a/projects/plugins/protect/src/js/components/threats-list/navigation.jsx b/projects/plugins/protect/src/js/components/threats-list/navigation.jsx deleted file mode 100644 index 9befe85a78612..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/navigation.jsx +++ /dev/null @@ -1,130 +0,0 @@ -import { useBreakpointMatch } from '@automattic/jetpack-components'; -import { __ } from '@wordpress/i18n'; -import { - wordpress as coreIcon, - plugins as pluginsIcon, - warning as warningIcon, - color as themesIcon, - code as filesIcon, -} from '@wordpress/icons'; -import { useCallback, useMemo } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; -import Navigation, { NavigationItem, NavigationGroup } from '../navigation'; - -const ThreatsNavigation = ( { selected, onSelect, sourceType = 'scan', statusFilter = 'all' } ) => { - const { hasPlan } = usePlan(); - const { - results: { plugins, themes }, - counts: { - current: { threats: numThreats, core: numCoreThreats, files: numFilesThreats }, - }, - } = useProtectData( { sourceType, filter: { status: statusFilter } } ); - - const { recordEvent } = useAnalyticsTracks(); - const [ isSmallOrLarge ] = useBreakpointMatch( 'lg', '<' ); - - const trackNavigationClickAll = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_all_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickCore = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_core_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickPlugin = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_plugin_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickTheme = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_theme_click' ); - }, [ recordEvent ] ); - - const trackNavigationClickFiles = useCallback( () => { - recordEvent( 'jetpack_protect_navigation_file_click' ); - }, [ recordEvent ] ); - - const allLabel = useMemo( () => { - if ( statusFilter === 'fixed' ) { - return __( 'All fixed threats', 'jetpack-protect' ); - } - if ( statusFilter === 'ignored' ) { - return __( - 'All ignored threats', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ); - } - return __( 'All threats', 'jetpack-protect' ); - }, [ statusFilter ] ); - - return ( - - - - - { plugins.map( ( { name, threats, checked } ) => ( - - ) ) } - - - { themes.map( ( { name, threats, checked } ) => ( - - ) ) } - - { hasPlan && ( - <> - - - ) } - - ); -}; - -export default ThreatsNavigation; diff --git a/projects/plugins/protect/src/js/components/threats-list/pagination.jsx b/projects/plugins/protect/src/js/components/threats-list/pagination.jsx deleted file mode 100644 index 3e17bed0eeac4..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/pagination.jsx +++ /dev/null @@ -1,142 +0,0 @@ -import { Button, useBreakpointMatch } from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import { chevronLeft, chevronRight } from '@wordpress/icons'; -import React, { useCallback, useState, useMemo } from 'react'; -import styles from './styles.module.scss'; - -const PaginationButton = ( { pageNumber, currentPage, onPageChange } ) => { - const isCurrentPage = useMemo( () => currentPage === pageNumber, [ currentPage, pageNumber ] ); - - const handleClick = useCallback( () => { - onPageChange( pageNumber ); - }, [ onPageChange, pageNumber ] ); - - return ( - - ); -}; - -const Pagination = ( { list, itemPerPage = 10, children } ) => { - const [ isSm ] = useBreakpointMatch( 'sm' ); - - const [ currentPage, setCurrentPage ] = useState( 1 ); - - const handlePreviousPageClick = useCallback( - () => setCurrentPage( currentPage - 1 ), - [ currentPage, setCurrentPage ] - ); - const handleNextPageClick = useCallback( - () => setCurrentPage( currentPage + 1 ), - [ currentPage, setCurrentPage ] - ); - - const totalPages = useMemo( () => Math.ceil( list.length / itemPerPage ), [ list, itemPerPage ] ); - - const currentItems = useMemo( () => { - const indexOfLastItem = currentPage * itemPerPage; - const indexOfFirstItem = indexOfLastItem - itemPerPage; - return list.slice( indexOfFirstItem, indexOfLastItem ); - }, [ currentPage, list, itemPerPage ] ); - - const pageNumbers = useMemo( () => { - if ( isSm ) { - return [ currentPage ]; - } - - const result = [ 1 ]; - if ( currentPage > 3 && totalPages > 4 ) { - result.push( '…' ); - } - - if ( currentPage === 1 ) { - // Current page is the first page. - // i.e. [ 1 ] 2 3 4 ... 10 - result.push( currentPage + 1, currentPage + 2, currentPage + 3 ); - } else if ( currentPage === 2 ) { - // Current page is the second to first page. - // i.e. 1 [ 2 ] 3 4 ... 10 - result.push( currentPage, currentPage + 1, currentPage + 2 ); - } else if ( currentPage < totalPages - 1 ) { - // Current page is positioned in the middle of the pagination. - // i.e. 1 ... 3 [ 4 ] 5 ... 10 - result.push( currentPage - 1, currentPage, currentPage + 1 ); - } else if ( currentPage === totalPages - 1 ) { - // Current page is the second to last page. - // i.e. 1 ... 7 8 [ 9 ] 10 - currentPage > 3 && result.push( currentPage - 2 ); - currentPage > 2 && result.push( currentPage - 1 ); - result.push( currentPage ); - } else if ( currentPage === totalPages ) { - // Current page is the last page. - // i.e. 1 ... 7 8 9 [ 10 ] - currentPage >= 5 && result.push( currentPage - 3 ); - currentPage >= 4 && result.push( currentPage - 2 ); - result.push( currentPage - 1 ); - } - - if ( result[ result.length - 1 ] < totalPages - 1 ) { - result.push( '…' ); - result.push( totalPages ); - } else if ( result[ result.length - 1 ] < totalPages ) { - result.push( totalPages ); - } - - return result.filter( pageNumber => pageNumber <= totalPages || isNaN( pageNumber ) ); - }, [ currentPage, isSm, totalPages ] ); - - return ( - <> - { children( { currentItems } ) } - { totalPages > 1 && ( - - ) } - - ); -}; - -export default Pagination; 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 deleted file mode 100644 index baedf8dfa5184..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/paid-list.jsx +++ /dev/null @@ -1,253 +0,0 @@ -import { - Text, - Button, - DiffViewer, - MarkedLines, - useBreakpointMatch, -} from '@automattic/jetpack-components'; -import { __, sprintf } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; -import useFixers from '../../hooks/use-fixers'; -import useModal from '../../hooks/use-modal'; -import PaidAccordion, { PaidAccordionItem } from '../paid-accordion'; -import Pagination from './pagination'; -import styles from './styles.module.scss'; - -const ThreatAccordionItem = ( { - context, - description, - diff, - filename, - firstDetected, - fixedIn, - fixedOn, - icon, - fixable, - id, - label, - name, - source, - title, - type, - severity, - status, - hideAutoFixColumn = false, -} ) => { - const { setModal } = useModal(); - const { recordEvent } = useAnalyticsTracks(); - - const { isThreatFixInProgress, isThreatFixStale } = useFixers(); - const isActiveFixInProgress = isThreatFixInProgress( id ); - const isStaleFixInProgress = isThreatFixStale( id ); - - const learnMoreButton = source ? ( - - ) : null; - - const handleIgnoreThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'IGNORE_THREAT', - props: { id, label, title, icon, severity }, - } ); - }; - }; - - const handleUnignoreThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'UNIGNORE_THREAT', - props: { id, label, title, icon, severity }, - } ); - }; - }; - - const handleFixThreatClick = () => { - return event => { - event.preventDefault(); - setModal( { - type: 'FIX_THREAT', - props: { id, fixable, label, icon, severity }, - } ); - }; - }; - - return ( - { - if ( ! [ 'core', 'plugin', 'theme', 'file', 'database' ].includes( type ) ) { - return; - } - recordEvent( `jetpack_protect_${ type }_threat_open` ); - }, [ recordEvent, type ] ) } - hideAutoFixColumn={ hideAutoFixColumn } - > - { description && ( -
- - { status !== 'fixed' - ? __( 'What is the problem?', 'jetpack-protect' ) - : __( - 'What was the problem?', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ) } - - { description } - { learnMoreButton } -
- ) } - { ( filename || context || diff ) && ( - - { __( 'The technical details', 'jetpack-protect' ) } - - ) } - { filename && ( - <> - - { - /* translators: filename follows in separate line; e.g. "PHP.Injection.5 in: `post.php`" */ - __( 'Threat found in file:', 'jetpack-protect' ) - } - -
{ filename }
- - ) } - { context && } - { diff && } - { fixedIn && status !== 'fixed' && ( -
- - { __( 'How to fix it?', 'jetpack-protect' ) } - - - { - /* translators: Translates to Update to. %1$s: Name. %2$s: Fixed version */ - sprintf( __( 'Update to %1$s %2$s', 'jetpack-protect' ), name, fixedIn ) - } - -
- ) } - { ! description &&
{ learnMoreButton }
} - { [ 'ignored', 'current' ].includes( status ) && ( -
- { 'ignored' === status && ( - - ) } - { 'current' === status && ( - <> - - { fixable && ( - - ) } - - ) } -
- ) } -
- ); -}; - -const PaidList = ( { list, hideAutoFixColumn = false } ) => { - const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); - - return ( - <> - { ! isSmall && ( -
- { __( 'Details', 'jetpack-protect' ) } - { __( 'Severity', 'jetpack-protect' ) } - { ! hideAutoFixColumn && { __( 'Auto-fix', 'jetpack-protect' ) } } - -
- ) } - - { ( { currentItems } ) => ( - - { currentItems.map( - ( { - context, - description, - diff, - filename, - firstDetected, - fixedIn, - fixedOn, - icon, - fixable, - id, - label, - name, - severity, - source, - table, - title, - type, - version, - status, - } ) => ( - - ) - ) } - - ) } - - - ); -}; - -export default PaidList; diff --git a/projects/plugins/protect/src/js/components/threats-list/styles.module.scss b/projects/plugins/protect/src/js/components/threats-list/styles.module.scss deleted file mode 100644 index 4a50d87b2562b..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/styles.module.scss +++ /dev/null @@ -1,129 +0,0 @@ -.empty { - display: flex; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - max-height: 600px; - flex-direction: column; -} - -.threat-section + .threat-section { - margin-top: calc( var( --spacing-base ) * 5 ); // 40px -} - -.threat-filename { - background-color: var( --jp-gray-0 ); - padding: calc( var( --spacing-base ) * 3 ); // 24px - overflow-x: scroll; -} - -.threat-footer { - display: flex; - justify-content: flex-end; - border-top: 1px solid var( --jp-gray ); - padding-top: calc( var( --spacing-base ) * 3 ); // 24px - margin-top: calc( var( --spacing-base ) * 3 ); // 24px -} -.threat-item-cta { - margin-top: calc( var( --spacing-base ) * 4 ); // 36px -} - -.list-header { - display: flex; - align-items: flex-end; - margin-bottom: calc( var( --spacing-base ) * 2.25 ); // 18px -} - -.list-title { - flex: 1; - margin-bottom: 0; -} - -.list-header__controls { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px -} - -.threat-footer { - width: 100%; - display: flex; - justify-content: right; - padding-top: calc( var( --spacing-base ) * 4 ); // 32px - border-top: 1px solid var( --jp-gray ); - - > :last-child { - margin-left: calc( var( --spacing-base ) * 2 ); // 16px - } -} - -.accordion-header { - display: grid; - grid-template-columns: repeat( 9, 1fr ); - background-color: white; - padding: calc( var( --spacing-base ) * 2 ) calc( var( --spacing-base ) * 3 ); // 16px | 24px - border: 1px solid var( --jp-gray ); - border-bottom: none; - color: var( --jp-gray-50 ); - width: 100%; - - > span:first-child { - grid-column: 1 / 7; - } - - > span:not( :first-child ) { - text-align: center; - } -} - -.manual-scan { - margin: calc( var( --spacing-base ) * 4 ) calc( var( --spacing-base ) * 8 ); // 32px | 64px - text-align: center; -} - -@media ( max-width: 599px ) { - - .list-header { - margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px - } - - .list-title { - display: none; - } - - .threat-footer { - justify-content: center; - - > * { - width: 50%; - } - } -} - -.pagination-container { - display: flex; - justify-content: center; - align-items: center; - gap: 4px; - margin-top: calc( var( --spacing-base ) * 4 ); // 24px - margin-bottom: calc(var(--spacing-base) * 2); // 16px - - button { - font-size: var( --font-body ); - width: auto; - height: auto; - padding: 0 var( --spacing-base ); // 0 | 8px - line-height: 32px; - min-width: 32px; - - &.unfocused { - color: var( --jp-black ); - background: none; - - &:hover:not(:disabled) { - color: var( --jp-black ); - background: none; - } - } - } -} diff --git a/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js b/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js deleted file mode 100644 index de000288251ae..0000000000000 --- a/projects/plugins/protect/src/js/components/threats-list/use-threats-list.js +++ /dev/null @@ -1,158 +0,0 @@ -import { - plugins as pluginsIcon, - wordpress as coreIcon, - color as themesIcon, - code as filesIcon, - grid as databaseIcon, -} from '@wordpress/icons'; -import { useEffect, useMemo, useState } from 'react'; -import useProtectData from '../../hooks/use-protect-data'; - -const sortThreats = ( a, b ) => b.severity - a.severity; - -/** - * Flatten threats data - * - * Merges threat category data with each threat it contains, plus any additional data provided. - * - * @param {object} data - The threat category data, i.e. "core", "plugins", "themes", etc. - * @param {object} newData - Additional data to add to each threat. - * @return {object[]} Array of threats with additional properties from the threat category and function argument. - */ -const flattenThreats = ( data, newData ) => { - // If "data" is an empty object - if ( typeof data === 'object' && Object.keys( data ).length === 0 ) { - return []; - } - - // If "data" has multiple entries, recursively flatten each one. - if ( Array.isArray( data ) ) { - return data.map( extension => flattenThreats( extension, newData ) ).flat(); - } - - // Merge the threat category data with each threat it contains, plus any additional data provided. - return data?.threats.map( threat => ( { - ...threat, - ...data, - ...newData, - } ) ); -}; - -/** - * Threats List Hook - * - * @param {object} args - Arguments for the hook. - * @param {string} args.source - "scan" or "history". - * @param {string} args.status - "all", "fixed", or "ignored". - * --- - * @typedef {object} UseThreatsList - * @property {object} item - The selected threat category. - * @property {object[]} list - The list of threats to display. - * @property {string} selected - The selected threat category. - * @property {Function} setSelected - Sets the selected threat category. - * --- - * @return {UseThreatsList} useThreatsList hook. - */ -const useThreatsList = ( { source, status } = { source: 'scan', status: 'all' } ) => { - const [ selected, setSelected ] = useState( 'all' ); - const { - results: { plugins, themes, core, files, database }, - } = useProtectData( { - sourceType: source, - filter: { status, key: selected }, - } ); - - const { unsortedList, item } = useMemo( () => { - // If a specific threat category is selected, filter for and flatten the category's threats. - if ( selected && selected !== 'all' ) { - // Core, files, and database data threats are already grouped together, - // so we just need to flatten them and add the appropriate icon. - switch ( selected ) { - case 'core': - return { - unsortedList: flattenThreats( core, { icon: coreIcon } ), - item: core, - }; - case 'files': - return { - unsortedList: flattenThreats( { threats: files }, { icon: filesIcon } ), - item: files, - }; - case 'database': - return { - unsortedList: flattenThreats( { threats: database }, { icon: databaseIcon } ), - item: database, - }; - default: - break; - } - - // Extensions (i.e. plugins and themes) have entries for each individual extension, - // so we need to check for a matching threat in each extension. - const selectedPlugin = plugins.find( plugin => plugin?.name === selected ); - if ( selectedPlugin ) { - return { - unsortedList: flattenThreats( selectedPlugin, { icon: pluginsIcon } ), - item: selectedPlugin, - }; - } - const selectedTheme = themes.find( theme => theme?.name === selected ); - if ( selectedTheme ) { - return { - unsortedList: flattenThreats( selectedTheme, { icon: themesIcon } ), - item: selectedTheme, - }; - } - } - - // Otherwise, return all threats. - return { - unsortedList: [ - ...flattenThreats( core, { icon: coreIcon } ), - ...flattenThreats( plugins, { icon: pluginsIcon } ), - ...flattenThreats( themes, { icon: themesIcon } ), - ...flattenThreats( { threats: files }, { icon: filesIcon } ), - ...flattenThreats( { threats: database }, { icon: databaseIcon } ), - ], - item: null, - }; - }, [ core, database, files, plugins, selected, themes ] ); - - const getLabel = threat => { - if ( threat.name && threat.version ) { - // Extension threat i.e. "Woocommerce (3.0.0)" - return `${ threat.name } (${ threat.version })`; - } - - if ( threat.filename ) { - // File threat i.e. "index.php" - return threat.filename.split( '/' ).pop(); - } - - if ( threat.table ) { - // Database threat i.e. "wp_posts" - return threat.table; - } - }; - - const list = useMemo( () => { - return unsortedList - .sort( sortThreats ) - .map( threat => ( { label: getLabel( threat ), ...threat } ) ); - }, [ unsortedList ] ); - - useEffect( () => { - if ( selected !== 'all' && status !== 'all' && list.length === 0 ) { - setSelected( 'all' ); - } - }, [ selected, status, item, list ] ); - - return { - item, - list, - selected, - setSelected, - }; -}; - -export default useThreatsList; 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 index 81f1eabb27d5b..7f1ef3652bb85 100644 --- a/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx +++ b/projects/plugins/protect/src/js/components/unignore-threat-modal/index.jsx @@ -1,4 +1,5 @@ import { Button, Text, ThreatSeverityBadge } from '@automattic/jetpack-components'; +import { getThreatIcon, getThreatSubtitle } from '@automattic/jetpack-scan'; import { __ } from '@wordpress/i18n'; import { Icon } from '@wordpress/icons'; import { useState } from 'react'; @@ -7,9 +8,14 @@ import useModal from '../../hooks/use-modal'; import UserConnectionGate from '../user-connection-gate'; import styles from './styles.module.scss'; -const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => { +const UnignoreThreatModal = ( { threat } ) => { const { setModal } = useModal(); + + const icon = getThreatIcon( threat ); + + const [ isUnignoring, setIsUnignoring ] = useState( false ); const unignoreThreatMutation = useUnIgnoreThreatMutation(); + const handleCancelClick = () => { return event => { event.preventDefault(); @@ -17,13 +23,11 @@ const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => { }; }; - const [ isUnignoring, setIsUnignoring ] = useState( false ); - const handleUnignoreClick = () => { return async event => { event.preventDefault(); setIsUnignoring( true ); - await unignoreThreatMutation.mutateAsync( id ); + await unignoreThreatMutation.mutateAsync( threat.id ); setModal( { type: null } ); setIsUnignoring( false ); }; @@ -40,12 +44,12 @@ const UnignoreThreatModal = ( { id, title, label, icon, severity } ) => {
- { label } + { getThreatSubtitle( threat ) } - { title } + { threat.title }
- +
diff --git a/projects/plugins/protect/src/js/data/scan/use-scan-status-query.ts b/projects/plugins/protect/src/js/data/scan/use-scan-status-query.ts index 848b4395e6baf..22df11b48dd71 100644 --- a/projects/plugins/protect/src/js/data/scan/use-scan-status-query.ts +++ b/projects/plugins/protect/src/js/data/scan/use-scan-status-query.ts @@ -1,4 +1,5 @@ import { useConnection } from '@automattic/jetpack-connection'; +import { ScanStatus } from '@automattic/jetpack-scan'; import { useQuery, UseQueryResult, useQueryClient } from '@tanstack/react-query'; import camelize from 'camelize'; import API from '../../api'; @@ -7,7 +8,6 @@ import { SCAN_STATUS_IDLE, SCAN_STATUS_UNAVAILABLE, } from '../../constants'; -import { ScanStatus } from '../../types/scans'; import { QUERY_SCAN_STATUS_KEY } from './../../constants'; export const isRequestedScanNotStarted = ( status: ScanStatus ) => { diff --git a/projects/plugins/protect/src/js/data/use-connection-mutation.ts b/projects/plugins/protect/src/js/data/use-connection-mutation.ts index 90d8481f65edd..e8f40011336d0 100644 --- a/projects/plugins/protect/src/js/data/use-connection-mutation.ts +++ b/projects/plugins/protect/src/js/data/use-connection-mutation.ts @@ -1,4 +1,5 @@ import { useConnection } from '@automattic/jetpack-connection'; +import { ScanStatus } from '@automattic/jetpack-scan'; import { useMutation, UseMutationResult, useQueryClient } from '@tanstack/react-query'; import { __ } from '@wordpress/i18n'; import { @@ -10,7 +11,6 @@ import { SCAN_STATUS_OPTIMISTICALLY_SCANNING, } from '../constants'; import useNotices from '../hooks/use-notices'; -import { ScanStatus } from '../types/scans'; /** * Connect Site Mutation diff --git a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts b/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts deleted file mode 100644 index d560cff20ef86..0000000000000 --- a/projects/plugins/protect/src/js/hooks/use-protect-data/index.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import { useMemo } from 'react'; -import useHistoryQuery from '../../data/scan/use-history-query'; -import useScanStatusQuery from '../../data/scan/use-scan-status-query'; -import useProductDataQuery from '../../data/use-product-data-query'; -import { ExtensionStatus } from '../../types/scans'; -import { Threat, ThreatStatus } from '../../types/threats'; - -type ThreatFilterKey = 'all' | 'core' | 'files' | 'database' | string; - -type Filter = { key: ThreatFilterKey; status: ThreatStatus | 'all' }; - -// Valid "key" values for filtering. -const KEY_FILTERS = [ 'all', 'core', 'plugins', 'themes', 'files', 'database' ]; - -/** - * Filter Extension Threats - * - * @param {Array} threats - The threats to filter. - * @param {object} filter - The filter to apply to the data. - * @param {string} filter.status - The status to filter: 'all', 'current', 'fixed', or 'ignored'. - * @param {string} filter.key - The key to filter: 'all', 'core', 'files', 'database', or an extension name. - * @param {string} key - The threat's key: 'all', 'core', 'files', 'database', or an extension name. - * - * @return {Array} The filtered threats. - */ -const filterThreats = ( threats: Threat[], filter: Filter, key: ThreatFilterKey ): Threat[] => { - if ( ! Array.isArray( threats ) ) { - return []; - } - - return threats.filter( threat => { - if ( filter.status && filter.status !== 'all' && threat.status !== filter.status ) { - return false; - } - if ( filter.key && filter.key !== 'all' && filter.key !== key ) { - return false; - } - return true; - } ); -}; - -/** - * Get parsed data from the initial state - * - * @param {object} options - The options to use when getting the data. - * @param {string} options.sourceType - 'scan' or 'history'. - * @param {object} options.filter - The filter to apply to the data. - * _param {string} options.filter.status - 'all', 'fixed', or 'ignored'. - * _param {string} options.filter.key - 'all', 'core', 'files', 'database', or an extension name. - * - * @return {object} The information available in Protect's initial state. - */ -export default function useProtectData( - { sourceType, filter } = { - sourceType: 'scan', - filter: { status: null, key: null }, - } -) { - const { data: status } = useScanStatusQuery(); - const { data: scanHistory } = useHistoryQuery(); - const { data: jetpackScan } = useProductDataQuery(); - - const { counts, results, error, lastChecked, hasUncheckedItems } = useMemo( () => { - // This hook can provide data from two sources: the current scan or the scan history. - const data = sourceType === 'history' ? { ...scanHistory } : { ...status }; - - // Prepare the result object. - const result = { - results: { - core: [], - plugins: [], - themes: [], - files: [], - database: [], - }, - counts: { - all: { - threats: 0, - core: 0, - plugins: 0, - themes: 0, - files: 0, - database: 0, - }, - current: { - threats: 0, - core: 0, - plugins: 0, - themes: 0, - files: 0, - database: 0, - }, - }, - error: null, - lastChecked: data.lastChecked || null, - hasUncheckedItems: data.hasUncheckedItems || false, - }; - - // Loop through the provided extensions, and update the result object. - const processExtensions = ( extensions: Array< ExtensionStatus >, key: ThreatFilterKey ) => { - if ( ! Array.isArray( extensions ) ) { - return []; - } - extensions.forEach( extension => { - // Update the total counts. - result.counts.all[ key ] += extension?.threats?.length || 0; - result.counts.all.threats += extension?.threats?.length || 0; - - // Filter the extension's threats based on the current filters. - const filteredThreats = filterThreats( - extension?.threats || [], - filter, - KEY_FILTERS.includes( filter.key ) ? key : extension?.name - ); - - // Update the result object with the extension and its filtered threats. - result.results[ key ].push( { ...extension, threats: filteredThreats } ); - - // Update the current counts. - result.counts.current[ key ] += filteredThreats.length; - result.counts.current.threats += filteredThreats.length; - } ); - }; - - // Loop through the provided threats, and update the result object. - const processThreats = ( threatsToProcess: Threat[], key: ThreatFilterKey ) => { - if ( ! Array.isArray( threatsToProcess ) ) { - return []; - } - - result.counts.all[ key ] += threatsToProcess.length; - result.counts.all.threats += threatsToProcess.length; - - const filteredThreats = filterThreats( threatsToProcess, filter, key ); - - result.results[ key ] = [ ...result.results[ key ], ...filteredThreats ]; - result.counts.current[ key ] += filteredThreats.length; - result.counts.current.threats += filteredThreats.length; - }; - - // Core data may be either a single object or an array of multiple objects. - let cores = Array.isArray( data.core ) ? data.core : []; - if ( data?.core?.threats ) { - cores = [ data.core ]; - } - - // Process the data - processExtensions( cores, 'core' ); - processExtensions( data?.plugins, 'plugins' ); - processExtensions( data?.themes, 'themes' ); - processThreats( data?.files, 'files' ); - processThreats( data?.database, 'database' ); - - // Handle errors - if ( data.error ) { - result.error = { - message: data.errorMessage || __( 'An error occurred.', 'jetpack-protect' ), - code: data.errorCode || 500, - }; - } - - return result; - }, [ scanHistory, sourceType, status, filter ] ); - - return { - results, - counts, - error, - lastChecked, - hasUncheckedItems, - jetpackScan, - }; -} diff --git a/projects/plugins/protect/src/js/index.tsx b/projects/plugins/protect/src/js/index.tsx index b8983d65bb836..2b91f4b090b92 100644 --- a/projects/plugins/protect/src/js/index.tsx +++ b/projects/plugins/protect/src/js/index.tsx @@ -2,7 +2,7 @@ import { ThemeProvider } from '@automattic/jetpack-components'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import * as WPElement from '@wordpress/element'; -import React, { useEffect } from 'react'; +import { useEffect } from 'react'; import { HashRouter, Routes, Route, useLocation, Navigate } from 'react-router-dom'; import Modal from './components/modal'; import PaidPlanGate from './components/paid-plan-gate'; @@ -12,7 +12,6 @@ import { OnboardingRenderedContextProvider } from './hooks/use-onboarding'; import { CheckoutProvider } from './hooks/use-plan'; import FirewallRoute from './routes/firewall'; import ScanRoute from './routes/scan'; -import ScanHistoryRoute from './routes/scan/history'; import SetupRoute from './routes/setup'; import './styles.module.scss'; @@ -62,7 +61,7 @@ function render() { path="/scan/history" element={ - + } /> @@ -70,7 +69,7 @@ function render() { path="/scan/history/:filter" element={ - + } /> diff --git a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx deleted file mode 100644 index 9c8f30b7b8067..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/history-admin-section-hero.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Status, Text } from '@automattic/jetpack-components'; -import { dateI18n } from '@wordpress/date'; -import { __, sprintf } from '@wordpress/i18n'; -import { useMemo } from 'react'; -import { useParams } from 'react-router-dom'; -import AdminSectionHero from '../../../components/admin-section-hero'; -import ErrorAdminSectionHero from '../../../components/error-admin-section-hero'; -import ScanNavigation from '../../../components/scan-navigation'; -import useThreatsList from '../../../components/threats-list/use-threats-list'; -import useProtectData from '../../../hooks/use-protect-data'; -import styles from './styles.module.scss'; - -const HistoryAdminSectionHero: React.FC = () => { - const { filter = 'all' } = useParams(); - const { list } = useThreatsList( { - source: 'history', - status: filter, - } ); - const { counts, error } = useProtectData( { - sourceType: 'history', - filter: { status: filter }, - } ); - const { threats: numAllThreats } = counts.all; - - const oldestFirstDetected = useMemo( () => { - if ( ! list.length ) { - return null; - } - - return list.reduce( ( oldest, current ) => { - return new Date( current.firstDetected ) < new Date( oldest.firstDetected ) - ? current - : oldest; - } ).firstDetected; - }, [ list ] ); - - if ( error ) { - return ( - - ); - } - - return ( - - - - { numAllThreats > 0 - ? sprintf( - /* translators: %s: Total number of threats */ - __( '%1$s previously active %2$s', 'jetpack-protect' ), - numAllThreats, - numAllThreats === 1 ? 'threat' : 'threats' - ) - : __( 'No previously active threats', 'jetpack-protect' ) } - - - - { oldestFirstDetected ? ( - - { sprintf( - /* translators: %s: Oldest first detected date */ - __( '%s - Today', 'jetpack-protect' ), - dateI18n( 'F jS g:i A', oldestFirstDetected, false ) - ) } - - ) : ( - __( 'Most recent results', 'jetpack-protect' ) - ) } - - -
- -
- - } - /> - ); -}; - -export default HistoryAdminSectionHero; diff --git a/projects/plugins/protect/src/js/routes/scan/history/index.jsx b/projects/plugins/protect/src/js/routes/scan/history/index.jsx deleted file mode 100644 index 723f9de9ab230..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/index.jsx +++ /dev/null @@ -1,301 +0,0 @@ -import { AdminSection, Container, Col, H3, Text, Title } from '@automattic/jetpack-components'; -import { __, _n, sprintf } from '@wordpress/i18n'; -import { useCallback } from 'react'; -import { Navigate, useParams } from 'react-router-dom'; -import AdminPage from '../../../components/admin-page'; -import ProtectCheck from '../../../components/protect-check-icon'; -import ThreatsNavigation from '../../../components/threats-list/navigation'; -import PaidList from '../../../components/threats-list/paid-list'; -import useThreatsList from '../../../components/threats-list/use-threats-list'; -import useAnalyticsTracks from '../../../hooks/use-analytics-tracks'; -import usePlan from '../../../hooks/use-plan'; -import useProtectData from '../../../hooks/use-protect-data'; -import ScanFooter from '../scan-footer'; -import HistoryAdminSectionHero from './history-admin-section-hero'; -import StatusFilters from './status-filters'; -import styles from './styles.module.scss'; - -const ScanHistoryRoute = () => { - // Track page view. - useAnalyticsTracks( { pageViewEventName: 'protect_scan_history' } ); - - const { hasPlan } = usePlan(); - const { filter = 'all' } = useParams(); - - const { item, list, selected, setSelected } = useThreatsList( { - source: 'history', - status: filter, - } ); - - const { counts, error } = useProtectData( { - sourceType: 'history', - filter: { status: filter }, - } ); - const { threats: numAllThreats } = counts.all; - - const { counts: fixedCounts } = useProtectData( { - sourceType: 'history', - filter: { status: 'fixed', key: selected }, - } ); - const { threats: numFixed } = fixedCounts.current; - - const { counts: ignoredCounts } = useProtectData( { - sourceType: 'history', - filter: { status: 'ignored', key: selected }, - } ); - const { threats: numIgnored } = ignoredCounts.current; - - /** - * Get the title for the threats list based on the selected filters and the amount of threats. - */ - const getTitle = useCallback( () => { - switch ( selected ) { - case 'all': - if ( list.length === 1 ) { - switch ( filter ) { - case 'fixed': - return __( 'All fixed threats', 'jetpack-protect' ); - case 'ignored': - return __( - 'All ignored threats', - 'jetpack-protect', - /** dummy arg to avoid bad minification */ 0 - ); - default: - return __( 'All threats', 'jetpack-protect' ); - } - } - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed threats found on the site. */ - __( 'All %s fixed threats', 'jetpack-protect' ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored threats found on the site. */ - __( 'All %s ignored threats', 'jetpack-protect' ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of threats found on the site. */ - __( 'All %s threats', 'jetpack-protect' ), - list.length - ); - } - case 'core': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed WordPress threats found on the site. */ - _n( - '%1$s fixed WordPress threat', - '%1$s fixed WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored WordPress threats found on the site. */ - _n( - '%1$s ignored WordPress threat', - '%1$s ignored WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of WordPress threats found on the site. */ - _n( - '%1$s WordPress threat', - '%1$s WordPress threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - } - case 'files': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed file threats found on the site. */ - _n( - '%1$s fixed file threat', - '%1$s fixed file threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored file threats found on the site. */ - _n( - '%1$s ignored file threat', - '%1$s ignored file threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of file threats found on the site. */ - _n( '%1$s file threat', '%1$s file threats', list.length, 'jetpack-protect' ), - list.length - ); - } - case 'database': - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: placeholder is the amount of fixed database threats found on the site. */ - _n( - '%1$s fixed database threat', - '%1$s fixed database threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - case 'ignored': - return sprintf( - /* translators: placeholder is the amount of ignored database threats found on the site. */ - _n( - '%1$s ignored database threat', - '%1$s ignored database threats', - list.length, - 'jetpack-protect' - ), - list.length - ); - default: - return sprintf( - /* translators: placeholder is the amount of database threats found on the site. */ - _n( '%1$s database threat', '%1$s database threats', list.length, 'jetpack-protect' ), - list.length - ); - } - default: - switch ( filter ) { - case 'fixed': - return sprintf( - /* translators: Translates to "123 fixed threats in Example Plugin (1.2.3)" */ - _n( - '%1$s fixed threat in %2$s %3$s', - '%1$s fixed threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - case 'ignored': - return sprintf( - /* translators: Translates to "123 ignored threats in Example Plugin (1.2.3)" */ - _n( - '%1$s ignored threat in %2$s %3$s', - '%1$s ignored threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - default: - return sprintf( - /* translators: Translates to "123 threats in Example Plugin (1.2.3)" */ - _n( - '%1$s threat in %2$s %3$s', - '%1$s threats in %2$s %3$s', - list.length, - 'jetpack-protect' - ), - list.length, - item?.name, - item?.version - ); - } - } - }, [ selected, list.length, filter, item?.name, item?.version ] ); - - // Threat history is only available for paid plans. - if ( ! hasPlan ) { - return ; - } - - // Remove the filter if there are no threats to show. - if ( list.length === 0 && filter !== 'all' ) { - return ; - } - - return ( - - - { ( ! error || numAllThreats ) && ( - - - - - - - - - { list.length > 0 ? ( -
-
- { getTitle() } -
- -
-
- -
- ) : ( - <> -
-
- -
-
-
- -

- { __( "Don't worry about a thing", 'jetpack-protect' ) } -

- - { sprintf( - /* translators: %s: Filter type */ - __( 'There are no%sthreats in your scan history.', 'jetpack-protect' ), - 'all' === filter ? ' ' : ` ${ filter } ` - ) } - -
- - ) } - -
- -
-
- ) } - -
- ); -}; - -export default ScanHistoryRoute; diff --git a/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx b/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx deleted file mode 100644 index 1bc9668b11065..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/status-filters.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import React, { useCallback } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; -import ButtonGroup from '../../../components/button-group'; - -/** - * Status Filters component. - * - * @param {object} props - Component props. - * @param {number} props.numFixed - Number of fixed threats. - * @param {number} props.numIgnored - Number of ignored threats. - * - * @return {React.ReactNode} StatusFilters component. - */ -export default function StatusFilters( { numFixed, numIgnored } ) { - const navigate = useNavigate(); - const { filter = 'all' } = useParams(); - const navigateOnClick = useCallback( path => () => navigate( path ), [ navigate ] ); - - return ( - - - { __( 'All', 'jetpack-protect' ) } - - - { __( 'Fixed', 'jetpack-protect' ) } - - - { __( 'Ignored', 'jetpack-protect' ) } - - - ); -} diff --git a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss deleted file mode 100644 index f66602e59a9e9..0000000000000 --- a/projects/plugins/protect/src/js/routes/scan/history/styles.module.scss +++ /dev/null @@ -1,45 +0,0 @@ -.empty { - display: flex; - width: 100%; - height: 100%; - align-items: center; - justify-content: center; - max-height: 600px; - flex-direction: column; -} - -.subheading-content { - font-weight: bold; -} - -.list-header { - display: flex; - justify-content: flex-end; - align-items: flex-end; - margin-bottom: calc( var( --spacing-base ) * 2.25 ); // 18px -} - -.list-title { - flex: 1; - margin-bottom: 0; -} - -.list-header__controls { - display: flex; - gap: calc( var( --spacing-base ) * 2 ); // 16px -} - -@media ( max-width: 599px ) { - - .list-header { - margin-bottom: calc( var( --spacing-base ) * 3 ); // 24px - } - - .list-title { - display: none; - } -} - -.scan-navigation { - margin-top: calc( var( --spacing-base ) * 3 ); // 24px -} \ No newline at end of file diff --git a/projects/plugins/protect/src/js/routes/scan/index.jsx b/projects/plugins/protect/src/js/routes/scan/index.jsx index 1f3cdfdd7520f..95edd0acf78a6 100644 --- a/projects/plugins/protect/src/js/routes/scan/index.jsx +++ b/projects/plugins/protect/src/js/routes/scan/index.jsx @@ -1,14 +1,15 @@ import { AdminSection, Container, Col } from '@automattic/jetpack-components'; +import { useMemo } from 'react'; +import { useLocation, useParams } from 'react-router-dom'; import AdminPage from '../../components/admin-page'; -import ThreatsList from '../../components/threats-list'; import useScanStatusQuery from '../../data/scan/use-scan-status-query'; import useAnalyticsTracks from '../../hooks/use-analytics-tracks'; import { OnboardingContext } from '../../hooks/use-onboarding'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; import onboardingSteps from './onboarding-steps'; import ScanAdminSectionHero from './scan-admin-section-hero'; import ScanFooter from './scan-footer'; +import ScanResultsDataView from './scan-results-data-view'; /** * Scan Page @@ -19,23 +20,39 @@ import ScanFooter from './scan-footer'; */ const ScanPage = () => { const { hasPlan } = usePlan(); - const { - counts: { - current: { threats: numThreats }, - }, - lastChecked, - } = useProtectData(); + const location = useLocation(); + const { filter } = useParams(); const { data: status } = useScanStatusQuery( { usePolling: true } ); let currentScanStatus; if ( status.error ) { currentScanStatus = 'error'; - } else if ( ! lastChecked ) { + } else if ( ! status.lastChecked ) { currentScanStatus = 'in_progress'; } else { currentScanStatus = 'active'; } + const filters = useMemo( () => { + if ( location.pathname.includes( '/scan/history' ) ) { + return [ + { + field: 'status', + value: filter ? [ filter ] : [ 'fixed', 'ignored' ], + operator: 'isAny', + }, + ]; + } + + return [ + { + field: 'status', + value: 'current', + operator: 'is', + }, + ]; + }, [ filter, location.pathname ] ); + // Track view for Protect admin page. useAnalyticsTracks( { pageViewEventName: 'protect_admin', @@ -49,15 +66,13 @@ const ScanPage = () => { - { ( ! status.error || numThreats ) && ( - - - - - - - - ) } + + + + + + + diff --git a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx index 60d484cd4a16f..44babe871b0e2 100644 --- a/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx +++ b/projects/plugins/protect/src/js/routes/scan/scan-admin-section-hero.tsx @@ -8,28 +8,23 @@ import OnboardingPopover from '../../components/onboarding-popover'; import ScanNavigation from '../../components/scan-navigation'; import useScanStatusQuery, { isScanInProgress } from '../../data/scan/use-scan-status-query'; import usePlan from '../../hooks/use-plan'; -import useProtectData from '../../hooks/use-protect-data'; import ScanningAdminSectionHero from './scanning-admin-section-hero'; import styles from './styles.module.scss'; const ScanAdminSectionHero: React.FC = () => { const { hasPlan } = usePlan(); const [ isSm ] = useBreakpointMatch( 'sm' ); - const { - counts: { - current: { threats: numThreats }, - }, - lastChecked, - } = useProtectData(); + const { data: status } = useScanStatusQuery(); + const numThreats = status.threats.length; // Popover anchor const [ dailyScansPopoverAnchor, setDailyScansPopoverAnchor ] = useState( null ); let lastCheckedLocalTimestamp = null; - if ( lastChecked ) { + if ( status.lastChecked ) { // Convert the lastChecked UTC date to a local timestamp - lastCheckedLocalTimestamp = new Date( lastChecked + ' UTC' ).getTime(); + lastCheckedLocalTimestamp = new Date( status.lastChecked + ' UTC' ).getTime(); } if ( isScanInProgress( status ) ) { diff --git a/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx b/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx new file mode 100644 index 0000000000000..67dddc09e3243 --- /dev/null +++ b/projects/plugins/protect/src/js/routes/scan/scan-results-data-view.tsx @@ -0,0 +1,56 @@ +import { ThreatsDataViews } from '@automattic/jetpack-components'; +import { Threat } from '@automattic/jetpack-scan'; +import { useCallback } from 'react'; +import useHistoryQuery from '../../data/scan/use-history-query'; +import useScanStatusQuery from '../../data/scan/use-scan-status-query'; +import useModal from '../../hooks/use-modal'; + +/** + * Scan Results Data View + * + * @param {object} props - Component props. + * @param {Array} props.filters - Default filters to apply to the data view. + * + * @return {JSX.Element} ScanResultDataView component. + */ +export default function ScanResultsDataView( { + filters = [], +}: { + filters: React.ComponentProps< typeof ThreatsDataViews >[ 'filters' ]; +} ) { + const { setModal } = useModal(); + + const { data: scanStatus } = useScanStatusQuery(); + const { data: history } = useHistoryQuery(); + + const onFixThreats = useCallback( + ( threats: Threat[] ) => { + setModal( { type: 'FIX_THREAT', props: { threat: threats[ 0 ] } } ); + }, + [ setModal ] + ); + + const onIgnoreThreats = useCallback( + ( threats: Threat[] ) => { + setModal( { type: 'IGNORE_THREAT', props: { threat: threats[ 0 ] } } ); + }, + [ setModal ] + ); + + const onUnignoreThreats = useCallback( + ( threats: Threat[] ) => { + setModal( { type: 'UNIGNORE_THREAT', props: { threat: threats[ 0 ] } } ); + }, + [ setModal ] + ); + + return ( + + ); +} diff --git a/projects/plugins/protect/src/js/routes/scan/styles.module.scss b/projects/plugins/protect/src/js/routes/scan/styles.module.scss index 8651420159fa1..678b7a953f45e 100644 --- a/projects/plugins/protect/src/js/routes/scan/styles.module.scss +++ b/projects/plugins/protect/src/js/routes/scan/styles.module.scss @@ -9,4 +9,11 @@ .scan-navigation { margin-top: calc( var( --spacing-base ) * 3 ); // 24px -} \ No newline at end of file +} + +:global { + .dataviews-wrapper { + margin-left: -48px; + margin-right: -48px; + } +} diff --git a/projects/plugins/protect/src/js/types/global.d.ts b/projects/plugins/protect/src/js/types/global.d.ts index d844820616d06..2b30631ad540f 100644 --- a/projects/plugins/protect/src/js/types/global.d.ts +++ b/projects/plugins/protect/src/js/types/global.d.ts @@ -1,6 +1,6 @@ +import { ScanStatus } from '@automattic/jetpack-scan'; import { PluginData, ThemeData } from './installed-extensions'; import { ProductData } from './products'; -import { ScanStatus } from './scans'; import { WafStatus } from './waf'; declare module '*.scss'; diff --git a/projects/plugins/protect/src/js/types/threats.ts b/projects/plugins/protect/src/js/types/threats.ts index 757503972fa0c..b1c796df603b6 100644 --- a/projects/plugins/protect/src/js/types/threats.ts +++ b/projects/plugins/protect/src/js/types/threats.ts @@ -56,4 +56,12 @@ export type Threat = { /** The diff showing the threat's modified file contents. */ diff?: string; + + /** The affected plugin or theme. */ + extension?: { + name: string; + slug: string; + type: 'plugin' | 'theme'; + version: string; + }; }; diff --git a/projects/plugins/protect/tsconfig.json b/projects/plugins/protect/tsconfig.json index bc49ef3cebb58..3efc0b5fbb273 100644 --- a/projects/plugins/protect/tsconfig.json +++ b/projects/plugins/protect/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "jetpack-js-tools/tsconfig.base.json", - "include": [ "./src/js" ], + "include": [ "./src/js", "../../js-packages/components/components/threats-data-view" ], "compilerOptions": { "sourceMap": true, "outDir": "./build/", diff --git a/projects/plugins/protect/webpack.config.js b/projects/plugins/protect/webpack.config.js index 2f6a45721b100..69d73db9e26d5 100644 --- a/projects/plugins/protect/webpack.config.js +++ b/projects/plugins/protect/webpack.config.js @@ -19,7 +19,7 @@ module.exports = [ ...jetpackWebpackConfig.resolve, }, node: false, - plugins: [ ...jetpackWebpackConfig.StandardPlugins() ], + plugins: [ ...jetpackWebpackConfig.StandardPlugins( { I18nCheckPlugin: { warnOnly: true } } ) ], // to do: handle i18n warnings from @automattic/jetpack-components/node_modules/@wordpress/dataviews module: { strictExportPresence: true, rules: [ @@ -33,6 +33,11 @@ module.exports = [ includeNodeModules: [ '@automattic/jetpack-' ], } ), + // Transpile @wordpress in node_modules too. + jetpackWebpackConfig.TranspileRule( { + includeNodeModules: [ '@wordpress' ], + } ), + // Handle CSS. jetpackWebpackConfig.CssRule( { extensions: [ 'css', 'sass', 'scss' ], From f452d368be1837c067ce0a85e5deb5b5a40ca24e Mon Sep 17 00:00:00 2001 From: Nate Weller Date: Wed, 6 Nov 2024 10:02:04 -0700 Subject: [PATCH 2/2] changelog --- pnpm-lock.yaml | 48 +++++++++++++---- .../update-threats-data-views-component | 5 ++ projects/js-packages/components/package.json | 2 +- projects/js-packages/scan/jest.config.cjs | 11 ++++ projects/js-packages/scan/package.json | 7 +-- projects/js-packages/scan/webpack.config.cjs | 53 ------------------- 6 files changed, 57 insertions(+), 69 deletions(-) create mode 100644 projects/js-packages/components/changelog/update-threats-data-views-component create mode 100644 projects/js-packages/scan/jest.config.cjs delete mode 100644 projects/js-packages/scan/webpack.config.cjs diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9f1bc952d48a..ee48d87954770 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -318,8 +318,8 @@ importers: specifier: 10.9.0 version: 10.9.0(react@18.3.1) '@wordpress/dataviews': - specifier: 4.5.0 - version: 4.5.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 4.7.0 + version: 4.7.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/date': specifier: 5.9.0 version: 5.9.0 @@ -7827,6 +7827,12 @@ packages: peerDependencies: react: ^18.0.0 + '@wordpress/dataviews@4.7.0': + resolution: {integrity: sha512-Uc6iKqo7kbeX7eB1TjcWakD7wLLSCeo+61H0r/OnHWBBV61UVl1IekZ2QLbfVDl1wTLYKMZcz49g1WcB6iidZA==} + engines: {node: '>=18.12.0', npm: '>=8.19.2'} + peerDependencies: + react: ^18.0.0 + '@wordpress/date@5.9.0': resolution: {integrity: sha512-Iywz1bga3cPSrf7k4dh2mYVsACqzu0GXYhfu57ElAM9robGjcUxJdzgbWUZw90v473NOp2UpVYsWCuDEqNDcdw==} engines: {node: '>=18.12.0', npm: '>=8.19.2'} @@ -18589,10 +18595,32 @@ snapshots: - react-dom - supports-color - '@wordpress/dataviews@4.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@wordpress/dataviews@4.7.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) - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.25.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.10.0 + '@wordpress/warning': 3.10.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.7.0(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.25.7 '@wordpress/components': 28.9.0(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) @@ -18738,7 +18766,7 @@ snapshots: '@wordpress/compose': 7.9.0(react@18.3.1) '@wordpress/core-data': 7.9.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/data': 10.9.0(react@18.3.1) - '@wordpress/dataviews': 4.5.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/dataviews': 4.7.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/date': 5.9.0 '@wordpress/deprecated': 4.9.0 '@wordpress/dom': 4.9.0 @@ -18797,7 +18825,7 @@ snapshots: '@wordpress/compose': 7.9.0(react@18.3.1) '@wordpress/core-data': 7.9.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/data': 10.9.0(react@18.3.1) - '@wordpress/dataviews': 4.5.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/dataviews': 4.7.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/date': 5.9.0 '@wordpress/deprecated': 4.9.0 '@wordpress/dom': 4.9.0 @@ -18856,7 +18884,7 @@ snapshots: '@wordpress/compose': 7.9.0(react@18.3.1) '@wordpress/core-data': 7.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/data': 10.9.0(react@18.3.1) - '@wordpress/dataviews': 4.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/dataviews': 4.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/date': 5.9.0 '@wordpress/deprecated': 4.9.0 '@wordpress/dom': 4.9.0 @@ -18969,7 +18997,7 @@ snapshots: '@wordpress/compose': 7.9.0(react@18.3.1) '@wordpress/core-data': 7.9.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/data': 10.9.0(react@18.3.1) - '@wordpress/dataviews': 4.5.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/dataviews': 4.7.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/element': 6.9.0 '@wordpress/hooks': 4.9.0 '@wordpress/html-entities': 4.9.0 @@ -19003,7 +19031,7 @@ snapshots: '@wordpress/compose': 7.9.0(react@18.3.1) '@wordpress/core-data': 7.9.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/data': 10.9.0(react@18.3.1) - '@wordpress/dataviews': 4.5.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/dataviews': 4.7.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/element': 6.9.0 '@wordpress/hooks': 4.9.0 '@wordpress/html-entities': 4.9.0 @@ -19037,7 +19065,7 @@ snapshots: '@wordpress/compose': 7.9.0(react@18.3.1) '@wordpress/core-data': 7.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/data': 10.9.0(react@18.3.1) - '@wordpress/dataviews': 4.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/dataviews': 4.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@wordpress/element': 6.9.0 '@wordpress/hooks': 4.9.0 '@wordpress/html-entities': 4.9.0 diff --git a/projects/js-packages/components/changelog/update-threats-data-views-component b/projects/js-packages/components/changelog/update-threats-data-views-component new file mode 100644 index 0000000000000..0b8fff39c6c6a --- /dev/null +++ b/projects/js-packages/components/changelog/update-threats-data-views-component @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Minor adjustments to the ThreatsDataViews component. + + diff --git a/projects/js-packages/components/package.json b/projects/js-packages/components/package.json index 00e5f8ca0c20d..529a2727158d3 100644 --- a/projects/js-packages/components/package.json +++ b/projects/js-packages/components/package.json @@ -22,7 +22,7 @@ "@wordpress/components": "28.9.0", "@wordpress/compose": "7.9.0", "@wordpress/data": "10.9.0", - "@wordpress/dataviews": "4.5.0", + "@wordpress/dataviews": "4.7.0", "@wordpress/date": "5.9.0", "@wordpress/element": "6.9.0", "@wordpress/i18n": "5.9.0", diff --git a/projects/js-packages/scan/jest.config.cjs b/projects/js-packages/scan/jest.config.cjs new file mode 100644 index 0000000000000..057829a25a0f1 --- /dev/null +++ b/projects/js-packages/scan/jest.config.cjs @@ -0,0 +1,11 @@ +const baseConfig = require( 'jetpack-js-tools/jest/config.base.js' ); + +module.exports = { + ...baseConfig, + transform: { + ...baseConfig.transform, + '\\.[jt]sx?$': require( 'jetpack-js-tools/jest/babel-jest-config-factory.js' )( + require.resolve + ), + }, +}; diff --git a/projects/js-packages/scan/package.json b/projects/js-packages/scan/package.json index 388548539c4df..3f10a9de856ff 100644 --- a/projects/js-packages/scan/package.json +++ b/projects/js-packages/scan/package.json @@ -15,7 +15,7 @@ "license": "GPL-2.0-or-later", "author": "Automattic", "scripts": { - "build": "pnpm run clean && webpack", + "build": "pnpm run clean && pnpm run compile-ts", "clean": "rm -rf build/", "compile-ts": "tsc --pretty", "test": "jest", @@ -53,7 +53,6 @@ "dependencies": { "@automattic/jetpack-api": "workspace:*", "@automattic/jetpack-base-styles": "workspace:*", - "@automattic/jetpack-webpack-config": "workspace:*", "@wordpress/api-fetch": "7.9.0", "@wordpress/element": "6.9.0", "@wordpress/i18n": "5.9.0", @@ -61,9 +60,7 @@ "@wordpress/url": "4.9.0", "debug": "4.3.4", "react": "^18.2.0", - "react-dom": "^18.2.0", - "webpack": "5.94.0", - "webpack-cli": "4.9.1" + "react-dom": "^18.2.0" }, "peerDependencies": { "@wordpress/i18n": "5.9.0", diff --git a/projects/js-packages/scan/webpack.config.cjs b/projects/js-packages/scan/webpack.config.cjs deleted file mode 100644 index 2fa9b2cfcf027..0000000000000 --- a/projects/js-packages/scan/webpack.config.cjs +++ /dev/null @@ -1,53 +0,0 @@ -const path = require( 'path' ); -const jetpackWebpackConfig = require( '@automattic/jetpack-webpack-config/webpack' ); - -module.exports = [ - { - entry: { - index: './src/index.ts', - }, - mode: jetpackWebpackConfig.mode, - devtool: jetpackWebpackConfig.devtool, - output: { - ...jetpackWebpackConfig.output, - path: path.resolve( './build' ), - }, - optimization: { - ...jetpackWebpackConfig.optimization, - }, - resolve: { - ...jetpackWebpackConfig.resolve, - }, - node: false, - plugins: [ ...jetpackWebpackConfig.StandardPlugins() ], - module: { - strictExportPresence: true, - rules: [ - // Transpile JavaScript - jetpackWebpackConfig.TranspileRule( { - exclude: /node_modules\//, - } ), - - // Transpile @automattic/jetpack-* in node_modules too. - jetpackWebpackConfig.TranspileRule( { - includeNodeModules: [ '@automattic/jetpack-' ], - } ), - - // Handle CSS. - jetpackWebpackConfig.CssRule( { - extensions: [ 'css', 'sass', 'scss' ], - extraLoaders: [ 'sass-loader' ], - } ), - - // Handle images. - jetpackWebpackConfig.FileRule(), - ], - }, - externals: { - ...jetpackWebpackConfig.externals, - jetpackConfig: JSON.stringify( { - consumer_slug: 'my_jetpack', - } ), - }, - }, -];