diff --git a/.github/files/list-changed-projects.sh b/.github/files/list-changed-projects.sh index 151d8a9dd5679..b57a0cb4826a9 100755 --- a/.github/files/list-changed-projects.sh +++ b/.github/files/list-changed-projects.sh @@ -32,4 +32,4 @@ else die "Unsupported GITHUB_EVENT_NAME \"$GITHUB_EVENT_NAME\"" fi -pnpx jetpack dependencies list "${ARGS[@]}" | jq -cR 'reduce inputs as $i ({}; .[$i] |= true)' +pnpx jetpack dependencies list "${ARGS[@]}" | jq -ncR 'reduce inputs as $i ({}; .[$i] |= true)' diff --git a/.github/files/setup-wordpress-env.sh b/.github/files/setup-wordpress-env.sh index 2caa7692c58b5..d98076fd31a8d 100755 --- a/.github/files/setup-wordpress-env.sh +++ b/.github/files/setup-wordpress-env.sh @@ -35,12 +35,6 @@ case "$WP_BRANCH" in ;; latest) LATEST=$(php ./tools/get-wp-version.php) - # 5.8.x still requires a monkey-patched obsolete version of phpunit. - # If that's latest, run the coverage test with the upcoming 5.9 instead. - # @todo: Remove this once WordPress 5.9 is "latest". - if [[ "$LATEST" == 5.8.* && "$TEST_SCRIPT" == "test-coverage" ]]; then - LATEST=master - fi git clone --depth=1 --branch "$LATEST" git://develop.git.wordpress.org/ /tmp/wordpress-latest ;; previous) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4bb161252f06a..04e6af9c41d4b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -163,7 +163,7 @@ importers: '@automattic/jetpack-analytics': workspace:^0.1.7 '@automattic/jetpack-api': workspace:^0.8.3 '@automattic/jetpack-base-styles': workspace:^0.1.7 - '@automattic/jetpack-components': workspace:^0.10.3 + '@automattic/jetpack-components': workspace:^0.10.4-alpha '@automattic/jetpack-config': workspace:^0.1.3 '@babel/core': 7.16.0 '@babel/preset-react': 7.16.0 @@ -276,7 +276,7 @@ importers: '@automattic/jetpack-analytics': workspace:^0.1.7 '@automattic/jetpack-api': workspace:^0.8.3 '@automattic/jetpack-base-styles': workspace:^0.1.7 - '@automattic/jetpack-components': workspace:^0.10.3 + '@automattic/jetpack-components': workspace:^0.10.4-alpha '@babel/core': 7.16.0 '@babel/preset-react': 7.16.0 '@wordpress/base-styles': 4.0.4 @@ -318,7 +318,7 @@ importers: specifiers: '@automattic/jetpack-api': workspace:^0.8.3 '@automattic/jetpack-base-styles': workspace:^0.1.7 - '@automattic/jetpack-components': workspace:^0.10.3 + '@automattic/jetpack-components': workspace:^0.10.4-alpha '@babel/core': 7.16.0 '@babel/preset-react': 7.16.0 '@wordpress/components': 19.1.6 @@ -349,7 +349,7 @@ importers: projects/js-packages/partner-coupon: specifiers: - '@automattic/jetpack-components': workspace:^0.10.3 + '@automattic/jetpack-components': workspace:^0.10.4-alpha '@automattic/jetpack-connection': workspace:^0.15.0-alpha '@babel/core': 7.16.0 '@babel/preset-react': 7.16.0 @@ -584,7 +584,7 @@ importers: projects/packages/identity-crisis: specifiers: - '@automattic/jetpack-idc': workspace:^0.9.0 + '@automattic/jetpack-idc': workspace:^0.9.1-alpha '@automattic/jetpack-webpack-config': workspace:^1.1.1 '@babel/core': 7.16.0 '@babel/preset-env': 7.16.4 @@ -647,7 +647,7 @@ importers: specifiers: '@automattic/jetpack-analytics': workspace:^0.1.7 '@automattic/jetpack-base-styles': workspace:^0.1.7 - '@automattic/jetpack-components': workspace:^0.10.3 + '@automattic/jetpack-components': workspace:^0.10.4-alpha '@automattic/jetpack-connection': workspace:^0.15.0-alpha '@automattic/jetpack-webpack-config': workspace:^1.1.1 '@babel/core': 7.16.0 @@ -660,6 +660,7 @@ importers: '@testing-library/react': 11.2.7 '@testing-library/react-hooks': 4.0.1 '@testing-library/user-event': 12.8.3 + '@wordpress/api-fetch': 6.0.0 '@wordpress/components': 19.1.6 '@wordpress/data': 6.1.5 '@wordpress/i18n': 4.2.4 @@ -679,6 +680,7 @@ importers: '@automattic/jetpack-analytics': link:../../js-packages/analytics '@automattic/jetpack-components': link:../../js-packages/components '@automattic/jetpack-connection': link:../../js-packages/connection + '@wordpress/api-fetch': 6.0.0 '@wordpress/components': 19.1.6_aae888dfa296766acacf1a733aa50b3a '@wordpress/data': 6.1.5_react@17.0.2 '@wordpress/i18n': 4.2.4 @@ -714,7 +716,7 @@ importers: '@automattic/color-studio': 2.5.0 '@automattic/jetpack-analytics': workspace:^0.1.7 '@automattic/jetpack-api': workspace:^0.8.3 - '@automattic/jetpack-components': workspace:^0.10.3 + '@automattic/jetpack-components': workspace:^0.10.4-alpha '@automattic/jetpack-webpack-config': workspace:^1.1.1 '@babel/core': 7.16.0 '@babel/plugin-proposal-nullish-coalescing-operator': 7.16.0 @@ -811,7 +813,7 @@ importers: specifiers: '@automattic/jetpack-api': workspace:^0.8.3 '@automattic/jetpack-base-styles': workspace:^0.1.7 - '@automattic/jetpack-components': workspace:^0.10.3 + '@automattic/jetpack-components': workspace:^0.10.4-alpha '@automattic/jetpack-connection': workspace:^0.15.0-alpha '@automattic/jetpack-webpack-config': workspace:^1.1.1 '@babel/core': 7.16.0 @@ -934,9 +936,9 @@ importers: '@automattic/jetpack-analytics': workspace:^0.1.7 '@automattic/jetpack-api': workspace:^0.8.3 '@automattic/jetpack-base-styles': workspace:^0.1.7 - '@automattic/jetpack-components': workspace:^0.10.3 + '@automattic/jetpack-components': workspace:^0.10.4-alpha '@automattic/jetpack-connection': workspace:^0.15.0-alpha - '@automattic/jetpack-licensing': workspace:^0.4.4 + '@automattic/jetpack-licensing': workspace:^0.4.5-alpha '@automattic/jetpack-partner-coupon': workspace:^0.1.7-alpha '@automattic/jetpack-webpack-config': workspace:^1.1.1 '@automattic/popup-monitor': 1.0.0 @@ -7112,6 +7114,15 @@ packages: '@wordpress/i18n': 4.2.4 '@wordpress/url': 3.3.1 + /@wordpress/api-fetch/6.0.0: + resolution: {integrity: sha512-pSfqdzaOO7/SrIDkFJDhVDs0DLy1WmrtIxz4rTub+H538MolyHmUh9xs1aIyakgC9PH7DrkzVmj00d8QXBcVcw==} + engines: {node: '>=12'} + dependencies: + '@babel/runtime': 7.16.7 + '@wordpress/i18n': 4.3.0 + '@wordpress/url': 3.4.0 + dev: false + /@wordpress/autop/3.2.3: resolution: {integrity: sha512-o66vC+aZPmJGMie+Emqa5gtfQYKbLXqGCESTfingXyMxXEpCa4qOEOi1D6vwX61sf3+k2qJ4bvKwJ5nZXjDaSQ==} engines: {node: '>=12'} @@ -8458,6 +8469,13 @@ packages: dependencies: '@babel/runtime': 7.16.7 + /@wordpress/hooks/3.3.0: + resolution: {integrity: sha512-RSOZS5cr9h830VHE7XQ/NbgIfQQXtuSWrb2mX+4uN4qK6ua2hUfZS/twW4af2H2beistK/rUDPpUVO9x7XSQ5w==} + engines: {node: '>=12'} + dependencies: + '@babel/runtime': 7.16.7 + dev: false + /@wordpress/html-entities/3.2.3: resolution: {integrity: sha512-406VUz8CuKgKGrW/wjRB877soSqGhGDwK4sSuNoIC1FvpfniZ0ijpqfsdhJOOynWdz+RYN1wAsfogBpzuREJOg==} engines: {node: '>=12'} @@ -8490,6 +8508,20 @@ packages: sprintf-js: 1.1.2 tannin: 1.2.0 + /@wordpress/i18n/4.3.0: + resolution: {integrity: sha512-VN0fPhhphX1EkVcCMxirLkwVhvdLNDTg5uYfLeK/e3OSUrKhlFFiI+JyfOXZOrD7LLRz6c6wzCPrCw3ehjmkrg==} + engines: {node: '>=12'} + hasBin: true + dependencies: + '@babel/runtime': 7.16.7 + '@wordpress/hooks': 3.3.0 + gettext-parser: 1.4.0 + lodash: 4.17.21 + memize: 1.1.0 + sprintf-js: 1.1.2 + tannin: 1.2.0 + dev: false + /@wordpress/icons/2.10.3: resolution: {integrity: sha512-hVXArGOHLE5pL1G3rHNzsUEuTR4/G6lB+enKYwhYSSIqWuSbyXbZq3nvibxpepPrLy9B3d5t6aR6QUmjMVzIcQ==} dependencies: @@ -8994,6 +9026,14 @@ packages: '@babel/runtime': 7.16.7 lodash: 4.17.21 + /@wordpress/url/3.4.0: + resolution: {integrity: sha512-E4jnotQrNwvrl6Zdo3S3bDBOzVKnTH7xnOr03VuNtjvWtu2ANRS9R/cD9yVXbvDdiBC2jAJHmCrEMVCxGJmuIw==} + engines: {node: '>=12'} + dependencies: + '@babel/runtime': 7.16.7 + lodash: 4.17.21 + dev: false + /@wordpress/viewport/4.0.7_react@17.0.2: resolution: {integrity: sha512-huxUrFW6JNhj/hUfvftZeht3B6HF0jk10oyHW3dPcFW57ceecJTDB9BdS7a99B1LBP8AWjdN8x/3SqrMwE4yfg==} engines: {node: '>=12'} @@ -19691,7 +19731,7 @@ packages: dependencies: array.prototype.flat: 1.2.5 global-cache: 1.2.1 - react-with-styles: 3.2.3 + react-with-styles: 3.2.3_react-dom@17.0.2+react@17.0.2 /react-with-styles/3.2.3: resolution: {integrity: sha512-MTI1UOvMHABRLj5M4WpODfwnveHaip6X7QUMI2x6zovinJiBXxzhA9AJP7MZNaKqg1JRFtHPXZdroUC8KcXwlQ==} @@ -20745,7 +20785,7 @@ packages: klona: 2.0.5 neo-async: 2.6.2 sass: 1.43.3 - webpack: 5.65.0_webpack-cli@4.9.1 + webpack: 5.65.0 dev: true /sass-loader/12.4.0_webpack@5.65.0: diff --git a/projects/js-packages/components/changelog/update-my-jetpack-styles b/projects/js-packages/components/changelog/update-my-jetpack-styles new file mode 100644 index 0000000000000..4e563d7a121dc --- /dev/null +++ b/projects/js-packages/components/changelog/update-my-jetpack-styles @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +RNA: Improve layout structure with Container and Col diff --git a/projects/js-packages/components/components/admin-page/footer.jsx b/projects/js-packages/components/components/admin-page/footer.jsx deleted file mode 100644 index cb130d487295f..0000000000000 --- a/projects/js-packages/components/components/admin-page/footer.jsx +++ /dev/null @@ -1,51 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import { __ } from '@wordpress/i18n'; -import PropTypes from 'prop-types'; - -/** - * Internal dependencies - */ -import styles from './style.module.scss'; -import JetpackFooter from '../jetpack-footer'; -import Row from '../layout/row'; -import Container from '../layout/container'; -import Col from '../layout/col'; - -/** - * Footer for the AdminPage component - * - * @param {object} props - Component properties. - * @returns {React.Component} AdminPage component. - */ -const AdminPageFooter = props => { - const { moduleName, a8cLogoHref } = props; - - return ( -
- - - - - - - -
- ); -}; - -AdminPageFooter.defaultProps = { - a8cLogoHref: 'https://jetpack.com', - moduleName: __( 'Jetpack', 'jetpack' ), -}; - -AdminPageFooter.propTypes = { - /** Link for 'An Automattic Airline' in the footer. */ - a8cLogoHref: PropTypes.string, - /** Name of the module, e.g. 'Jetpack Search' that will be displayed in the footer. */ - moduleName: PropTypes.string, -}; - -export default AdminPageFooter; diff --git a/projects/js-packages/components/components/admin-page/header.jsx b/projects/js-packages/components/components/admin-page/header.jsx deleted file mode 100644 index 5d23d46f13f9c..0000000000000 --- a/projects/js-packages/components/components/admin-page/header.jsx +++ /dev/null @@ -1,32 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; - -/** - * Internal dependencies - */ -import styles from './style.module.scss'; -import JetpackLogo from '../jetpack-logo'; -import Row from '../layout/row'; -import Container from '../layout/container'; -import Col from '../layout/col'; - -/** - * Header for the AdminPage component - * - * @returns {React.Component} AdminPage component. - */ -const AdminPageHeader = () => ( -
- - - - - - - -
-); - -export default AdminPageHeader; diff --git a/projects/js-packages/components/components/admin-page/index.jsx b/projects/js-packages/components/components/admin-page/index.jsx index 865a0353ed93f..10e770a7561cb 100644 --- a/projects/js-packages/components/components/admin-page/index.jsx +++ b/projects/js-packages/components/components/admin-page/index.jsx @@ -9,8 +9,10 @@ import PropTypes from 'prop-types'; * Internal dependencies */ import styles from './style.module.scss'; -import AdminPageHeader from './header'; -import AdminPageFooter from './footer'; +import JetpackFooter from '../jetpack-footer'; +import JetpackLogo from '../jetpack-logo'; +import Container from '../layout/container'; +import Col from '../layout/col'; /** * This is the base structure for any admin page. It comes with Header and Footer. @@ -24,10 +26,24 @@ const AdminPage = props => { const { children, moduleName, a8cLogoHref, showHeader, showFooter } = props; return ( -
- { showHeader && } - { children } - { showFooter && } +
+ { showHeader && ( + + + + + + ) } + + { children } + + { showFooter && ( + + + + + + ) }
); }; diff --git a/projects/js-packages/components/components/admin-page/style.module.scss b/projects/js-packages/components/components/admin-page/style.module.scss index 141e917c78f0d..72aa0ab6a900e 100644 --- a/projects/js-packages/components/components/admin-page/style.module.scss +++ b/projects/js-packages/components/components/admin-page/style.module.scss @@ -1,10 +1,8 @@ -@import '@automattic/jetpack-base-styles/style'; - -.jp-admin-page { +.admin-page { margin-left: -20px; // to neutralize the padding of #wpcontent. -} + background-color: var(--jp-white); -.jp-admin-page-section { - padding: 40px 0px; - background-color: white; + @media (max-width: 782px) { + margin-left: -10px; // to neutralize the padding of #wpcontent. + } } diff --git a/projects/js-packages/components/components/admin-section/basic/index.jsx b/projects/js-packages/components/components/admin-section/basic/index.jsx index f107f45dbd9a3..03efbc2ef6dd1 100644 --- a/projects/js-packages/components/components/admin-section/basic/index.jsx +++ b/projects/js-packages/components/components/admin-section/basic/index.jsx @@ -7,7 +7,6 @@ import React from 'react'; * Internal dependencies */ import styles from './style.module.scss'; -import Container from '../../layout/container'; /** * This is the wrapper component to build sections within your admin page. @@ -17,11 +16,7 @@ import Container from '../../layout/container'; */ const AdminSection = props => { const { children } = props; - return ( -
- { children } -
- ); + return
{ children }
; }; export default AdminSection; diff --git a/projects/js-packages/components/components/admin-section/basic/style.module.scss b/projects/js-packages/components/components/admin-section/basic/style.module.scss index cbe5d2d6f1866..b31bf1adb230b 100644 --- a/projects/js-packages/components/components/admin-section/basic/style.module.scss +++ b/projects/js-packages/components/components/admin-section/basic/style.module.scss @@ -1,6 +1,3 @@ - -.jp-admin-section { - padding: 64px 0px; - background-color: white; - +.section { + background-color: var(--jp-white); } diff --git a/projects/js-packages/components/components/admin-section/hero/index.jsx b/projects/js-packages/components/components/admin-section/hero/index.jsx index e7c46fb5e5749..adc642f0e7123 100644 --- a/projects/js-packages/components/components/admin-section/hero/index.jsx +++ b/projects/js-packages/components/components/admin-section/hero/index.jsx @@ -7,7 +7,6 @@ import React from 'react'; * Internal dependencies */ import styles from './style.module.scss'; -import Container from '../../layout/container'; /** * The wrapper component for a Hero Section to be used in admin pages. @@ -17,11 +16,7 @@ import Container from '../../layout/container'; */ const AdminSectionHero = props => { const { children } = props; - return ( -
- { children } -
- ); + return
{ children }
; }; export default AdminSectionHero; diff --git a/projects/js-packages/components/components/admin-section/hero/style.module.scss b/projects/js-packages/components/components/admin-section/hero/style.module.scss index 6eb3fc404c2ee..65c3200b871a5 100644 --- a/projects/js-packages/components/components/admin-section/hero/style.module.scss +++ b/projects/js-packages/components/components/admin-section/hero/style.module.scss @@ -1,11 +1,3 @@ -@import '@automattic/jetpack-base-styles/style'; - -.jp-admin-section-hero { - padding: 48px 0px 64px 0px; +.section-hero { background: var( --jp-white-off ); - - h1, h2, h3, h4, h5, h6 { - margin-top: 0px; - line-height: 1.2; - } } diff --git a/projects/js-packages/components/components/admin-section/stories/index.jsx b/projects/js-packages/components/components/admin-section/stories/index.jsx index a5cd28c1f377f..b0ecc05221ac9 100644 --- a/projects/js-packages/components/components/admin-section/stories/index.jsx +++ b/projects/js-packages/components/components/admin-section/stories/index.jsx @@ -6,7 +6,7 @@ import AdminSection from '../basic'; import AdminSectionHero from '../hero'; import AdminPage from '../../admin-page'; import Col from '../../layout/col'; -import Row from '../../layout/row'; +import Container from '../../layout/container'; export default { title: 'Playground/Admin Sections', @@ -16,20 +16,20 @@ export default { const Template = () => ( - +

Sample Hero section

This is a sample Hero section

-
+
- +

Sample Section

This is a sample section

-
+
); @@ -39,22 +39,22 @@ export const _default = Template.bind( {} ); export const onlyBasic = () => ( - +

Sample Section

This is a sample section

-
+
); export const onlyHero = () => ( - +

Sample Hero Section

This is a sample Hero section

-
+
); diff --git a/projects/js-packages/components/components/layout/col/index.jsx b/projects/js-packages/components/components/layout/col/index.jsx index 07d9fccf85abb..93580d4b685c7 100644 --- a/projects/js-packages/components/components/layout/col/index.jsx +++ b/projects/js-packages/components/components/layout/col/index.jsx @@ -17,28 +17,29 @@ import styles from './style.module.scss'; * @returns {React.Component} Col component. */ const Col = props => { - const { children, sm, md, lg } = props; - const small = Number.isInteger( sm ) ? sm : 0; - const medium = Number.isInteger( md ) ? md : 0; - const large = Number.isInteger( lg ) ? lg : 0; - const minimum = [ small, medium, large ].reduce( ( prev, curr ) => - curr > 0 && curr < prev ? curr : prev - ); + const { children, className } = props; - const className = classnames( - small > 0 ? styles[ 'sm-col-span-' + small ] : styles[ 'sm-col-span-' + minimum ], - medium > 0 ? styles[ 'md-col-span-' + medium ] : styles[ 'md-col-span-' + minimum ], - large > 0 ? styles[ 'lg-col-span-' + large ] : styles[ 'lg-col-span-' + minimum ] - ); - return
{ children }
; + const sm = Math.min( 4, props.sm ?? 4 ); // max of 4, if undefined = 4 + const md = Math.min( 8, props.md ?? 8 ); // max of 8, if undefined = 8 + const lg = Math.min( 12, props.lg ?? 12 ); // max of 12, if undefined = 12 + + const colClassName = classnames( className, { + [ styles[ `col-sm-${ sm }` ] ]: Number.isInteger( sm ), + [ styles[ `col-md-${ md }` ] ]: Number.isInteger( md ), + [ styles[ `col-lg-${ lg }` ] ]: Number.isInteger( lg ), + } ); + + return
{ children }
; }; Col.proptypes = { - /** Colspan for small viewport. Needs to be an integer. Defaults to the smallest colspan informed. */ - sm: PropTypes.number, - /** Colspan for medium viewport. Needs to be an integer. Defaults to the smallest colspan informed. */ + /** Custom className to be inserted. */ + className: PropTypes.string, + /** Colspan for small viewport. Needs to be an integer. */ + sm: PropTypes.number.isRequired, + /** Colspan for medium viewport. Needs to be an integer. */ md: PropTypes.number, - /** Colspan for large viewport. Needs to be an integer. Defaults to the smallest colspan informed. */ + /** Colspan for large viewport. Needs to be an integer. */ lg: PropTypes.number, }; diff --git a/projects/js-packages/components/components/layout/col/style.module.scss b/projects/js-packages/components/components/layout/col/style.module.scss index 706ad07fc8959..558c50c0e1651 100644 --- a/projects/js-packages/components/components/layout/col/style.module.scss +++ b/projects/js-packages/components/components/layout/col/style.module.scss @@ -1,35 +1,20 @@ -@import '@automattic/jetpack-base-styles/style'; - -@for $i from 1 through 4 { - .sm-col-span-#{$i} { - grid-column-end: span #{$i}; - } -} - -@include for-phone-up { - @for $i from 1 through 8 { - .md-col-span-#{$i} { +@mixin cols($size, $columns) { + @for $i from 1 through $columns { + .col-#{$size}-#{$i} { grid-column-end: span #{$i}; } } } -@include for-tablet-up { - @for $i from 1 through 12 { - .lg-col-span-#{$i} { - grid-column-end: span #{$i}; - } - } +@media ( min-width: 0px ) { + @include cols(sm, 4) } -@include for-tablet-down { - .md-col-span-0 { - display: none; - } +@media ( min-width: 600px ) { + @include cols(md, 8) } -@include for-phone-down { - .sm-col-span-0 { - display: none; - } +@media ( min-width: 960px ) { + @include cols(lg, 12) } + diff --git a/projects/js-packages/components/components/layout/container/index.jsx b/projects/js-packages/components/components/layout/container/index.jsx index 221d769accb34..1c156f008e65e 100644 --- a/projects/js-packages/components/components/layout/container/index.jsx +++ b/projects/js-packages/components/components/layout/container/index.jsx @@ -2,6 +2,8 @@ * External dependencies */ import React from 'react'; +import classNames from 'classnames'; +import PropTypes from 'prop-types'; /** * Internal dependencies @@ -15,8 +17,43 @@ import styles from './style.module.scss'; * @returns {React.Component} Container component. */ const Container = props => { - const { children } = props; - return
{ children }
; + const { children, fluid, className } = props; + + const horizontalSpacing = `calc( var(--horizontal-spacing) * ${ props.horizontalSpacing } )`; + const horizontalGap = `calc( var(--horizontal-spacing) * ${ props.horizontalGap } )`; + + const containerStyle = { + paddingTop: horizontalSpacing, + paddingBottom: horizontalSpacing, + rowGap: horizontalGap, + }; + + const containerClassName = classNames( className, styles.container, { + [ styles.fluid ]: fluid, + } ); + + return ( +
+ { children } +
+ ); +}; + +Container.propTypes = { + /** Make container not having a max width. */ + fluid: PropTypes.bool, + /** Custom className to be inserted. */ + className: PropTypes.string, + /** Number of spacing (top / bottom), it gets mutiplied by 8px. Needs to be an integer */ + horizontalSpacing: PropTypes.number, + /** Number of gap betwen rows, it gets multipled by 8px. Needs to be an integer */ + horizontalGap: PropTypes.number, +}; + +Container.defaultProps = { + fluid: false, + horizontalGap: 1, + horizontalSpacing: 1, }; export default Container; diff --git a/projects/js-packages/components/components/layout/container/style.module.scss b/projects/js-packages/components/components/layout/container/style.module.scss index d914ad02b4df0..9c983ee6939e2 100644 --- a/projects/js-packages/components/components/layout/container/style.module.scss +++ b/projects/js-packages/components/components/layout/container/style.module.scss @@ -1,7 +1,34 @@ -.jp-container { - display: flex; - align-items: center; - flex-wrap: wrap; - max-width: 1128px; +@mixin container($columns, $width, $padding) { + @media ( min-width: #{$width} ) { + padding: 0 #{$padding}; + grid-template-columns: repeat( #{$columns}, minmax(0, 1fr) ); + } +} + +.container { + --max-container-width: 1128px; + + // vertical spacing + --vertical-gutter: 24px; + --vertical-spacing-sm: 16px; + --vertical-spacing-md: 18px; + --vertical-spacing-lg: 24px; + + // horizontal spacing + --horizontal-spacing: 8px; + + display: grid; + column-gap: var(--vertical-gutter); + max-width: var(--max-container-width); margin: 0 auto; + width: 100%; + + @include container( 4, 0px, var(--vertical-spacing-sm) ); + @include container( 8, 600px, var(--vertical-spacing-md) ); + @include container( 12, 960px, var(--vertical-spacing-lg) ); + + &.fluid { + max-width: none; + padding: unset; + } } diff --git a/projects/js-packages/components/components/layout/row/index.jsx b/projects/js-packages/components/components/layout/row/index.jsx deleted file mode 100644 index 9d8e1dec37c78..0000000000000 --- a/projects/js-packages/components/components/layout/row/index.jsx +++ /dev/null @@ -1,22 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; - -/** - * Internal dependencies - */ -import styles from './style.module.scss'; - -/** - * JP Row - * - * @param {object} props - Component properties. - * @returns {React.Component} Row component. - */ -const Row = props => { - const { children } = props; - return
{ children }
; -}; - -export default Row; diff --git a/projects/js-packages/components/components/layout/row/style.module.scss b/projects/js-packages/components/components/layout/row/style.module.scss deleted file mode 100644 index 731a5e746b80b..0000000000000 --- a/projects/js-packages/components/components/layout/row/style.module.scss +++ /dev/null @@ -1,20 +0,0 @@ -@import '@automattic/jetpack-base-styles/style'; - -.jp-row { - display: grid; - grid-gap: 24px; - grid-template-columns: repeat( 4, 1fr ); - width: 100%; - margin: 0 16px; - - @include for-phone-up { - grid-template-columns: repeat( 8, 1fr ); - margin: 0 18px; - } - - @include for-tablet-up { - grid-template-columns: repeat( 12, 1fr ); - max-width: 1128px; - margin: 0 24px; - } -} diff --git a/projects/js-packages/components/components/layout/stories/index.jsx b/projects/js-packages/components/components/layout/stories/index.jsx new file mode 100644 index 0000000000000..382dcac644029 --- /dev/null +++ b/projects/js-packages/components/components/layout/stories/index.jsx @@ -0,0 +1,81 @@ +/** + * External dependencies + */ +import React from 'react'; + +/** + * Internal dependencies + */ +import Container from '../container'; +import Col from '../col'; +import styles from './styles.module.scss'; + +const Layout = ( { items, fluid, horizontalGap, horizontalSpacing } ) => { + return ( + + { items.map( ( { sm, lg, md } ) => ( + + { Number.isInteger( sm ) ? `sm=${ sm } ` : '' } + { Number.isInteger( md ) ? `md=${ md } ` : '' } + { Number.isInteger( lg ) ? `lg=${ lg } ` : '' } + + ) ) } + + + Composition Example + Composition Example + + + + ); +}; + +export default { + title: 'Playground/Layout', + component: Layout, +}; + +const Template = args => ; +export const Default = Template.bind( {} ); +Default.args = { + fluid: false, + horizontalSpacing: 10, + horizontalGap: 5, + items: [ + { + sm: 2, + md: 5, + lg: 4, + }, + { + sm: 2, + md: 3, + lg: 8, + }, + { + sm: 2, + md: 3, + lg: 8, + }, + { + sm: 2, + md: 5, + lg: 4, + }, + { + sm: 2, + md: 5, + lg: 4, + }, + { + sm: 2, + md: 3, + lg: 8, + }, + ], +}; diff --git a/projects/js-packages/components/components/layout/stories/styles.module.scss b/projects/js-packages/components/components/layout/stories/styles.module.scss new file mode 100644 index 0000000000000..5d02b35942615 --- /dev/null +++ b/projects/js-packages/components/components/layout/stories/styles.module.scss @@ -0,0 +1,10 @@ +.container { + background: var(--jp-gray-50); +} + +.col { + padding: 20px; + background: var(--jp-gray-80); + font-size: 1rem; + color: var(--jp-white-off); +} diff --git a/projects/js-packages/components/index.jsx b/projects/js-packages/components/index.jsx index dabd234c58852..493f150083452 100644 --- a/projects/js-packages/components/index.jsx +++ b/projects/js-packages/components/index.jsx @@ -27,6 +27,5 @@ export { default as AdminPage } from './components/admin-page'; export { default as DecorativeCard } from './components/decorative-card'; export { default as Col } from './components/layout/col'; export { default as Container } from './components/layout/container'; -export { default as Row } from './components/layout/row'; export { default as numberFormat } from './components/number-format'; export { getUserLocale, cleanLocale } from './lib/locale'; diff --git a/projects/js-packages/components/package.json b/projects/js-packages/components/package.json index 51010020f841a..883b58ec8abce 100644 --- a/projects/js-packages/components/package.json +++ b/projects/js-packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-components", - "version": "0.10.3", + "version": "0.10.4-alpha", "description": "Jetpack Components Package", "author": "Automattic", "license": "GPL-2.0-or-later", diff --git a/projects/js-packages/connection/changelog/update-my-jetpack-styles b/projects/js-packages/connection/changelog/update-my-jetpack-styles new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/js-packages/connection/changelog/update-my-jetpack-styles @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/js-packages/connection/package.json b/projects/js-packages/connection/package.json index 9e5003d690cfe..864a07701d6a3 100644 --- a/projects/js-packages/connection/package.json +++ b/projects/js-packages/connection/package.json @@ -7,7 +7,7 @@ "dependencies": { "@automattic/jetpack-analytics": "workspace:^0.1.7", "@automattic/jetpack-config": "workspace:^0.1.3", - "@automattic/jetpack-components": "workspace:^0.10.3", + "@automattic/jetpack-components": "workspace:^0.10.4-alpha", "@automattic/jetpack-api": "workspace:^0.8.3", "@wordpress/base-styles": "4.0.4", "@wordpress/browserslist-config": "4.1.0", diff --git a/projects/js-packages/idc/changelog/update-my-jetpack-styles b/projects/js-packages/idc/changelog/update-my-jetpack-styles new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/js-packages/idc/changelog/update-my-jetpack-styles @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/js-packages/idc/package.json b/projects/js-packages/idc/package.json index 482060264ea58..a42387ecb830c 100644 --- a/projects/js-packages/idc/package.json +++ b/projects/js-packages/idc/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-idc", - "version": "0.9.0", + "version": "0.9.1-alpha", "description": "Jetpack Connection Component", "author": "Automattic", "license": "GPL-2.0-or-later", @@ -8,7 +8,7 @@ "@automattic/jetpack-analytics": "workspace:^0.1.7", "@automattic/jetpack-api": "workspace:^0.8.3", "@automattic/jetpack-base-styles": "workspace:^0.1.7", - "@automattic/jetpack-components": "workspace:^0.10.3", + "@automattic/jetpack-components": "workspace:^0.10.4-alpha", "@wordpress/base-styles": "4.0.4", "@wordpress/components": "19.1.6", "@wordpress/compose": "5.0.7", diff --git a/projects/js-packages/licensing/changelog/update-my-jetpack-styles b/projects/js-packages/licensing/changelog/update-my-jetpack-styles new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/js-packages/licensing/changelog/update-my-jetpack-styles @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/js-packages/licensing/package.json b/projects/js-packages/licensing/package.json index 579cd9a463d02..c4e6ce2accef5 100644 --- a/projects/js-packages/licensing/package.json +++ b/projects/js-packages/licensing/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-licensing", - "version": "0.4.4", + "version": "0.4.5-alpha", "description": "Jetpack licensing flow", "homepage": "https://jetpack.com", "bugs": { @@ -37,7 +37,7 @@ }, "dependencies": { "@automattic/jetpack-api": "workspace:^0.8.3", - "@automattic/jetpack-components": "workspace:^0.10.3", + "@automattic/jetpack-components": "workspace:^0.10.4-alpha", "@wordpress/i18n": "4.2.4", "@wordpress/element": "4.0.4", "prop-types": "15.7.2", diff --git a/projects/js-packages/partner-coupon/changelog/update-my-jetpack-styles b/projects/js-packages/partner-coupon/changelog/update-my-jetpack-styles new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/js-packages/partner-coupon/changelog/update-my-jetpack-styles @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/js-packages/partner-coupon/package.json b/projects/js-packages/partner-coupon/package.json index a625139ba536a..6cf6c944ba34d 100644 --- a/projects/js-packages/partner-coupon/package.json +++ b/projects/js-packages/partner-coupon/package.json @@ -28,7 +28,7 @@ }, "dependencies": { "@automattic/jetpack-connection": "workspace:^0.15.0-alpha", - "@automattic/jetpack-components": "workspace:^0.10.3", + "@automattic/jetpack-components": "workspace:^0.10.4-alpha", "@wordpress/i18n": "4.2.4", "classnames": "2.3.1", "prop-types": "15.7.2" diff --git a/projects/js-packages/shared-extension-utils/changelog/update-shared-extension-utils-prepare-release b/projects/js-packages/shared-extension-utils/changelog/update-shared-extension-utils-prepare-release new file mode 100644 index 0000000000000..df7ae220b753e --- /dev/null +++ b/projects/js-packages/shared-extension-utils/changelog/update-shared-extension-utils-prepare-release @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Core: prepare utility for release diff --git a/projects/js-packages/shared-extension-utils/composer.json b/projects/js-packages/shared-extension-utils/composer.json index f60d568dfcb3b..5e88b1125fe17 100644 --- a/projects/js-packages/shared-extension-utils/composer.json +++ b/projects/js-packages/shared-extension-utils/composer.json @@ -26,5 +26,13 @@ } ], "minimum-stability": "dev", - "prefer-stable": true + "prefer-stable": true, + "extra": { + "autotagger": true, + "npmjs-autopublish": true, + "mirror-repo": "Automattic/jetpack-shared-extension-utils", + "changelogger": { + "link-template": "https://github.com/Automattic/jetpack-shared-extension-utils/compare/${old}...${new}" + } + } } diff --git a/projects/js-packages/shared-extension-utils/package.json b/projects/js-packages/shared-extension-utils/package.json index dce5717662b07..c05e0bfd3ef22 100644 --- a/projects/js-packages/shared-extension-utils/package.json +++ b/projects/js-packages/shared-extension-utils/package.json @@ -1,5 +1,4 @@ { - "private": true, "name": "@automattic/jetpack-shared-extension-utils", "version": "0.1.0-alpha", "description": "Utility functions used by the block editor extensions", diff --git a/projects/packages/identity-crisis/changelog/update-my-jetpack-styles b/projects/packages/identity-crisis/changelog/update-my-jetpack-styles new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/packages/identity-crisis/changelog/update-my-jetpack-styles @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/packages/identity-crisis/package.json b/projects/packages/identity-crisis/package.json index 119c87fae0f62..0bc289810fdac 100644 --- a/projects/packages/identity-crisis/package.json +++ b/projects/packages/identity-crisis/package.json @@ -1,6 +1,6 @@ { "name": "jetpack-identity-crisis", - "version": "0.7.0", + "version": "0.7.1-alpha", "description": "Jetpack Identity Crisis", "main": "_inc/admin.jsx", "repository": "https://github.com/Automattic/jetpack-identity-crisis", @@ -15,7 +15,7 @@ }, "browserslist": "extends @wordpress/browserslist-config", "dependencies": { - "@automattic/jetpack-idc": "workspace:^0.9.0", + "@automattic/jetpack-idc": "workspace:^0.9.1-alpha", "@wordpress/data": "6.1.5" }, "devDependencies": { diff --git a/projects/packages/identity-crisis/src/class-identity-crisis.php b/projects/packages/identity-crisis/src/class-identity-crisis.php index 22f775ba754d2..98f66f00b9d55 100644 --- a/projects/packages/identity-crisis/src/class-identity-crisis.php +++ b/projects/packages/identity-crisis/src/class-identity-crisis.php @@ -28,7 +28,7 @@ class Identity_Crisis { /** * Package Version */ - const PACKAGE_VERSION = '0.7.0'; + const PACKAGE_VERSION = '0.7.1-alpha'; /** * Instance of the object. diff --git a/projects/packages/my-jetpack/_inc/components/connections-section/index.jsx b/projects/packages/my-jetpack/_inc/components/connections-section/index.jsx index c653aafb4ae07..671bfede4589a 100644 --- a/projects/packages/my-jetpack/_inc/components/connections-section/index.jsx +++ b/projects/packages/my-jetpack/_inc/components/connections-section/index.jsx @@ -1,10 +1,9 @@ -/* global myJetpackRest */ -/* global myJetpackInitialState */ /** * External dependencies */ -import React, { useCallback } from 'react'; +import React from 'react'; import { ConnectionStatusCard } from '@automattic/jetpack-connection'; +import useMyJetpackConnection from '../../hooks/use-my-jetpack-connection'; /** * Plan section component. @@ -12,16 +11,8 @@ import { ConnectionStatusCard } from '@automattic/jetpack-connection'; * @returns {object} ConnectionsSection React component. */ export default function ConnectionsSection() { - const { apiRoot, apiNonce } = myJetpackRest; - const redirectAfterDisconnect = useCallback( () => { - window.location = myJetpackInitialState.topJetpackMenuItemUrl; - }, [] ); + const { apiRoot, apiNonce, redirectUrl } = useMyJetpackConnection(); return ( - + ); } diff --git a/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/index.jsx b/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/index.jsx index 4f0db5a7ea1c2..d724421f3fe77 100644 --- a/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/index.jsx +++ b/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/index.jsx @@ -9,7 +9,7 @@ import { AdminSection, AdminSectionHero, AdminPage, - Row, + Container, Col, } from '@automattic/jetpack-components'; @@ -21,17 +21,10 @@ import PlansSection from '../plans-section'; import ProductCardsSection from '../product-cards-section'; import useAnalytics from '../../hooks/use-analytics'; import useNoticeWatcher, { useGlobalNotice } from '../../hooks/use-notice'; -import './style.scss'; - -/** - * Component that renders the My Jetpack global notices. - * - * @returns {object} The GlobalNotice component. - */ -function GlobalNotice() { - // Watch global events. - useNoticeWatcher(); +import useMyJetpackConnection from '../../hooks/use-my-jetpack-connection'; +import styles from './styles.module.scss'; +const GlobalNotice = ( { message, options, clean } ) => { /* * Map Notice statuses with Icons. * `success`, `info`, `warning`, `error` @@ -41,18 +34,13 @@ function GlobalNotice() { info, }; - const { message, options, clean } = useGlobalNotice(); - if ( ! message ) { - return null; - } - return ( { iconMap?.[ options.status ] && }
{ message }
); -} +}; /** * The My Jetpack App Main Screen. @@ -60,46 +48,57 @@ function GlobalNotice() { * @returns {object} The MyJetpackScreen component. */ export default function MyJetpackScreen() { + useNoticeWatcher(); + const { message, options, clean } = useGlobalNotice(); + const { tracks: { recordEvent }, } = useAnalytics(); + useEffect( () => { recordEvent( 'jetpack_myjetpack_page_view' ); }, [ recordEvent ] ); + // No render when site is not connected. + const { isSiteConnected } = useMyJetpackConnection( { redirect: true } ); + + if ( ! isSiteConnected ) { + return null; + } + return ( -
- - - - -

- { __( - 'Manage your Jetpack plan and products all in one place', - 'jetpack-my-jetpack' - ) } -

- - -
- - - + + + + +

+ { __( + 'Manage your Jetpack plan and products all in one place', + 'jetpack-my-jetpack' + ) } +

+ + { message && ( + + -
-
+ ) } + + + + + - - - - - - - - - - -
-
+ + + + + + + + + + + ); } diff --git a/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/style.scss b/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/style.scss deleted file mode 100644 index 430bfe51c91ad..0000000000000 --- a/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/style.scss +++ /dev/null @@ -1,20 +0,0 @@ -@import '@automattic/jetpack-base-styles/style'; - -.components-notice { - margin: 20px 0; - - .components-notice__content { - display: flex; - - .components-notice__message-content { - height: 24px; - line-height: 24px; - margin-left: 10px; - } - } - - .components-notice__action.components-button.is-link { - color: var( --jp-black ); - font-weight: 600; - } -} diff --git a/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/styles.module.scss b/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/styles.module.scss new file mode 100644 index 0000000000000..bf1745a22b5b8 --- /dev/null +++ b/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/styles.module.scss @@ -0,0 +1,28 @@ +@import '@automattic/jetpack-base-styles/style'; + +.heading { + font-size: 36px; + line-height: 40px; + margin: 0; +} + +:global { + .components-notice { + margin: 0; + + .components-notice__content { + display: flex; + + .components-notice__message-content { + height: 24px; + line-height: 24px; + margin-left: 10px; + } + } + + .components-notice__action.components-button.is-link { + color: var( --jp-black ); + font-weight: 600; + } + } +} diff --git a/projects/packages/my-jetpack/_inc/components/product-card/index.jsx b/projects/packages/my-jetpack/_inc/components/product-card/index.jsx index b715b15302783..fbcf70bc453c8 100644 --- a/projects/packages/my-jetpack/_inc/components/product-card/index.jsx +++ b/projects/packages/my-jetpack/_inc/components/product-card/index.jsx @@ -148,7 +148,7 @@ const ProductCard = props => {

{ description }

{ canDeactivate ? ( - + { renderActionButton( { ...props, onActivate: activateHandler } ) } { return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + ); }; diff --git a/projects/packages/my-jetpack/_inc/hooks/use-analytics/index.js b/projects/packages/my-jetpack/_inc/hooks/use-analytics/index.js index c5d71bc1d91ef..d33504b9636e0 100644 --- a/projects/packages/my-jetpack/_inc/hooks/use-analytics/index.js +++ b/projects/packages/my-jetpack/_inc/hooks/use-analytics/index.js @@ -1,20 +1,14 @@ -/* global myJetpackRest */ /** * External dependencies */ import { useEffect } from 'react'; import jetpackAnalytics from '@automattic/jetpack-analytics'; -import { useConnection } from '@automattic/jetpack-connection'; +import useMyJetpackConnection from '../use-my-jetpack-connection'; const useAnalytics = () => { - const { apiRoot, apiNonce } = myJetpackRest; - - const { isUserConnected, userConnectionData } = useConnection( { - apiRoot, - apiNonce, - } ); - + const { isUserConnected, userConnectionData } = useMyJetpackConnection(); const { login, ID } = userConnectionData.currentUser.wpcomUser; + /** * Initialize tracks with user data. * Should run when we have a connected user. diff --git a/projects/packages/my-jetpack/_inc/hooks/use-my-jetpack-connection/index.js b/projects/packages/my-jetpack/_inc/hooks/use-my-jetpack-connection/index.js new file mode 100644 index 0000000000000..f04ad8d947b17 --- /dev/null +++ b/projects/packages/my-jetpack/_inc/hooks/use-my-jetpack-connection/index.js @@ -0,0 +1,56 @@ +/* global myJetpackInitialState */ +/* global myJetpackRest */ +/** + * WordPress dependencies + */ +import { useEffect } from 'react'; +import { useConnection } from '@automattic/jetpack-connection'; + +/** + * React custom hook to get the site purchases data. + * + * @param {object} options - Options to pass to the hook. + * @param {boolean} options.reditect - Perform a redirect when no connection is found. + * @returns {object} site purchases data + */ +export default function useMyJetpackConnection( options = { redirect: false } ) { + const { apiRoot, apiNonce } = myJetpackRest; + const { topJetpackMenuItemUrl } = myJetpackInitialState; + const { redirect } = options; + const connectionData = useConnection( { apiRoot, apiNonce } ); + + // Alias: https://github.com/Automattic/jetpack/blob/master/projects/packages/connection/src/class-rest-connector.php/#L315 + const isSiteConnected = connectionData.isRegistered; + + /* + * When the site is not connect, + * and the `redirect` option is set to `true`, + * redirect to the Jetpack dashboard. + */ + useEffect( () => { + // Bail early when topJetpackMenuItemUrl is not defined. + if ( ! topJetpackMenuItemUrl ) { + return; + } + + // Bail early when redirect mode is disabled. + if ( ! redirect ) { + return; + } + + // When site is connected, bail early. + if ( isSiteConnected ) { + return; + } + + window.location = topJetpackMenuItemUrl; + }, [ isSiteConnected, redirect, topJetpackMenuItemUrl ] ); + + return { + apiNonce, + apiRoot, + ...connectionData, + isSiteConnected, + redirectUrl: topJetpackMenuItemUrl, + }; +} diff --git a/projects/packages/my-jetpack/_inc/hooks/use-notice/index.js b/projects/packages/my-jetpack/_inc/hooks/use-notice/index.js index b8473771dd30b..4c4ae629977da 100644 --- a/projects/packages/my-jetpack/_inc/hooks/use-notice/index.js +++ b/projects/packages/my-jetpack/_inc/hooks/use-notice/index.js @@ -1,16 +1,15 @@ -/* global myJetpackRest */ /** * WordPress dependencies */ import { useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { useConnection } from '@automattic/jetpack-connection'; import { useSelect, useDispatch } from '@wordpress/data'; /** * Internal dependencies */ import { STORE_ID } from '../../state/store'; +import useMyJetpackConnection from '../use-my-jetpack-connection'; /** * React custom hook to get global notices. @@ -34,13 +33,8 @@ export function useGlobalNotice() { * the hook dispatches an action to populate the global notice. */ export default function useNoticeWatcher() { - const { apiRoot, apiNonce } = myJetpackRest; const dispatch = useDispatch(); - - const { isUserConnected } = useConnection( { - apiRoot, - apiNonce, - } ); + const { isUserConnected, redirectUrl } = useMyJetpackConnection(); useEffect( () => { if ( ! isUserConnected ) { @@ -54,11 +48,11 @@ export default function useNoticeWatcher() { actions: [ { label: __( 'Connect Jetpack now.', 'jetpack-my-jetpack' ), - url: '#', + url: redirectUrl, }, ], } ); } - }, [ isUserConnected, dispatch ] ); + }, [ isUserConnected, dispatch, redirectUrl ] ); } diff --git a/projects/packages/my-jetpack/changelog/add-hide-my-jetpack-not-connected-site b/projects/packages/my-jetpack/changelog/add-hide-my-jetpack-not-connected-site new file mode 100644 index 0000000000000..64548e21c053e --- /dev/null +++ b/projects/packages/my-jetpack/changelog/add-hide-my-jetpack-not-connected-site @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Do not initialize My Jetpack id site is not connected diff --git a/projects/packages/my-jetpack/changelog/fix-my-jetpack-tests b/projects/packages/my-jetpack/changelog/fix-my-jetpack-tests new file mode 100644 index 0000000000000..c04953f478221 --- /dev/null +++ b/projects/packages/my-jetpack/changelog/fix-my-jetpack-tests @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Fix tests diff --git a/projects/packages/my-jetpack/changelog/fix-my-jetpack-tests#2 b/projects/packages/my-jetpack/changelog/fix-my-jetpack-tests#2 new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/packages/my-jetpack/changelog/fix-my-jetpack-tests#2 @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-handle-when-no-site-connected b/projects/packages/my-jetpack/changelog/update-my-jetpack-handle-when-no-site-connected new file mode 100644 index 0000000000000..021102a890067 --- /dev/null +++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-handle-when-no-site-connected @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Handle when site is not connected diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-remove-connections-section b/projects/packages/my-jetpack/changelog/update-my-jetpack-remove-connections-section new file mode 100644 index 0000000000000..1273531e251be --- /dev/null +++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-remove-connections-section @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +My Jetpack: handle redirect when no connection #22549 diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-styles b/projects/packages/my-jetpack/changelog/update-my-jetpack-styles new file mode 100644 index 0000000000000..0179615c2e85c --- /dev/null +++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-styles @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +My Jetpack: Refactor styles to use layout components and theme provider diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-styles#2 b/projects/packages/my-jetpack/changelog/update-my-jetpack-styles#2 new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-styles#2 @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/packages/my-jetpack/composer.json b/projects/packages/my-jetpack/composer.json index 1c9deb42c30d5..fb8664650ca3e 100644 --- a/projects/packages/my-jetpack/composer.json +++ b/projects/packages/my-jetpack/composer.json @@ -14,6 +14,7 @@ "require-dev": { "yoast/phpunit-polyfills": "1.0.3", "automattic/jetpack-changelogger": "^3.0", + "automattic/jetpack-options": "^1.14", "automattic/wordbless": "@dev" }, "autoload": { diff --git a/projects/packages/my-jetpack/package.json b/projects/packages/my-jetpack/package.json index 6ddd530b01a7a..8ac7b26fe6df5 100644 --- a/projects/packages/my-jetpack/package.json +++ b/projects/packages/my-jetpack/package.json @@ -22,8 +22,9 @@ }, "dependencies": { "@automattic/jetpack-analytics": "workspace:^0.1.7", - "@automattic/jetpack-components": "workspace:^0.10.3", + "@automattic/jetpack-components": "workspace:^0.10.4-alpha", "@automattic/jetpack-connection": "workspace:^0.15.0-alpha", + "@wordpress/api-fetch": "6.0.0", "@wordpress/components": "19.1.6", "@wordpress/data": "6.1.5", "@wordpress/i18n": "4.2.4", diff --git a/projects/packages/my-jetpack/src/class-initializer.php b/projects/packages/my-jetpack/src/class-initializer.php index e46a4dc9453b7..93cd1557dd3d7 100644 --- a/projects/packages/my-jetpack/src/class-initializer.php +++ b/projects/packages/my-jetpack/src/class-initializer.php @@ -37,6 +37,11 @@ public static function init() { return; } + // Do not initialize My Jetpack if site is not connected. + if ( ! ( new Connection_Manager() )->is_connected() ) { + return; + } + // Set up the REST authentication hooks. Connection_Rest_Authentication::init(); diff --git a/projects/packages/my-jetpack/tests/php/test-products-rest.php b/projects/packages/my-jetpack/tests/php/test-products-rest.php index c927eb51a8393..b9a2171e8b480 100644 --- a/projects/packages/my-jetpack/tests/php/test-products-rest.php +++ b/projects/packages/my-jetpack/tests/php/test-products-rest.php @@ -2,6 +2,8 @@ namespace Automattic\Jetpack\My_Jetpack; +use Automattic\Jetpack\Connection\Tokens; +use Jetpack_Options; use PHPUnit\Framework\TestCase; use WorDBless\Options as WorDBless_Options; use WorDBless\Users as WorDBless_Users; @@ -60,6 +62,10 @@ public function set_up() { $this->install_mock_plugin(); + // Mock site connection. + ( new Tokens() )->update_blog_token( 'test.test.1' ); + Jetpack_Options::update_option( 'id', 123 ); + global $wp_rest_server; $wp_rest_server = new WP_REST_Server(); diff --git a/projects/packages/search/CHANGELOG.md b/projects/packages/search/CHANGELOG.md index 5abeb81da6c73..db115f51ccf70 100644 --- a/projects/packages/search/CHANGELOG.md +++ b/projects/packages/search/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.4] - 2022-01-31 +### Fixed +- Search: Fetch plan info as blog, not as user, to allow nonconnected admins to use dashboard + ## [0.5.3] - 2022-01-27 ### Fixed - Search package: fixed compatibility issue with plan activation @@ -74,6 +78,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated package dependencies. - Update PHPUnit configs to include just what needs coverage rather than include everything then try to exclude stuff that doesn't. +[0.5.4]: https://github.com/Automattic/jetpack-search/compare/v0.5.3...v0.5.4 [0.5.3]: https://github.com/Automattic/jetpack-search/compare/v0.5.2...v0.5.3 [0.5.2]: https://github.com/Automattic/jetpack-search/compare/v0.5.1...v0.5.2 [0.5.1]: https://github.com/Automattic/jetpack-search/compare/v0.5.0...v0.5.1 diff --git a/projects/packages/search/changelog/update-my-jetpack-styles b/projects/packages/search/changelog/update-my-jetpack-styles new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/packages/search/changelog/update-my-jetpack-styles @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/packages/search/package.json b/projects/packages/search/package.json index ccc7c97f541fa..84da17e7b344e 100644 --- a/projects/packages/search/package.json +++ b/projects/packages/search/package.json @@ -40,7 +40,7 @@ "@automattic/color-studio": "2.5.0", "@automattic/jetpack-analytics": "workspace:^0.1.7", "@automattic/jetpack-api": "workspace:^0.8.3", - "@automattic/jetpack-components": "workspace:^0.10.3", + "@automattic/jetpack-components": "workspace:^0.10.4-alpha", "@wordpress/base-styles": "4.0.4", "@wordpress/block-editor": "8.0.12", "@wordpress/data": "6.1.5", diff --git a/projects/packages/search/src/class-plan.php b/projects/packages/search/src/class-plan.php index b02ad89a54baa..4748653058879 100644 --- a/projects/packages/search/src/class-plan.php +++ b/projects/packages/search/src/class-plan.php @@ -43,9 +43,12 @@ public function init_hooks() { */ public function get_plan_info_from_wpcom() { $blog_id = Jetpack_Options::get_option( 'id' ); - $response = Client::wpcom_json_api_request_as_user( + $response = Client::wpcom_json_api_request_as_blog( '/sites/' . $blog_id . '/jetpack-search/plan', - '2' + '2', + array(), + null, + 'wpcom' ); // store plan in options. diff --git a/projects/plugins/backup/changelog/fix-my-jetpack-tests b/projects/plugins/backup/changelog/fix-my-jetpack-tests new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/backup/changelog/fix-my-jetpack-tests @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/backup/changelog/update-my-jetpack-styles b/projects/plugins/backup/changelog/update-my-jetpack-styles new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/plugins/backup/changelog/update-my-jetpack-styles @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/backup/composer.lock b/projects/plugins/backup/composer.lock index 0c5532321a6f6..ddddce434fe4b 100644 --- a/projects/plugins/backup/composer.lock +++ b/projects/plugins/backup/composer.lock @@ -777,7 +777,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "56f7774f2d6193d2a1691e8b59dfe20fe5a2459c" + "reference": "5ca3f982d2e026a0b72c4965a349812f9e321677" }, "require": { "automattic/jetpack-admin-ui": "^0.2", @@ -789,6 +789,7 @@ }, "require-dev": { "automattic/jetpack-changelogger": "^3.0", + "automattic/jetpack-options": "^1.14", "automattic/wordbless": "@dev", "yoast/phpunit-polyfills": "1.0.3" }, diff --git a/projects/plugins/backup/package.json b/projects/plugins/backup/package.json index 35ce50142170c..145c61854f6c5 100644 --- a/projects/plugins/backup/package.json +++ b/projects/plugins/backup/package.json @@ -25,7 +25,7 @@ ], "dependencies": { "@automattic/jetpack-api": "workspace:^0.8.3", - "@automattic/jetpack-components": "workspace:^0.10.3", + "@automattic/jetpack-components": "workspace:^0.10.4-alpha", "@automattic/jetpack-connection": "workspace:^0.15.0-alpha", "@wordpress/api-fetch": "5.2.6", "@wordpress/data": "6.1.5", diff --git a/projects/plugins/backup/src/js/components/Admin.js b/projects/plugins/backup/src/js/components/Admin.js index 2724a00d0c7dd..1398091f32028 100644 --- a/projects/plugins/backup/src/js/components/Admin.js +++ b/projects/plugins/backup/src/js/components/Admin.js @@ -9,7 +9,7 @@ import { AdminPage, AdminSection, AdminSectionHero, - Row, + Container, Col, getRedirectUrl, PricingCard, @@ -84,7 +84,7 @@ const Admin = () => { 'jetpack-backup' ); return ( - +

{ __( 'Secure your site with a Backup subscription.', 'jetpack-backup' ) }

@@ -115,7 +115,7 @@ const Admin = () => { title={ __( 'Jetpack Backup', 'jetpack-backup' ) } /> - + ); }; @@ -130,11 +130,11 @@ const Admin = () => { } return ( - + { renderConnectScreen() } - + ); } @@ -154,11 +154,11 @@ const Admin = () => { // Render an error state, this shouldn't occurr since we've passed userConnected checks if ( capabilitiesError ) { return ( - + { capabilitiesError } - + ); } @@ -168,7 +168,7 @@ const Admin = () => { // Renders additional segments under the jp-hero area condition on having a backup plan const renderBackupSegments = () => { return ( - +

{ __( 'Your cloud backups', 'jetpack-backup' ) }

@@ -218,7 +218,7 @@ const Admin = () => { { renderConnectionStatusCard() } - + ); }; diff --git a/projects/plugins/boost/.phpcs.dir.xml b/projects/plugins/boost/.phpcs.dir.xml index de9dcf15a11a6..0d50ca55e7c99 100644 --- a/projects/plugins/boost/.phpcs.dir.xml +++ b/projects/plugins/boost/.phpcs.dir.xml @@ -21,4 +21,38 @@ + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + diff --git a/projects/plugins/boost/app/admin/class-admin.php b/projects/plugins/boost/app/admin/class-admin.php index 014fc768c3b77..4cfe6eb0e9b69 100644 --- a/projects/plugins/boost/app/admin/class-admin.php +++ b/projects/plugins/boost/app/admin/class-admin.php @@ -10,14 +10,14 @@ use Automattic\Jetpack\Admin_UI\Admin_Menu; use Automattic\Jetpack\Status; +use Automattic\Jetpack_Boost\Features\Optimizations\Critical_CSS\Critical_CSS; +use Automattic\Jetpack_Boost\Features\Optimizations\Optimizations; +use Automattic\Jetpack_Boost\Features\Speed_Score\Speed_Score; use Automattic\Jetpack_Boost\Jetpack_Boost; use Automattic\Jetpack_Boost\Lib\Analytics; use Automattic\Jetpack_Boost\Lib\Environment_Change_Detector; -use Automattic\Jetpack_Boost\Lib\Speed_Score; +use Automattic\Jetpack_Boost\REST_API\Permissions\Nonce; -/** - * Class Admin - */ class Admin { /** @@ -45,7 +45,7 @@ class Admin { * * @var Jetpack_Boost Plugin. */ - private $jetpack_boost; + private $modules; /** * Speed_Score class instance. @@ -54,21 +54,13 @@ class Admin { */ private $speed_score; - /** - * Initialize the class and set its properties. - * - * @param Jetpack_Boost $jetpack_boost Main plugin instance. - * - * @since 1.0.0 - */ - public function __construct( Jetpack_Boost $jetpack_boost ) { - $this->jetpack_boost = $jetpack_boost; - $this->speed_score = new Speed_Score( $jetpack_boost ); + public function __construct( Optimizations $modules ) { + $this->modules = $modules; + $this->speed_score = new Speed_Score( $modules ); Environment_Change_Detector::init(); add_action( 'init', array( new Analytics(), 'init' ) ); add_filter( 'plugin_action_links_' . JETPACK_BOOST_PLUGIN_BASE, array( $this, 'plugin_page_settings_link' ) ); - add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) ); add_action( 'admin_notices', array( $this, 'show_notices' ) ); add_action( 'wp_ajax_set_show_rating_prompt', array( $this, 'handle_set_show_rating_prompt' ) ); add_filter( 'jetpack_boost_js_constants', array( $this, 'add_js_constants' ) ); @@ -102,7 +94,7 @@ public function enqueue_styles() { $internal_path = apply_filters( 'jetpack_boost_asset_internal_path', 'app/assets/dist/' ); wp_enqueue_style( - $this->jetpack_boost->get_plugin_name() . '-css', + 'jetpack-boost-css', plugins_url( $internal_path . 'jetpack-boost.css', JETPACK_BOOST_PATH ), array( 'wp-components' ), JETPACK_BOOST_VERSION @@ -117,7 +109,7 @@ public function enqueue_styles() { public function enqueue_scripts() { $internal_path = apply_filters( 'jetpack_boost_asset_internal_path', 'app/assets/dist/' ); - $admin_js_handle = $this->jetpack_boost->get_plugin_name() . '-admin'; + $admin_js_handle = 'jetpack-boost-admin'; wp_register_script( $admin_js_handle, @@ -127,6 +119,7 @@ public function enqueue_scripts() { true ); + $optimizations = ( new Optimizations() )->get_status(); // Prepare configuration constants for JavaScript. $constants = array( 'version' => JETPACK_BOOST_VERSION, @@ -134,8 +127,7 @@ public function enqueue_scripts() { 'namespace' => JETPACK_BOOST_REST_NAMESPACE, 'prefix' => JETPACK_BOOST_REST_PREFIX, ), - 'modules' => $this->jetpack_boost->get_available_modules(), - 'config' => $this->jetpack_boost->config()->get_data(), + 'optimizations' => $optimizations, 'locale' => get_locale(), 'site' => array( 'url' => get_home_url(), @@ -146,6 +138,14 @@ public function enqueue_scripts() { 'preferences' => array( 'showRatingPrompt' => $this->get_show_rating_prompt(), ), + + /** + * A bit of necessary magic, + * Explained more in the Nonce class. + * + * Nonces are automatically generated when registering routes. + */ + 'nonces' => Nonce::get_generated_nonces(), ); // Give each module an opportunity to define extra constants. @@ -179,7 +179,7 @@ public function plugin_page_settings_link( $links ) { */ public function render_settings() { wp_localize_script( - $this->jetpack_boost->get_plugin_name() . '-admin', + 'jetpack-boost-admin', 'wpApiSettings', array( 'root' => esc_url_raw( rest_url() ), @@ -200,49 +200,6 @@ public function check_for_permissions() { return current_user_can( 'manage_options' ); } - /** - * Register REST routes for settings. - * - * @return void - */ - public function register_rest_routes() { - // Activate and deactivate a module. - register_rest_route( - JETPACK_BOOST_REST_NAMESPACE, - JETPACK_BOOST_REST_PREFIX . '/module/(?P[a-z\-]+)/status', - array( - 'methods' => \WP_REST_Server::EDITABLE, - 'callback' => array( $this, 'set_module_status' ), - 'permission_callback' => array( $this, 'check_for_permissions' ), - ) - ); - } - - /** - * Handler for the /module/(?P[a-z\-]+)/status endpoint. - * - * @param \WP_REST_Request $request The request object. - * - * @return \WP_REST_Response|\WP_Error The response. - */ - public function set_module_status( $request ) { - $params = $request->get_json_params(); - - if ( ! isset( $params['status'] ) ) { - return new \WP_Error( - 'jetpack_boost_error_missing_module_status_param', - __( 'Missing status param', 'jetpack-boost' ) - ); - } - - $module_slug = $request['slug']; - $this->jetpack_boost->set_module_status( (bool) $params['status'], $module_slug ); - - return rest_ensure_response( - $this->jetpack_boost->get_module_status( $module_slug ) - ); - } - /** * Show any admin notices from enabled modules. */ @@ -250,7 +207,7 @@ public function show_notices() { // Determine if we're already on the settings page. // phpcs:ignore WordPress.Security.NonceVerification.Recommended $on_settings_page = isset( $_GET['page'] ) && self::MENU_SLUG === $_GET['page']; - $notices = $this->jetpack_boost->get_admin_notices(); + $notices = $this->get_admin_notices(); // Filter out any that have been dismissed, unless newer than the dismissal. $dismissed_notices = \get_option( self::DISMISSED_NOTICE_OPTION, array() ); @@ -281,7 +238,7 @@ function ( $notice ) use ( $dismissed_notices ) { * @return array List of notice ids. */ private function get_shown_admin_notice_ids() { - $notices = $this->jetpack_boost->get_admin_notices(); + $notices = $this->get_admin_notices(); $ids = array(); foreach ( $notices as $notice ) { $ids[] = $notice->get_id(); @@ -290,6 +247,18 @@ private function get_shown_admin_notice_ids() { return $ids; } + /** + * Returns a list of admin notices to show. Asks each module to provide admin notices the user needs to see. + * + * @TODO: This is still a code smell. We're carrying the whole modules instance just to get a list of admin notices. + * + * @return \Automattic\Jetpack_Boost\Admin\Admin_Notice[] + */ + public function get_admin_notices() { + $critical_css = new Critical_CSS(); + return $critical_css->get_admin_notices(); + } + /** * Check for a GET parameter used to dismiss an admin notice. * diff --git a/projects/plugins/boost/app/assets/src/js/global.d.ts b/projects/plugins/boost/app/assets/src/js/global.d.ts index 16d75ef98e167..b341177e5a5a8 100644 --- a/projects/plugins/boost/app/assets/src/js/global.d.ts +++ b/projects/plugins/boost/app/assets/src/js/global.d.ts @@ -12,7 +12,7 @@ import type { BrowserInterfaceIframe, generateCriticalCSS } from 'jetpack-boost- */ import type { ConnectionStatus } from './stores/connection'; import type { CriticalCssStatus } from './stores/critical-css-status'; -import type { ModulesState } from './stores/modules'; +import type { Optimizations } from './stores/modules'; declare global { const wpApiSettings: { @@ -35,15 +35,17 @@ declare global { connection: ConnectionStatus; criticalCssStatus?: CriticalCssStatus; showRatingPromptNonce?: string; - criticalCssDismissRecommendationsNonce?: string; criticalCssDismissedRecommendations: string[]; site: { url: string; online: boolean; assetPath: string; }; - config: ModulesState; + optimizations: Optimizations; shownAdminNoticeIds: string[]; + nonces: { + [ key: string ]: string; + }; }; // Critical CSS Generator library. diff --git a/projects/plugins/boost/app/assets/src/js/stores/critical-css-recommendations.ts b/projects/plugins/boost/app/assets/src/js/stores/critical-css-recommendations.ts index 4f80206670bc4..b6c98fe8d5aac 100644 --- a/projects/plugins/boost/app/assets/src/js/stores/critical-css-recommendations.ts +++ b/projects/plugins/boost/app/assets/src/js/stores/critical-css-recommendations.ts @@ -6,11 +6,11 @@ import { writable, derived } from 'svelte/store'; /** * Internal dependencies */ +import api from '../api/api'; import { CriticalCssErrorDetails, criticalCssStatus } from './critical-css-status'; import type { JSONObject } from '../utils/json-types'; import { objectFilter } from '../utils/object-filter'; import { sortByFrequency } from '../utils/sort-by-frequency'; -import { makeAdminAjaxRequest } from '../utils/make-admin-ajax-request'; import { castToString } from '../utils/cast-to-string'; const importantProviders = [ @@ -118,10 +118,9 @@ export function setDismissalError( title: string, error: JSONObject ): void { * @param {string} key Key of recommendation to dismiss. */ export async function dismissRecommendation( key: string ): Promise< void > { - await makeAdminAjaxRequest( { - action: 'dismiss_recommendations', + await api.post( '/recommendations/dismiss', { providerKey: key, - nonce: Jetpack_Boost.criticalCssDismissRecommendationsNonce, + nonce: Jetpack_Boost.nonces[ 'recommendations/dismiss' ], } ); dismissed.update( keys => [ ...keys, key ] ); } @@ -130,9 +129,8 @@ export async function dismissRecommendation( key: string ): Promise< void > { * Clear all the dismissed recommendations. */ export async function clearDismissedRecommendations(): Promise< void > { - await makeAdminAjaxRequest( { - action: 'reset_dismissed_recommendations', - nonce: Jetpack_Boost.criticalCssDismissRecommendationsNonce, + await api.post( '/recommendations/reset', { + nonce: Jetpack_Boost.nonces[ 'recommendations/reset' ], } ); dismissed.set( [] ); } diff --git a/projects/plugins/boost/app/assets/src/js/stores/modules.ts b/projects/plugins/boost/app/assets/src/js/stores/modules.ts index f9accfb9b2544..668f0771c61e6 100644 --- a/projects/plugins/boost/app/assets/src/js/stores/modules.ts +++ b/projects/plugins/boost/app/assets/src/js/stores/modules.ts @@ -9,6 +9,10 @@ import { writable } from 'svelte/store'; import config from './config'; import { setModuleState } from '../api/modules'; +export type Optimizations = { + [ slug: string ]: boolean; +}; + export type ModulesState = { [ slug: string ]: { enabled: boolean; @@ -16,7 +20,13 @@ export type ModulesState = { }; }; -const initialState = config.config; +const initialState = {}; +for ( const [ name, value ] of Object.entries( config.optimizations ) ) { + initialState[ name ] = { + enabled: value, + }; +} + const { subscribe, update } = writable< ModulesState >( initialState ); // Keep a subscribed copy for quick reading. diff --git a/projects/plugins/boost/app/assets/src/js/utils/generate-critical-css.ts b/projects/plugins/boost/app/assets/src/js/utils/generate-critical-css.ts index 9e931710023bb..a76d34c2321d6 100644 --- a/projects/plugins/boost/app/assets/src/js/utils/generate-critical-css.ts +++ b/projects/plugins/boost/app/assets/src/js/utils/generate-critical-css.ts @@ -76,12 +76,12 @@ export default async function generateCriticalCss( hasGenerateRun = true; let cancelling = false; - if ( reset ) { - await clearDismissedRecommendations(); - updateGenerateStatus( true, 0 ); - } - try { + if ( reset ) { + await clearDismissedRecommendations(); + updateGenerateStatus( true, 0 ); + } + // Fetch a list of provider keys and URLs while loading the Critical CSS lib. const cssStatus = await requestGeneration( reset, isShowstopperRetry ); diff --git a/projects/plugins/boost/app/class-jetpack-boost.php b/projects/plugins/boost/app/class-jetpack-boost.php index cbef9b8fcd3f3..02ac717a4041c 100644 --- a/projects/plugins/boost/app/class-jetpack-boost.php +++ b/projects/plugins/boost/app/class-jetpack-boost.php @@ -12,19 +12,17 @@ namespace Automattic\Jetpack_Boost; -use Automattic\Jetpack\Config as Jetpack_Config; use Automattic\Jetpack_Boost\Admin\Admin; +use Automattic\Jetpack_Boost\Features\Optimizations\Critical_CSS\Critical_CSS; +use Automattic\Jetpack_Boost\Features\Optimizations\Critical_CSS\Critical_CSS_Storage; +use Automattic\Jetpack_Boost\Features\Optimizations\Critical_CSS\Regenerate_Admin_Notice; +use Automattic\Jetpack_Boost\Features\Optimizations\Optimizations; use Automattic\Jetpack_Boost\Lib\Analytics; use Automattic\Jetpack_Boost\Lib\CLI; -use Automattic\Jetpack_Boost\Lib\Config; use Automattic\Jetpack_Boost\Lib\Connection; -use Automattic\Jetpack_Boost\Lib\Speed_Score_History; -use Automattic\Jetpack_Boost\Lib\Viewport; -use Automattic\Jetpack_Boost\Modules\Critical_CSS\Critical_CSS; -use Automattic\Jetpack_Boost\Modules\Critical_CSS\Regenerate_Admin_Notice; -use Automattic\Jetpack_Boost\Modules\Lazy_Images\Lazy_Images; -use Automattic\Jetpack_Boost\Modules\Module; -use Automattic\Jetpack_Boost\Modules\Render_Blocking_JS\Render_Blocking_JS; +use Automattic\Jetpack_Boost\Lib\Setup; +use Automattic\Jetpack_Boost\REST_API\Endpoints\Optimization_Status; +use Automattic\Jetpack_Boost\REST_API\REST_API; /** * The core plugin class. @@ -40,28 +38,6 @@ */ class Jetpack_Boost { - const MODULES = array( - Critical_CSS::MODULE_SLUG => Critical_CSS::class, - Lazy_Images::MODULE_SLUG => Lazy_Images::class, - Render_Blocking_JS::MODULE_SLUG => Render_Blocking_JS::class, - ); - - /** - * Default enabled modules. - */ - const ENABLED_MODULES_DEFAULT = array(); - - /** - * Default available modules. - */ - const AVAILABLE_MODULES_DEFAULT = array( - Critical_CSS::MODULE_SLUG, - Render_Blocking_JS::MODULE_SLUG, - Lazy_Images::MODULE_SLUG, - ); - - const CURRENT_CONFIG_ID = 'default'; - /** * The unique identifier of this plugin. * @@ -78,21 +54,6 @@ class Jetpack_Boost { */ private $version; - /** - * The config - * - * @since 1.0.0 - * @var Config|null $config The configuration object - */ - private $config; - - /** - * Store all plugin module instances here - * - * @var array - */ - private $modules = array(); - /** * The Jetpack Boost Connection manager instance. * @@ -127,20 +88,12 @@ public function __construct() { \WP_CLI::add_command( 'jetpack-boost', $cli_instance ); } - // Initialize the config module separately. - $this->init_config(); - - $this->prepare_modules(); + $optimizations = new Optimizations(); + Setup::add( $optimizations ); // Initialize the Admin experience. - $this->init_admin(); - - // Module readiness filter. - add_action( 'wp_head', array( $this, 'display_meta_field_module_ready' ) ); - - add_action( 'init', array( $this, 'initialize_modules' ) ); + $this->init_admin( $optimizations ); add_action( 'init', array( $this, 'init_textdomain' ) ); - add_action( 'init', array( $this, 'register_cache_clear_actions' ) ); add_action( 'handle_theme_change', array( $this, 'handle_theme_change' ) ); @@ -156,307 +109,23 @@ private function register_deactivation_hook() { register_deactivation_hook( $plugin_file, array( $this, 'deactivate' ) ); } - /** - * Wipe all cached values. - */ - public function clear_cache() { - do_action( 'jetpack_boost_clear_cache' ); - } - /** * Plugin deactivation handler. Clear cache, and reset admin notices. */ public function deactivate() { do_action( 'jetpack_boost_deactivate' ); - - $this->clear_cache(); - Admin::clear_dismissed_notices(); - } - - /** - * Plugin uninstallation handler. Delete all settings and cache. - */ - public function uninstall() { - do_action( 'jetpack_boost_uninstall' ); - - Speed_Score_History::clear_all(); - $this->clear_cache(); - delete_option( apply_filters( 'jetpack_boost_options_store_key_name', 'jetpack_boost_config' ) ); - } - - /** - * Handlers for clearing module caches go here, so that caches get cleared even if the module is not enabled. - */ - public function register_cache_clear_actions() { - add_action( 'jetpack_boost_clear_cache', array( $this, 'record_clear_cache_event' ) ); - } - - /** - * Record the clear cache event. - */ - public function record_clear_cache_event() { + do_action( 'jetpack_boost_clear_cache' ); Analytics::record_user_event( 'clear_cache' ); - } - - /** - * Initialize modules. - * - * Note: this method ignores the nonce verification linter rule, as jb-disable-modules is intended to work - * without a nonce. - * - * phpcs:disable WordPress.Security.NonceVerification.Recommended - */ - public function prepare_modules() { - $available_modules = $this->get_available_modules(); - - $forced_disabled_modules = array(); - - // Get the lists of modules explicitly disabled from the 'jb-disable-modules' query string. - // The parameter is a comma separated value list of module slug. - if ( ! empty( $_GET['jb-disable-modules'] ) ) { - // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash - // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - $forced_disabled_modules = array_map( 'sanitize_key', explode( ',', $_GET['jb-disable-modules'] ) ); - } - - foreach ( self::MODULES as $module_slug => $module_class ) { - // Don't register modules that have been forcibly disabled from the url 'jb-disable-modules' query string parameter. - if ( in_array( $module_slug, $forced_disabled_modules, true ) || in_array( 'all', $forced_disabled_modules, true ) ) { - continue; - } - - // All Jetpack Boost modules should extend Module class. - if ( ! is_subclass_of( $module_class, Module::class ) ) { - continue; - } - - // Don't register modules that aren't available. - if ( ! in_array( $module_slug, $available_modules, true ) ) { - continue; - } - - $module = new $module_class(); - $this->modules[ $module_slug ] = $module; - } - - do_action( 'jetpack_boost_modules_loaded' ); - } - - /** - * Initialize modules when WordPress is ready - */ - public function initialize_modules() { - foreach ( $this->modules as $module_slug => $module ) { - if ( true === $this->get_module_status( $module_slug ) ) { - $module->initialize(); - } - } - } - - /** - * Returns the list of available modules. - * - * @return array The available modules. - */ - public function get_available_modules() { - $available_modules = self::AVAILABLE_MODULES_DEFAULT; - - // Add the Lazy Images module if Jetpack Lazy Images module is enabled. - if ( Lazy_Images::is_jetpack_lazy_images_module_enabled() ) { - $available_modules = array_unique( array_merge( self::AVAILABLE_MODULES_DEFAULT, array( Lazy_Images::MODULE_SLUG ) ) ); - } - - return apply_filters( - 'jetpack_boost_modules', - $available_modules - ); - } - - /** - * Returns an array of active modules. - */ - public function get_active_modules() { - // Cache active modules. - static $active_modules = null; - if ( null !== $active_modules ) { - return $active_modules; - } - - return array_filter( - $this->modules, - function ( $module, $module_slug ) { - return true === $this->get_module_status( $module_slug ); - }, - ARRAY_FILTER_USE_BOTH - ); - } - - /** - * Returns the status of a given module. - * - * @param string $module_slug The module's slug. - * - * @return bool The enablement status of the module. - */ - public function get_module_status( $module_slug ) { - $default_module_status = in_array( $module_slug, self::ENABLED_MODULES_DEFAULT, true ); - - return apply_filters( 'jetpack_boost_module_enabled', $default_module_status, $module_slug ); - } - - /** - * Check if a module is enabled. - * - * @param boolean $is_enabled Default value. - * @param string $module_slug The module we are checking. - * - * @return mixed|null - */ - public function is_module_enabled( $is_enabled, $module_slug ) { - do_action( 'jetpack_boost_pre_is_module_enabled', $is_enabled, $module_slug ); - - return $this->config()->get_value( "$module_slug/enabled", $is_enabled ); - } - - /** - * Set status of a module. - * - * @param boolean $is_enabled Default value. - * @param string $module_slug The module we are checking. - */ - public function set_module_status( $is_enabled, $module_slug ) { - do_action( 'jetpack_boost_pre_set_module_status', $is_enabled, $module_slug ); - Analytics::record_user_event( - 'set_module_status', - array( - 'module' => $module_slug, - 'status' => $is_enabled, - ) - ); - $this->config()->set_value( "$module_slug/enabled", $is_enabled, true ); - } - - /** - * Get critical CSS viewport sizes. - * - * @param mixed $default The default value. - * - * @return mixed|null - */ - public function get_critical_css_viewport_sizes( $default ) { - return $this->config()->get_value( 'critical-css/settings/viewport_sizes', $default ); - } - - /** - * Get critical CSS default viewports. - * - * @param mixed $default The default value. - * - * @return mixed|null - */ - public function get_critical_css_default_viewports( $default ) { - return $this->config()->get_value( 'critical-css/settings/default_viewports', $default ); - } - - /** - * Get critical CSS ignore rules. - * - * @param mixed $default The default value. - * - * @return mixed|null - */ - public function get_critical_css_ignore_rules( $default ) { - return $this->config()->get_value( 'critical-css/settings/css-ignore-rules', $default ); - } - - /** - * Returns configuration array. - * - * @return Config Configuration array. - */ - public function config() { - if ( ! $this->config ) { - do_action( 'jetpack_boost_pre_get_config' ); - $this->config = Config::get( self::CURRENT_CONFIG_ID ); // under the hood, this actually fetches from an option, not the config cache. - } - - return apply_filters( 'jetpack_boost_config', $this->config ); - } - - /** - * Initialize config system. - * - * @todo This should be replaced by a proper configuration implementation eventually. - */ - public function init_config() { - add_action( 'switch_blog', array( $this, 'clear_memoized_config' ) ); - add_filter( 'jetpack_boost_module_enabled', array( $this, 'is_module_enabled' ), 0, 2 ); - add_filter( 'jetpack_boost_critical_css_viewport_sizes', array( $this, 'get_critical_css_viewport_sizes' ) ); - add_filter( 'jetpack_boost_critical_css_default_viewports', array( $this, 'get_critical_css_default_viewports' ) ); - add_filter( 'jetpack_boost_critical_css_ignore_rules', array( $this, 'get_critical_css_ignore_rules' ) ); - } - - /** - * Clear the memoized config, executed on `switch_blog` - */ - public function clear_memoized_config() { - $this->config = null; - } - - /** - * Returns a default config array. - * - * @return array Default config. - */ - public static function get_default_config_array() { - return apply_filters( - 'jetpack_boost_config_array', - array( - Render_Blocking_JS::MODULE_SLUG => array( - 'enabled' => false, - ), - Critical_CSS::MODULE_SLUG => array( - 'enabled' => false, - 'settings' => array( - 'viewport_sizes' => Viewport::DEFAULT_VIEWPORT_SIZES, - 'default_viewports' => Viewport::DEFAULT_VIEWPORTS, - 'css-ignore-rules' => array( - // TODO: Define if we need any default CSS ignore rules - // Example regex, exclude all css where there is a url inside. - 'url\(', - ), - ), - ), - Lazy_Images::MODULE_SLUG => array( - 'enabled' => false, - ), - 'show_rating_prompt' => true, - ) - ); + Admin::clear_dismissed_notices(); } /** * Initialize the admin experience. */ - public function init_admin() { - if ( ! apply_filters( 'jetpack_boost_connection_bypass', false ) ) { - $jetpack_config = new Jetpack_Config(); - $jetpack_config->ensure( - 'connection', - array( - 'slug' => 'jetpack-boost', - 'name' => 'Jetpack Boost', - 'url_info' => '', // Optional, URL of the plugin. - ) - ); - } - - /** - * The class responsible for defining all actions that occur in the admin area. - */ - require_once plugin_dir_path( __FILE__ ) . 'admin/class-admin.php'; - - new Admin( $this ); + public function init_admin( $modules ) { + REST_API::register( Optimization_Status::class ); + $this->connection->ensure_connection(); + new Admin( $modules ); } /** @@ -470,15 +139,6 @@ public function init_textdomain() { ); } - /** - * Registers the `jetpack_boost_url_ready` filter which allows modules to provide their readiness status. - */ - public function display_meta_field_module_ready() { - ?> - - version; } - /** - * Returns a list of admin notices to show. Asks each module to provide admin notices the user needs to see. - * - * @return \Automattic\Jetpack_Boost\Admin\Admin_Notice[] - */ - public function get_admin_notices() { - $all_notices = array(); - - foreach ( $this->get_active_modules() as $module ) { - $module_notices = $module->get_admin_notices(); - - if ( ! empty( $module_notices ) ) { - $all_notices = array_merge( $all_notices, $module_notices ); - } - } - - return $all_notices; - } - /** * Handle an environment change to set the correct status to the Critical CSS request. * This is done here so even if the Critical CSS module is switched off we can @@ -528,4 +169,28 @@ public function handle_theme_change() { Admin::clear_dismissed_notice( Regenerate_Admin_Notice::SLUG ); \update_option( Critical_CSS::RESET_REASON_STORAGE_KEY, Regenerate_Admin_Notice::REASON_THEME_CHANGE, false ); } + + /** + * Plugin uninstallation handler. Delete all settings and cache. + */ + public function uninstall() { + + global $wpdb; + + // When uninstalling, make sure all deactivation cleanups have run as well. + $this->deactivate(); + + // Delete all Jetpack Boost options. + $wpdb->query( + " + DELETE + FROM `$wpdb->options` + WHERE `option_name` LIKE jetpack_boost_% + " + ); + + // Delete stored Critical CSS. + ( new Critical_CSS_Storage() )->clear(); + + } } diff --git a/projects/plugins/boost/app/contracts/Feature.php b/projects/plugins/boost/app/contracts/Feature.php new file mode 100644 index 0000000000000..33edf2c86b622 --- /dev/null +++ b/projects/plugins/boost/app/contracts/Feature.php @@ -0,0 +1,11 @@ +feature = $feature; + $this->status = new Status( $feature->get_slug() ); + } +} diff --git a/projects/plugins/boost/app/features/optimizations/Optimizations.php b/projects/plugins/boost/app/features/optimizations/Optimizations.php new file mode 100644 index 0000000000000..a933dbeb54edd --- /dev/null +++ b/projects/plugins/boost/app/features/optimizations/Optimizations.php @@ -0,0 +1,127 @@ +get_slug(); + $this->features[ $slug ] = new Optimization( $feature ); + } + } + + public function available_modules() { + $forced_disabled_modules = $this->get_disabled_modules(); + + if ( empty( $forced_disabled_modules ) ) { + return $this->features; + } + + if ( array( 'all' ) === $forced_disabled_modules ) { + return array(); + } + + $available_modules = array(); + foreach ( $this->features as $slug => $feature ) { + if ( ! in_array( $slug, $forced_disabled_modules, true ) ) { + $available_modules[ $slug ] = $feature; + } + } + + return $available_modules; + } + + public function have_enabled_modules() { + return count( $this->get_status() ) > 0; + } + + public function get_status() { + $status = array(); + foreach ( $this->features as $slug => $optimization ) { + $status[ $slug ] = $optimization->status->is_enabled(); + } + return $status; + } + + public function register_endpoints( $feature ) { + if ( ! $feature instanceof Has_Endpoints ) { + return false; + } + + if ( empty( $feature->get_endpoints() ) ) { + return false; + } + + } + + /** + * @inheritDoc + */ + public function setup() { + + foreach ( $this->available_modules() as $slug => $optimization ) { + + if ( ! $optimization->status->is_enabled() ) { + continue; + } + + $optimization->feature->setup(); + $this->register_endpoints( $optimization->feature ); + + do_action( "jetpack_boost_{$slug}_initialized", $this ); + + } + } + + /** + * Get the lists of modules explicitly disabled from the 'jb-disable-modules' query string. + * The parameter is a comma separated value list of module slug. + * + * @return array + */ + + public function get_disabled_modules() { + // phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( ! empty( $_GET['jb-disable-modules'] ) ) { + // phpcs:disable WordPress.Security.NonceVerification.Recommended + // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash + // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + return array_map( 'sanitize_key', explode( ',', $_GET['jb-disable-modules'] ) ); + } + + return array(); + } + + /** + * @inheritDoc + */ + public function setup_trigger() { + return 'plugins_loaded'; + } + +} diff --git a/projects/plugins/boost/app/modules/critical-css/class-admin-bar-css-compat.php b/projects/plugins/boost/app/features/optimizations/critical-css/Admin_Bar_Compatibilty.php similarity index 75% rename from projects/plugins/boost/app/modules/critical-css/class-admin-bar-css-compat.php rename to projects/plugins/boost/app/features/optimizations/critical-css/Admin_Bar_Compatibilty.php index 5d7924ac6bf13..a2f18f83e04c0 100644 --- a/projects/plugins/boost/app/modules/critical-css/class-admin-bar-css-compat.php +++ b/projects/plugins/boost/app/features/optimizations/critical-css/Admin_Bar_Compatibilty.php @@ -1,24 +1,11 @@ storage = new Critical_CSS_Storage(); + $this->paths = new Source_Providers(); + + } + + /** + * This is only run if Critical CSS module has been activated. + */ + public function setup() { + // Touch to setup the post type. This is a temporary hack. + // This should instantiate a new Post_Type_Storage class, + // so that Critical_CSS class is responsible + // for setting up the storage. + $recommendations = new Recommendations(); + $recommendations->attach_hooks(); + + add_action( 'wp', array( $this, 'display_critical_css' ) ); + + if ( Generator::is_generating_critical_css() ) { + add_action( 'wp_head', array( $this, 'display_generate_meta' ), 0 ); + $this->force_logged_out_render(); + } + + add_action( 'handle_theme_change', array( $this, 'clear_critical_css' ) ); + add_action( 'jetpack_boost_clear_cache', array( $this, 'clear_critical_css' ) ); + add_filter( 'jetpack_boost_js_constants', array( $this, 'add_critical_css_constants' ) ); + + REST_API::register( $this->get_endpoints() ); + return true; + } + + public function get_slug() { + return 'critical-css'; + } + + /** + * Renders a tag used to verify this is a valid page to generate Critical CSS with. + */ + public function display_generate_meta() { + ?> + + paths->get_current_request_css(); + if ( ! $critical_css ) { + return; + } + + $display = new Display_Critical_CSS( $critical_css ); + add_action( 'wp_head', array( $display, 'display_critical_css' ), 0 ); + add_filter( 'style_loader_tag', array( $display, 'asynchronize_stylesheets' ), 10, 4 ); + add_action( 'wp_footer', array( $display, 'onload_flip_stylesheets' ) ); + } + + /** + * Clear Critical CSS. + */ + public function clear_critical_css() { + // Mass invalidate all cached values. + // ^^ Not true anymore. Mass invalidate __some__ cached values. + $this->storage->clear(); + Critical_CSS_State::reset(); + } + + /** + * Force the current page to render as viewed by a logged out user. Useful when generating + * Critical CSS. + */ + private function force_logged_out_render() { + $current_user_id = get_current_user_id(); + + if ( 0 !== $current_user_id ) { + // Force current user to 0 to ensure page is rendered as a non-logged-in user. + wp_set_current_user( 0 ); + + // Turn off display of admin bar. + add_filter( 'show_admin_bar', '__return_false', PHP_INT_MAX ); + } + } + + /** + * Override; returns an admin notice to show if there was a reset reason. + * + * @TODO: + * There should be an Admin_Notice class + * To create a notice, (new Admin_Notice())->create("notice text"); + * To view notices: (new Admin_Notice())->get_all(); + * @return null|\Automattic\Jetpack_Boost\Admin\Admin_Notice[] + */ + public function get_admin_notices() { + $reason = \get_option( self::RESET_REASON_STORAGE_KEY ); + + if ( ! $reason ) { + return array(); + } + + return array( new Regenerate_Admin_Notice( $reason ) ); + } + + /** + * Clear Critical CSS reset reason option. + * + * @TODO: Admin notices need to be moved elsewhere. + * Note: Looks like we need a way to and options throughout the app. + * This is why it's currently awkwardly using a static method with a constant + * If we could trust classes to use constructors properly - without performing actions + * Then we could easily (and cheaply) instantiate all Boost objects + * and kindly ask them to delete themselves + */ + public static function clear_reset_reason() { + \delete_option( self::RESET_REASON_STORAGE_KEY ); + } + + /** + * Add Critical CSS related constants to be passed to JavaScript only if the module is enabled. + * + * @param array $constants Constants to be passed to JavaScript. + * + * @return array + */ + public function add_critical_css_constants( $constants ) { + // Information about the current status of Critical CSS / generation. + $generator = new Generator(); + $constants['criticalCssStatus'] = $generator->get_local_critical_css_generation_info(); + + return $constants; + } + + /** + * @TODO: Facepalm. PHP Typehinting is broken. + * @return Endpoint[] + * + */ + public function get_endpoints() { + return array( + Generator_Status::class, + Generator_Request::class, + Generator_Success::class, + Recommendations_Dismiss::class, + Recommendations_Reset::class, + Generator_Error::class, + ); + } + + /** + * @inheritDoc + */ + public function setup_trigger() { + return 'init'; + } +} diff --git a/projects/plugins/boost/app/modules/critical-css/class-critical-css-state.php b/projects/plugins/boost/app/features/optimizations/critical-css/Critical_CSS_State.php similarity index 97% rename from projects/plugins/boost/app/modules/critical-css/class-critical-css-state.php rename to projects/plugins/boost/app/features/optimizations/critical-css/Critical_CSS_State.php index 1768bd0227abc..e5867d9887da1 100644 --- a/projects/plugins/boost/app/modules/critical-css/class-critical-css-state.php +++ b/projects/plugins/boost/app/features/optimizations/critical-css/Critical_CSS_State.php @@ -7,10 +7,9 @@ * @package automattic/jetpack-boost */ -namespace Automattic\Jetpack_Boost\Modules\Critical_CSS; +namespace Automattic\Jetpack_Boost\Features\Optimizations\Critical_CSS; use Automattic\Jetpack_Boost\Lib\Transient; -use Automattic\Jetpack_Boost\Modules\Critical_CSS\Providers\Provider; /** * Critical CSS State @@ -203,11 +202,6 @@ public function get_core_providers_status( $keys ) { protected function get_provider_sources( $providers ) { $sources = array(); - /** - * Provider. - * - * @var $provider Provider - */ foreach ( $providers as $provider ) { $provider_name = $provider::get_provider_name(); @@ -382,7 +376,7 @@ public function get_percent_complete() { /** * Reset the Critical CSS state. */ - public function reset() { + public static function reset() { Transient::delete( self::KEY ); } diff --git a/projects/plugins/boost/app/modules/critical-css/class-critical-css-storage.php b/projects/plugins/boost/app/features/optimizations/critical-css/Critical_CSS_Storage.php similarity index 94% rename from projects/plugins/boost/app/modules/critical-css/class-critical-css-storage.php rename to projects/plugins/boost/app/features/optimizations/critical-css/Critical_CSS_Storage.php index 29f233f445fe5..820b560ac3de3 100644 --- a/projects/plugins/boost/app/modules/critical-css/class-critical-css-storage.php +++ b/projects/plugins/boost/app/features/optimizations/critical-css/Critical_CSS_Storage.php @@ -7,7 +7,7 @@ * @package automattic/jetpack-boost */ -namespace Automattic\Jetpack_Boost\Modules\Critical_CSS; +namespace Automattic\Jetpack_Boost\Features\Optimizations\Critical_CSS; use Automattic\Jetpack_Boost\Lib\Storage_Post_Type; diff --git a/projects/plugins/boost/app/features/optimizations/critical-css/Display_Critical_CSS.php b/projects/plugins/boost/app/features/optimizations/critical-css/Display_Critical_CSS.php new file mode 100644 index 0000000000000..35cf33817306c --- /dev/null +++ b/projects/plugins/boost/app/features/optimizations/critical-css/Display_Critical_CSS.php @@ -0,0 +1,149 @@ +css = $css; + } + + /** + * Converts existing screen CSS to be asynchronously loaded. + * + * @param string $html The link tag for the enqueued style. + * @param string $handle The style's registered handle. + * @param string $href The stylesheet's source URL. + * @param string $media The stylesheet's media attribute. + * + * @return string + * @see style_loader_tag + */ + public function asynchronize_stylesheets( + $html, + $handle, + $href, + $media + ) { + // If there is no critical CSS, do not alter the stylesheet loading. + if ( false === $this->css ) { + return $html; + } + + $available_methods = array( + 'async' => 'media="not all" data-media="' . $media . '" onload="this.media=this.dataset.media; delete this.dataset.media; this.removeAttribute( \'onload\' );"', + 'deferred' => 'media="not all" data-media="' . $media . '"', + ); + + /** + * Loading method for stylesheets. + * + * Filter the loading method for each stylesheet for the screen with following values: + * async - Stylesheets are loaded asynchronously. + * Styles are applied once the stylesheet is loaded completely without render blocking. + * deferred - Loading of stylesheets are deferred until the window load event. + * Styles from all the stylesheets are applied at once after the page load. + * + * Stylesheet loading behaviour is not altered for any other value such as false or 'default'. + * Stylesheet loading is instant and the process blocks the page rendering. + * Eg: add_filter( 'jetpack_boost_async_style', '__return_false' ); + * + * @param string $handle The style's registered handle. + * @param string $media The stylesheet's media attribute. + * + * @see onload_flip_stylesheets for how stylesheets loading is deferred. + * + * @todo Retrieve settings from database, either via auto-configuration or UI option. + */ + $method = apply_filters( 'jetpack_boost_async_style', 'async', $handle, $media ); + + // If the loading method is not allowed, do not alter the stylesheet loading. + if ( ! isset( $available_methods[ $method ] ) ) { + return $html; + } + + $html_no_script = ''; + + // Update the stylesheet markup for allowed methods. + $html = preg_replace( '~media=(\'[^\']+\')|("[^"]+")~', $available_methods[ $method ], $html ); + + // Append to the HTML stylesheet tag the same untouched HTML stylesheet tag within the noscript tag + // to support the rendering of the stylesheet when JavaScript is disabled. + return $html_no_script . $html; + } + + /** + * Prints the critical CSS to the page. + */ + public function display_critical_css() { + $critical_css = $this->css; + + if ( false === $critical_css ) { + return false; + } + + echo ' tag (or any HTML tags) in output. + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo wp_strip_all_tags( $critical_css ); + + echo ''; + } + + /** + * Add a small piece of JavaScript to the footer, which on load flips all + * linked stylesheets from media="not all" to "all", and switches the + * Critical CSS tag (or any HTML tags) in output. - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - echo wp_strip_all_tags( $critical_css ); - - echo ''; - } - - /** - * Check if the current URL is warmed up. For this module, "warmed up" means that - * either Critical CSS has been generated for this page, or this page is not - * eligible to have Critical CSS generated for it. - * - * @param bool $ready Injected filter value. - * - * @return bool - */ - public function is_ready_filter( $ready ) { - if ( ! $ready ) { - return $ready; - } - - // If this page has no provider keys, it is ineligible for Critical CSS. - $keys = $this->get_current_request_css_keys(); - if ( count( $keys ) === 0 ) { - return true; - } - - // Return "ready" if Critical CSS has been generated. - return ! empty( $this->get_critical_css() ); - } - - /** - * Force the current page to render as viewed by a logged out user. Useful when generating - * Critical CSS. - */ - private function force_logged_out_render() { - $current_user_id = get_current_user_id(); - - if ( 0 !== $current_user_id ) { - // Force current user to 0 to ensure page is rendered as a non-logged-in user. - wp_set_current_user( 0 ); - - // Turn off display of admin bar. - add_filter( 'show_admin_bar', '__return_false', PHP_INT_MAX ); - } - } - - /** - * AJAX handler to handle proxying of external CSS resources. - */ - public function handle_css_proxy() { - // Verify valid nonce. - if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), self::GENERATE_PROXY_NONCE ) ) { - wp_die( '', 400 ); - } - - // Make sure currently logged in as admin. - if ( ! $this->current_user_can_modify_critical_css() ) { - wp_die( '', 400 ); - } - - // Reject any request made when not generating. - if ( ! $this->state->is_pending() ) { - wp_die( '', 400 ); - } - - // Validate URL and fetch. - $proxy_url = filter_var( wp_unslash( $_POST['proxy_url'] ), FILTER_VALIDATE_URL ); - if ( ! wp_http_validate_url( $proxy_url ) ) { - die( 'Invalid URL' ); - } - - $response = wp_remote_get( $proxy_url ); - if ( is_wp_error( $response ) ) { - // TODO: Nicer error handling. - die( 'error' ); - } - - header( 'Content-type: text/css' ); - - // Outputting proxied CSS contents unescaped. - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - echo wp_strip_all_tags( $response['body'] ); - - die(); - } - - /** - * API helper for ensuring this module is enabled before allowing an API - * endpoint to continue. Will die if this module is not initialized, with - * a status message indicating so. - */ - public function ensure_module_initialized() { - if ( ! $this->is_initialized() ) { - wp_send_json( array( 'status' => 'module-unavailable' ) ); - } - } - - /** - * Add a small piece of JavaScript to the footer, which on load flips all - * linked stylesheets from media="not all" to "all", and switches the - * Critical CSS