- { showHeader &&
}
- { children }
- { showFooter &&
}
+
+ { showHeader && (
+
+
+
+
+
+ ) }
+
+ { children }
+
+ { showFooter && (
+
+
+
+
+
+ ) }
);
};
diff --git a/projects/js-packages/components/components/admin-page/stories/index.jsx b/projects/js-packages/components/components/admin-page/stories/index.jsx
index 3e9ee09d538b7..c57f8b46b15fb 100644
--- a/projects/js-packages/components/components/admin-page/stories/index.jsx
+++ b/projects/js-packages/components/components/admin-page/stories/index.jsx
@@ -5,7 +5,7 @@ import React from 'react';
import AdminPage from '../index.jsx';
export default {
- title: 'Playground/Admin Page',
+ title: 'JS Packages/Components/Admin Page',
component: AdminPage,
};
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..db10c3bd11ad1 100644
--- a/projects/js-packages/components/components/admin-section/stories/index.jsx
+++ b/projects/js-packages/components/components/admin-section/stories/index.jsx
@@ -6,30 +6,30 @@ 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',
+ title: 'JS Packages/Components/Admin Sections',
};
// Export additional stories using pre-defined values
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/automattic-byline-logo/stories/index.jsx b/projects/js-packages/components/components/automattic-byline-logo/stories/index.jsx
index 8e7af04464380..6fbb1b107bda3 100644
--- a/projects/js-packages/components/components/automattic-byline-logo/stories/index.jsx
+++ b/projects/js-packages/components/components/automattic-byline-logo/stories/index.jsx
@@ -11,7 +11,7 @@ import React from 'react';
import AutomatticBylineLogo from '../index.jsx';
export default {
- title: 'Playground/Automattic Byline Logo',
+ title: 'JS Packages/Components/Automattic Byline Logo',
component: AutomatticBylineLogo,
};
diff --git a/projects/js-packages/components/components/decorative-card/stories/index.jsx b/projects/js-packages/components/components/decorative-card/stories/index.jsx
index 6cd2d1a31310f..68f2cf9dfe4d2 100644
--- a/projects/js-packages/components/components/decorative-card/stories/index.jsx
+++ b/projects/js-packages/components/components/decorative-card/stories/index.jsx
@@ -5,7 +5,7 @@ import React from 'react';
import DecorativeCard from '../index.jsx';
export default {
- title: 'Playground/Decorative Card',
+ title: 'JS Packages/Components/Decorative Card',
component: DecorativeCard,
};
diff --git a/projects/js-packages/components/components/jetpack-footer/stories/index.jsx b/projects/js-packages/components/components/jetpack-footer/stories/index.jsx
index b94fbe4946461..7d4ec6664e2bc 100644
--- a/projects/js-packages/components/components/jetpack-footer/stories/index.jsx
+++ b/projects/js-packages/components/components/jetpack-footer/stories/index.jsx
@@ -10,7 +10,7 @@ import React from 'react';
import JetpackFooter from '../index.jsx';
export default {
- title: 'Playground/Jetpack Footer',
+ title: 'JS Packages/Components/Jetpack Footer',
component: JetpackFooter,
};
diff --git a/projects/js-packages/components/components/jetpack-logo/stories/index.jsx b/projects/js-packages/components/components/jetpack-logo/stories/index.jsx
index eb180b04e09d6..142fff399ae1b 100644
--- a/projects/js-packages/components/components/jetpack-logo/stories/index.jsx
+++ b/projects/js-packages/components/components/jetpack-logo/stories/index.jsx
@@ -10,7 +10,7 @@ import React from 'react';
import JetpackLogo from '../index.jsx';
export default {
- title: 'Playground/Jetpack Logo',
+ title: 'JS Packages/Components/Jetpack Logo',
component: JetpackLogo,
argTypes: {
logoColor: { control: 'color' },
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..f01aa78f264ad
--- /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: 'JS Packages/Components/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/components/pricing-card/stories/index.jsx b/projects/js-packages/components/components/pricing-card/stories/index.jsx
index 60a045429b2ed..da72e12e22e21 100644
--- a/projects/js-packages/components/components/pricing-card/stories/index.jsx
+++ b/projects/js-packages/components/components/pricing-card/stories/index.jsx
@@ -11,7 +11,7 @@ import { action } from '@storybook/addon-actions';
import PricingCard from '../index.jsx';
export default {
- title: 'Playground/Pricing Card',
+ title: 'JS Packages/Components/Pricing Card',
component: PricingCard,
// TODO: Storybook Actions are not working. See https://github.com/storybookjs/storybook/issues/7215
argTypes: {
diff --git a/projects/js-packages/components/components/spinner/stories/index.jsx b/projects/js-packages/components/components/spinner/stories/index.jsx
index c8c0460e178a4..b344d5ff69859 100644
--- a/projects/js-packages/components/components/spinner/stories/index.jsx
+++ b/projects/js-packages/components/components/spinner/stories/index.jsx
@@ -10,7 +10,7 @@ import React from 'react';
import Spinner from '../index.jsx';
export default {
- title: 'Playground/Spinner',
+ title: 'JS Packages/Components/Spinner',
component: Spinner,
argTypes: {
color: { control: 'color' },
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 62b79e88405cb..6ab65265c017c 100644
--- a/projects/js-packages/components/package.json
+++ b/projects/js-packages/components/package.json
@@ -1,21 +1,21 @@
{
"name": "@automattic/jetpack-components",
- "version": "0.10.3-alpha",
+ "version": "0.10.5-alpha",
"description": "Jetpack Components Package",
"author": "Automattic",
"license": "GPL-2.0-or-later",
"dependencies": {
"@automattic/format-currency": "1.0.0-alpha.0",
"@wordpress/browserslist-config": "4.1.0",
- "@wordpress/components": "19.1.6",
- "@wordpress/date": "4.2.3",
- "@wordpress/i18n": "4.2.4",
+ "@wordpress/components": "19.3.0",
+ "@wordpress/date": "4.3.0",
+ "@wordpress/i18n": "4.3.0",
"classnames": "2.3.1",
"lodash": "4.17.21",
"prop-types": "^15.7.2"
},
"devDependencies": {
- "@automattic/jetpack-base-styles": "workspace:^0.1.7-alpha",
+ "@automattic/jetpack-base-styles": "workspace:^0.1.8-alpha",
"@babel/core": "7.16.0",
"@babel/preset-react": "7.16.0",
"jetpack-js-test-runner": "workspace:*",
diff --git a/projects/js-packages/config/CHANGELOG.md b/projects/js-packages/config/CHANGELOG.md
index 8b79d254eeb01..f333ab0a17f2a 100644
--- a/projects/js-packages/config/CHANGELOG.md
+++ b/projects/js-packages/config/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.1.3 - 2022-01-25
+### Added
+- Add missing dev dependency on `nyc` for code coverage.
+
## 0.1.2 - 2022-01-18
### Changed
- General: update required node version to v16.13.2
diff --git a/projects/js-packages/config/changelog/update-project-scripts-no-install b/projects/js-packages/config/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/js-packages/config/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/js-packages/config/package.json b/projects/js-packages/config/package.json
index 113de6c088884..64d70ba21edc7 100644
--- a/projects/js-packages/config/package.json
+++ b/projects/js-packages/config/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@automattic/jetpack-config",
- "version": "0.1.3-alpha",
+ "version": "0.1.3",
"description": "Handles Jetpack global configuration shared across all packages",
"homepage": "https://jetpack.com",
"bugs": {
@@ -17,7 +17,8 @@
"test": "NODE_ENV=test NODE_PATH=tests:. js-test-runner --jsdom 'glob:./!(node_modules)/**/test/*.@(jsx|js)'"
},
"devDependencies": {
- "jetpack-js-test-runner": "workspace:*"
+ "jetpack-js-test-runner": "workspace:*",
+ "nyc": "15.1.0"
},
"engines": {
"node": "^14.18.3 || ^16.13.2",
diff --git a/projects/js-packages/connection/CHANGELOG.md b/projects/js-packages/connection/CHANGELOG.md
index 04b75bdc7e8fa..37fc27b086495 100644
--- a/projects/js-packages/connection/CHANGELOG.md
+++ b/projects/js-packages/connection/CHANGELOG.md
@@ -2,6 +2,25 @@
### This is a list detailing changes for the Jetpack RNA Connection Component releases.
+## 0.15.0 - 2022-02-02
+### Added
+- Added user click tracking to disconnect dialog modal.
+
+### Changed
+- Updated package dependencies.
+
+### Fixed
+- Fix access to display_name property in connection status card.
+
+## 0.14.0 - 2022-01-25
+### Changed
+- Enforces the usage of initial state
+- Update h3 style for connection status card
+
+### Fixed
+- Fix Wrong spelling of propTypes in ConnectedPlugins
+- Make redirectUri property not be required in ConnectionStatusCard
+
## 0.13.2 - 2022-01-18
### Changed
- General: update required node version to v16.13.2
diff --git a/projects/js-packages/connection/changelog/add-fancy-eslint-ignore b/projects/js-packages/connection/changelog/add-fancy-eslint-ignore
deleted file mode 100644
index afe95e481cf32..0000000000000
--- a/projects/js-packages/connection/changelog/add-fancy-eslint-ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Hack eslint config to use `.gitignore` and per-dir `.eslintignore`.
-
-
diff --git a/projects/js-packages/connection/changelog/fix-redirectUri-connection-status-card b/projects/js-packages/connection/changelog/fix-redirectUri-connection-status-card
deleted file mode 100644
index 5939a4ff0c800..0000000000000
--- a/projects/js-packages/connection/changelog/fix-redirectUri-connection-status-card
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fixed
-
-Make redirectUri property not be required in ConnectionStatusCard
diff --git a/projects/js-packages/storybook/changelog/renovate-babel-monorepo b/projects/js-packages/connection/changelog/renovate-wordpress-monorepo
similarity index 100%
rename from projects/js-packages/storybook/changelog/renovate-babel-monorepo
rename to projects/js-packages/connection/changelog/renovate-wordpress-monorepo
diff --git a/projects/js-packages/storybook/changelog/2021-09-28-16-11-44-715508 b/projects/js-packages/connection/changelog/renovate-wordpress-monorepo#2
similarity index 100%
rename from projects/js-packages/storybook/changelog/2021-09-28-16-11-44-715508
rename to projects/js-packages/connection/changelog/renovate-wordpress-monorepo#2
diff --git a/projects/js-packages/connection/changelog/update-connection-enforce-initial-state b/projects/js-packages/connection/changelog/update-connection-enforce-initial-state
deleted file mode 100644
index f169b8f8f7b87..0000000000000
--- a/projects/js-packages/connection/changelog/update-connection-enforce-initial-state
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: changed
-
-Enforces the usage of initial state
diff --git a/projects/js-packages/connection/changelog/update-connection-section-my-jetpack-styles b/projects/js-packages/connection/changelog/update-connection-section-my-jetpack-styles
deleted file mode 100644
index f73d2d7a6494e..0000000000000
--- a/projects/js-packages/connection/changelog/update-connection-section-my-jetpack-styles
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Update h3 style for connection status card
diff --git a/projects/js-packages/connection/changelog/update-enforce-connection-initial-state b/projects/js-packages/connection/changelog/update-enforce-connection-initial-state
deleted file mode 100644
index f169b8f8f7b87..0000000000000
--- a/projects/js-packages/connection/changelog/update-enforce-connection-initial-state
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: changed
-
-Enforces the usage of initial state
diff --git a/projects/js-packages/connection/changelog/update-project-scripts-no-install b/projects/js-packages/connection/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/js-packages/connection/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/js-packages/connection/components/connected-plugins/index.jsx b/projects/js-packages/connection/components/connected-plugins/index.jsx
index d0408266fdaad..71a9ba54af894 100644
--- a/projects/js-packages/connection/components/connected-plugins/index.jsx
+++ b/projects/js-packages/connection/components/connected-plugins/index.jsx
@@ -64,7 +64,7 @@ const ConnectedPlugins = props => {
return null;
};
-ConnectedPlugins.PropTypes = {
+ConnectedPlugins.propTypes = {
/** Plugins that are using the Jetpack connection. */
connectedPlugins: PropTypes.object,
/** Slug of the plugin that has initiated the disconnect. */
diff --git a/projects/js-packages/connection/components/connection-status-card/index.jsx b/projects/js-packages/connection/components/connection-status-card/index.jsx
index ebef16433caa8..36173d60060af 100644
--- a/projects/js-packages/connection/components/connection-status-card/index.jsx
+++ b/projects/js-packages/connection/components/connection-status-card/index.jsx
@@ -147,7 +147,8 @@ const ConnectionStatusCard = props => {
{ isUserConnected && (
- { __( 'Logged in as', 'jetpack' ) } { userConnectionData?.display_name }
+ { __( 'Logged in as', 'jetpack' ) }{ ' ' }
+ { userConnectionData.currentUser?.wpcomUser?.display_name }
) }
diff --git a/projects/js-packages/connection/components/disconnect-dialog/index.jsx b/projects/js-packages/connection/components/disconnect-dialog/index.jsx
index 5ca75d6b5fbad..378f1e8520b28 100644
--- a/projects/js-packages/connection/components/disconnect-dialog/index.jsx
+++ b/projects/js-packages/connection/components/disconnect-dialog/index.jsx
@@ -220,6 +220,11 @@ const DisconnectDialog = props => {
[ setDisconnectError, setIsDisconnecting, pluginScreenDisconnectCallback, context, _disconnect ]
);
+ const trackModalClick = useCallback(
+ target => jetpackAnalytics.tracks.recordEvent( target, defaultTracksArgs ),
+ [ defaultTracksArgs ]
+ );
+
/**
* Do we have the necessary data to be able to submit a survey?
* Need to have the ID of the connected user and the ID of the connected site.
@@ -321,6 +326,7 @@ const DisconnectDialog = props => {
disconnectError={ disconnectError }
context={ context } // Where is the modal showing? ( most important for when it loads on the plugins page )
disconnectingPlugin={ disconnectingPlugin } // Which plugin is initiating the disconnect.
+ trackModalClick={ trackModalClick }
/>
);
} else if ( isDisconnected && ! isProvidingFeedback && ! isFeedbackProvided ) {
diff --git a/projects/js-packages/connection/components/disconnect-dialog/steps/step-disconnect.jsx b/projects/js-packages/connection/components/disconnect-dialog/steps/step-disconnect.jsx
index b3f887a2f2048..f4a2ba72a353a 100644
--- a/projects/js-packages/connection/components/disconnect-dialog/steps/step-disconnect.jsx
+++ b/projects/js-packages/connection/components/disconnect-dialog/steps/step-disconnect.jsx
@@ -1,7 +1,7 @@
/**
* External Dependencies
*/
-import React from 'react';
+import React, { useCallback } from 'react';
/**
* Internal Dependencies
@@ -30,8 +30,29 @@ const StepDisconnect = props => {
disconnectingPlugin,
closeModal,
context,
+ trackModalClick,
} = props;
+ const trackLearnClick = useCallback(
+ () => trackModalClick( 'jetpack_disconnect_dialog_click_learn_about' ),
+ [ trackModalClick ]
+ );
+ const trackSupportClick = useCallback(
+ () => trackModalClick( 'jetpack_disconnect_dialog_click_support' ),
+ [ trackModalClick ]
+ );
+ const handleStayConnectedClick = useCallback( () => {
+ trackModalClick( 'jetpack_disconnect_dialog_click_stay_connected' );
+ closeModal();
+ }, [ trackModalClick, closeModal ] );
+ const handleDisconnectClick = useCallback(
+ e => {
+ trackModalClick( 'jetpack_disconnect_dialog_click_disconnect' );
+ onDisconnect( e );
+ },
+ [ trackModalClick, onDisconnect ]
+ );
+
/**
* Render the disconnect button, allows for some variance based on context.
*
@@ -50,7 +71,7 @@ const StepDisconnect = props => {
{ buttonText }
@@ -109,6 +130,7 @@ const StepDisconnect = props => {
rel="noopener noreferrer"
target="_blank"
className="jp-connection__disconnect-dialog__link"
+ onClick={ trackLearnClick }
/>
),
jpSupportLink: (
@@ -117,6 +139,7 @@ const StepDisconnect = props => {
rel="noopener noreferrer"
target="_blank"
className="jp-connection__disconnect-dialog__link"
+ onClick={ trackSupportClick }
/>
),
}
@@ -127,7 +150,7 @@ const StepDisconnect = props => {
{ __( 'Stay connected', 'jetpack' ) }
@@ -162,6 +185,8 @@ StepDisconnect.propTypes = {
closeModal: PropTypes.func,
/** Where this modal is being rendered. */
context: PropTypes.string,
+ /** Callback tracks link/btn clicks */
+ trackModalClick: PropTypes.func,
};
export default StepDisconnect;
diff --git a/projects/js-packages/connection/components/disconnect-dialog/test/step-disconnect.jsx b/projects/js-packages/connection/components/disconnect-dialog/test/step-disconnect.jsx
index f2d3591a3df9e..427671a692523 100644
--- a/projects/js-packages/connection/components/disconnect-dialog/test/step-disconnect.jsx
+++ b/projects/js-packages/connection/components/disconnect-dialog/test/step-disconnect.jsx
@@ -16,8 +16,13 @@ describe( 'StepDisconnect', () => {
title: 'Test Title',
onDisconnect: spy(),
closeModal: spy(),
+ trackModalClick: spy(),
};
+ afterEach( () => {
+ testProps.trackModalClick.resetHistory();
+ } );
+
describe( 'Initially', () => {
const wrapper = shallow( );
@@ -35,25 +40,66 @@ describe( 'StepDisconnect', () => {
describe( 'When the disconnect button is clicked', () => {
const wrapper = shallow( );
-
- wrapper
- .find( '.jp-connection__disconnect-dialog__btn-disconnect' )
- .simulate( 'click', { preventDefault: () => undefined } );
+ const disconnectButton = wrapper.find( '.jp-connection__disconnect-dialog__btn-disconnect' );
it( 'calls the disconnect callback', () => {
+ disconnectButton.simulate( 'click', { preventDefault: () => undefined } );
+
expect( testProps.onDisconnect.called ).to.be.true;
} );
+
+ it( 'calls the trackModalClick callback with jetpack_disconnect_dialog_click_disconnect', () => {
+ disconnectButton.simulate( 'click', { preventDefault: () => undefined } );
+
+ expect(
+ testProps.trackModalClick.calledOnceWith( 'jetpack_disconnect_dialog_click_disconnect' )
+ ).to.be.true;
+ } );
} );
describe( 'When the dismiss button is clicked', () => {
const wrapper = shallow( );
-
- wrapper
- .find( '.jp-connection__disconnect-dialog__btn-dismiss' )
- .simulate( 'click', { preventDefault: () => undefined } );
+ const dismissBtn = wrapper.find( '.jp-connection__disconnect-dialog__btn-dismiss' );
it( 'calls the closeModal callback', () => {
+ dismissBtn.simulate( 'click', { preventDefault: () => undefined } );
+
expect( testProps.closeModal.called ).to.be.true;
} );
+
+ it( 'calls the trackModalClick callback with jetpack_disconnect_dialog_click_stay_connected', () => {
+ dismissBtn.simulate( 'click', { preventDefault: () => undefined } );
+
+ expect(
+ testProps.trackModalClick.calledOnceWith( 'jetpack_disconnect_dialog_click_stay_connected' )
+ ).to.be.true;
+ } );
+ } );
+
+ describe( 'When help links are clicked', () => {
+ const trackEvents = [
+ 'jetpack_disconnect_dialog_click_learn_about',
+ 'jetpack_disconnect_dialog_click_support',
+ ];
+ const wrapper = shallow( );
+ const links = wrapper.find( '.jp-connection__disconnect-dialog__link' );
+ let trackedTotal = 0;
+
+ links.forEach( ( link, i ) => {
+ link.simulate( 'click', { preventDefault: () => undefined } );
+
+ const clickTrackValue = testProps.trackModalClick.getCall( i ).args[ 0 ];
+
+ it( `should track ${ clickTrackValue }`, async () => {
+ const valueIdx = trackEvents.indexOf( clickTrackValue );
+
+ expect( clickTrackValue ).to.equal( trackEvents[ valueIdx ] );
+ trackedTotal++;
+ } );
+ } );
+
+ it( `should track all ${ trackEvents.length } expected events`, () => {
+ expect( trackedTotal ).to.equal( trackEvents.length );
+ } );
} );
} );
diff --git a/projects/js-packages/connection/package.json b/projects/js-packages/connection/package.json
index de6b6324aa2a5..b28df673abba6 100644
--- a/projects/js-packages/connection/package.json
+++ b/projects/js-packages/connection/package.json
@@ -1,25 +1,25 @@
{
"name": "@automattic/jetpack-connection",
- "version": "0.14.0-alpha",
+ "version": "0.15.1-alpha",
"description": "Jetpack Connection Component",
"author": "Automattic",
"license": "GPL-2.0-or-later",
"dependencies": {
- "@automattic/jetpack-analytics": "workspace:^0.1.7-alpha",
- "@automattic/jetpack-config": "workspace:^0.1.3-alpha",
- "@automattic/jetpack-components": "workspace:^0.10.3-alpha",
- "@automattic/jetpack-api": "workspace:^0.8.3-alpha",
- "@wordpress/base-styles": "4.0.4",
+ "@automattic/jetpack-analytics": "workspace:^0.1.7",
+ "@automattic/jetpack-config": "workspace:^0.1.3",
+ "@automattic/jetpack-components": "workspace:^0.10.5-alpha",
+ "@automattic/jetpack-api": "workspace:^0.8.4-alpha",
+ "@wordpress/base-styles": "4.1.0",
"@wordpress/browserslist-config": "4.1.0",
- "@wordpress/components": "19.1.6",
- "@wordpress/data": "6.1.5",
- "@wordpress/element": "4.0.4",
- "@wordpress/i18n": "4.2.4",
+ "@wordpress/components": "19.3.0",
+ "@wordpress/data": "6.2.0",
+ "@wordpress/element": "4.1.0",
+ "@wordpress/i18n": "4.3.0",
"lodash": "4.17.21",
"prop-types": "^15.7.2"
},
"devDependencies": {
- "@automattic/jetpack-base-styles": "workspace:^0.1.7-alpha",
+ "@automattic/jetpack-base-styles": "workspace:^0.1.8-alpha",
"@babel/core": "7.16.0",
"@babel/preset-react": "7.16.0",
"jetpack-js-test-runner": "workspace:*",
diff --git a/projects/js-packages/connection/state/selectors.jsx b/projects/js-packages/connection/state/selectors.jsx
index b52694205b123..8284700bfbfee 100644
--- a/projects/js-packages/connection/state/selectors.jsx
+++ b/projects/js-packages/connection/state/selectors.jsx
@@ -3,7 +3,7 @@ const connectionSelectors = {
/**
* Checks whether the store is fetching the connection status from the server
*
- * @deprecated since $$next-version$$
+ * @deprecated since 0.14.0
* @returns {boolean} Is the store is fetching the connection status from the server?
*/
getConnectionStatusIsFetching: () => false,
diff --git a/projects/js-packages/storybook/changelog/renovate-babel-monorepo#2 b/projects/js-packages/eslint-changed/changelog/renovate-major-eslint-packages
similarity index 100%
rename from projects/js-packages/storybook/changelog/renovate-babel-monorepo#2
rename to projects/js-packages/eslint-changed/changelog/renovate-major-eslint-packages
diff --git a/projects/js-packages/eslint-changed/changelog/renovate-major-eslint-packages#2 b/projects/js-packages/eslint-changed/changelog/renovate-major-eslint-packages#2
new file mode 100644
index 0000000000000..3a3e8e69399b5
--- /dev/null
+++ b/projects/js-packages/eslint-changed/changelog/renovate-major-eslint-packages#2
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Update tests for eslint 8.8.0.
diff --git a/projects/js-packages/eslint-changed/package.json b/projects/js-packages/eslint-changed/package.json
index 4351bd949c6f5..a1359c05d7acf 100644
--- a/projects/js-packages/eslint-changed/package.json
+++ b/projects/js-packages/eslint-changed/package.json
@@ -26,7 +26,7 @@
"parse-diff": "0.8.1"
},
"devDependencies": {
- "eslint": "7.32.0",
+ "eslint": "8.8.0",
"jetpack-js-test-runner": "workspace:*",
"nyc": "15.1.0"
},
diff --git a/projects/js-packages/eslint-changed/tests/bin/eslint-changed.js b/projects/js-packages/eslint-changed/tests/bin/eslint-changed.js
index 47efdadb0262b..eabb971c99713 100644
--- a/projects/js-packages/eslint-changed/tests/bin/eslint-changed.js
+++ b/projects/js-packages/eslint-changed/tests/bin/eslint-changed.js
@@ -441,6 +441,7 @@ describe( 'bin/eslint-changed.js', () => {
fixableErrorCount: 1,
fixableWarningCount: 0,
source: 'console.log( "Hello, world?" );\n',
+ suppressedMessages: [],
usedDeprecatedRules: [],
},
];
@@ -484,6 +485,7 @@ describe( 'bin/eslint-changed.js', () => {
fixableErrorCount: 1,
fixableWarningCount: 0,
source: 'console.log( "Hello, world?" );\n',
+ suppressedMessages: [],
usedDeprecatedRules: [],
},
];
@@ -527,6 +529,7 @@ describe( 'bin/eslint-changed.js', () => {
fixableErrorCount: 1,
fixableWarningCount: 0,
source: "console.log( '¡Hola, mundo!' )\n",
+ suppressedMessages: [],
usedDeprecatedRules: [],
},
];
@@ -566,6 +569,7 @@ describe( 'bin/eslint-changed.js', () => {
fixableErrorCount: 0,
fixableWarningCount: 0,
source: "var x;\nconsole.log( 'Hello, world!' );\n",
+ suppressedMessages: [],
usedDeprecatedRules: [],
},
];
@@ -619,6 +623,7 @@ describe( 'bin/eslint-changed.js', () => {
fixableErrorCount: 0,
fixableWarningCount: 0,
source: 'var x = 1;\n',
+ suppressedMessages: [],
usedDeprecatedRules: [],
},
{
@@ -646,6 +651,7 @@ describe( 'bin/eslint-changed.js', () => {
fixableErrorCount: 1,
fixableWarningCount: 0,
source: 'var x = \'Hello\';\nx += ", world!";\nconsole.log( x );\n',
+ suppressedMessages: [],
usedDeprecatedRules: [],
},
];
@@ -688,6 +694,7 @@ describe( 'bin/eslint-changed.js', () => {
fixableErrorCount: 0,
fixableWarningCount: 0,
source: "var x = 'Hello, world!';\n\n\n\n\n\n\n\n\n\n\n\nconsole.log( x )\n",
+ suppressedMessages: [],
usedDeprecatedRules: [],
},
{
@@ -722,6 +729,7 @@ describe( 'bin/eslint-changed.js', () => {
fixableErrorCount: 0,
fixableWarningCount: 0,
source: "var y = 'Hello, world!';\n\n\n\n\n\n\n\n\n\n\n\nconsole.log( x )\n",
+ suppressedMessages: [],
usedDeprecatedRules: [],
},
];
@@ -779,6 +787,7 @@ describe( 'bin/eslint-changed.js', () => {
fixableErrorCount: 0,
fixableWarningCount: 0,
source: "var y = 'Hello, world!';\n\n\n\n\n\n\n\n\n\n\n\nconsole.log( x )\n",
+ suppressedMessages: [],
usedDeprecatedRules: [],
},
];
diff --git a/projects/js-packages/eslint-config-target-es/CHANGELOG.md b/projects/js-packages/eslint-config-target-es/CHANGELOG.md
index 11285e895a097..5a6dfd04d25f5 100644
--- a/projects/js-packages/eslint-config-target-es/CHANGELOG.md
+++ b/projects/js-packages/eslint-config-target-es/CHANGELOG.md
@@ -5,6 +5,13 @@ 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).
+## [1.0.1] - 2022-02-01
+### Changed
+- General: update required node version to v16.13.2
+- Updated package dependencies
+
## 1.0.0 - 2021-11-16
### Added
- Initial release.
+
+[1.0.1]: https://github.com/Automattic/eslint-config-target-es/compare/1.0.0...1.0.1
diff --git a/projects/js-packages/storybook/changelog/renovate-babel-monorepo#3 b/projects/js-packages/eslint-config-target-es/changelog/renovate-major-eslint-packages
similarity index 100%
rename from projects/js-packages/storybook/changelog/renovate-babel-monorepo#3
rename to projects/js-packages/eslint-config-target-es/changelog/renovate-major-eslint-packages
diff --git a/projects/js-packages/eslint-config-target-es/changelog/update-min-pnpm-version b/projects/js-packages/eslint-config-target-es/changelog/update-min-pnpm-version
deleted file mode 100644
index d824134990470..0000000000000
--- a/projects/js-packages/eslint-config-target-es/changelog/update-min-pnpm-version
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Update minimum pnpm version.
-
-
diff --git a/projects/js-packages/eslint-config-target-es/changelog/update-node-version-16132 b/projects/js-packages/eslint-config-target-es/changelog/update-node-version-16132
deleted file mode 100644
index feb2fb77ea11d..0000000000000
--- a/projects/js-packages/eslint-config-target-es/changelog/update-node-version-16132
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-General: update required node version to v16.13.2
diff --git a/projects/js-packages/eslint-config-target-es/changelog/update-project-scripts-no-install b/projects/js-packages/eslint-config-target-es/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/js-packages/eslint-config-target-es/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/js-packages/eslint-config-target-es/package.json b/projects/js-packages/eslint-config-target-es/package.json
index fcd6319fc5278..8c45a879c75c7 100644
--- a/projects/js-packages/eslint-config-target-es/package.json
+++ b/projects/js-packages/eslint-config-target-es/package.json
@@ -1,6 +1,6 @@
{
"name": "@automattic/eslint-config-target-es",
- "version": "1.0.1-alpha",
+ "version": "1.0.2-alpha",
"description": "ESLint sharable config to activate eslint-plugin-es checks based on browserslist targets.",
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/eslint-config-target-es/README.md#readme",
"bugs": {
@@ -24,7 +24,7 @@
},
"devDependencies": {
"@wordpress/browserslist-config": "4.1.0",
- "eslint": "7.32.0",
+ "eslint": "8.8.0",
"eslint-plugin-es": "4.1.0",
"jest": "27.3.1"
},
diff --git a/projects/js-packages/i18n-check-webpack-plugin/CHANGELOG.md b/projects/js-packages/i18n-check-webpack-plugin/CHANGELOG.md
index 2ec597f896fa6..c4cd965a476ac 100644
--- a/projects/js-packages/i18n-check-webpack-plugin/CHANGELOG.md
+++ b/projects/js-packages/i18n-check-webpack-plugin/CHANGELOG.md
@@ -5,6 +5,14 @@ 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).
+## [1.0.4] - 2022-01-27
+### Changed
+- Updated package dependencies.
+
+## [1.0.3] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [1.0.2] - 2022-01-18
### Changed
- General: update required node version to v16.13.2
@@ -26,6 +34,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Initial release.
+[1.0.4]: https://github.com/Automattic/i18n-check-webpack-plugin/compare/v1.0.3...v1.0.4
+[1.0.3]: https://github.com/Automattic/i18n-check-webpack-plugin/compare/v1.0.2...v1.0.3
[1.0.2]: https://github.com/Automattic/i18n-check-webpack-plugin/compare/v1.0.1...v1.0.2
[1.0.1]: https://github.com/Automattic/i18n-check-webpack-plugin/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/Automattic/i18n-check-webpack-plugin/compare/v0.1.0...v1.0.0
diff --git a/projects/js-packages/i18n-check-webpack-plugin/changelog/add-fancy-eslint-ignore b/projects/js-packages/i18n-check-webpack-plugin/changelog/add-fancy-eslint-ignore
deleted file mode 100644
index afe95e481cf32..0000000000000
--- a/projects/js-packages/i18n-check-webpack-plugin/changelog/add-fancy-eslint-ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Hack eslint config to use `.gitignore` and per-dir `.eslintignore`.
-
-
diff --git a/projects/js-packages/i18n-check-webpack-plugin/changelog/update-project-scripts-no-install b/projects/js-packages/i18n-check-webpack-plugin/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/js-packages/i18n-check-webpack-plugin/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/js-packages/i18n-check-webpack-plugin/composer.json b/projects/js-packages/i18n-check-webpack-plugin/composer.json
index 18bda6bc8f720..68a8ae88558da 100644
--- a/projects/js-packages/i18n-check-webpack-plugin/composer.json
+++ b/projects/js-packages/i18n-check-webpack-plugin/composer.json
@@ -32,6 +32,11 @@
"mirror-repo": "Automattic/i18n-check-webpack-plugin",
"changelogger": {
"link-template": "https://github.com/Automattic/i18n-check-webpack-plugin/compare/v${old}...v${new}"
+ },
+ "dependencies": {
+ "test": [
+ "js-packages/webpack-config"
+ ]
}
}
}
diff --git a/projects/js-packages/i18n-check-webpack-plugin/package.json b/projects/js-packages/i18n-check-webpack-plugin/package.json
index 78bc2b3f3c92a..72db279637c8d 100644
--- a/projects/js-packages/i18n-check-webpack-plugin/package.json
+++ b/projects/js-packages/i18n-check-webpack-plugin/package.json
@@ -1,6 +1,6 @@
{
"name": "@automattic/i18n-check-webpack-plugin",
- "version": "1.0.3-alpha",
+ "version": "1.0.4",
"description": "A Webpack plugin to check that WordPress i18n hasn't been mangled by Webpack optimizations.",
"homepage": "https://jetpack.com",
"bugs": {
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/CHANGELOG.md b/projects/js-packages/i18n-loader-webpack-plugin/CHANGELOG.md
index 1bd3e43c1d1ba..7068bf46afef8 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/CHANGELOG.md
+++ b/projects/js-packages/i18n-loader-webpack-plugin/CHANGELOG.md
@@ -5,6 +5,11 @@ 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).
+## [2.0.0] - 2022-01-25
+### Changed
+- BREAKING: Remove the downloading logic from the runtime by requiring a "loader" module rather than a "state" module. This gives more flexibility for different implementations in the future.
+- Update documentation for moving of the handling of package→plugin path mapping into jetpack-composer-plugin and jetpack-assets.
+
## [1.0.2] - 2022-01-18
### Changed
- General: update required node version to v16.13.2
@@ -20,5 +25,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Initial release.
+[2.0.0]: https://github.com/Automattic/i18n-loader-webpack-plugin/compare/v1.0.2...v2.0.0
[1.0.2]: https://github.com/Automattic/i18n-loader-webpack-plugin/compare/v1.0.1...v1.0.2
[1.0.1]: https://github.com/Automattic/i18n-loader-webpack-plugin/compare/v1.0.0...v1.0.1
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/README.md b/projects/js-packages/i18n-loader-webpack-plugin/README.md
index 7b427d4635fa8..007922b8166c8 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/README.md
+++ b/projects/js-packages/i18n-loader-webpack-plugin/README.md
@@ -28,9 +28,10 @@ This goes in the `plugins` section of your Webpack config, e.g.
Parameters recognized by the plugin are:
- `textdomain`: The text domain used in the JavaScript code. This is required, unless nothing in your JS actually uses [@wordpress/i18n].
-- `stateModule`: The name of a module supplying the i18n state. See [State module](#state-module) below for details.
+- `loaderModule`: The name of a module supplying the i18n loader. See [Loader module](#loader-module) below for details.
+- `loaderMethod`: The name of the function from `loaderModule` to download the i18n. See [Loader module](#loader-module) below for details.
- `target`: The target of the build: 'plugin' (the default), 'theme', or 'core'. This is used to determine where in WordPress's languages directory to look for the translation files.
-- `path`: See [Webpack context](#webpack-context) and [Use in Composer packages](#use-in-composer-packages) below for details.
+- `path`: See [Webpack context](#webpack-context) below for details.
- `ignoreModules`: If some bundles in your build depend on [@wordpress/i18n] for purposes other than translating strings, i18n-loader-webpack-plugin will none the less count them as "using @wordpress/i18n" which may result in it trying to load translations for bundles that do not need it. This option may be used to ignore the relevant source files when examining the bundles.
The value may be a function, which will be passed the file path relative to [Webpack's context] and the Webpack Module object and which should return true if the file should be ignored, or a string or RegExp to be compared with the relative file path, or an array of such strings, RegExps, and/or functions.
@@ -50,23 +51,32 @@ But if for some reason you want to do it manually, something like this in your W
}
```
-### State module
+### Loader module
-In order to load the translations, the generated bundle needs to be provided with some state at runtime. This is handled by loading a module that must be externalized by your Webpack configuration.
+In order to load the translations, the generated bundle needs to call a method to do the actual downloading at runtime. This is handled by loading a module that must be externalized by your Webpack configuration.
-The default module name is `@wordpress/jp-i18n-state`, which will automatically be externalized by [@wordpress/dependency-extraction-webpack-plugin], which will also register it as a dependency for "wp-jp-i18n-state" in the generated `.asset.php` file. That, in turn, is provided by the [automattic/jetpack-assets] Composer package (which also provides an `Automattic\Jetpack\Assets::register_script()` function to easily consume the `.asset.php` file).
+The default module name is `@wordpress/jp-i18n-loader`, which will automatically be externalized by [@wordpress/dependency-extraction-webpack-plugin], which will also register it as a dependency for "wp-jp-i18n-loader" in the generated `.asset.php` file. That, in turn, is provided by the [automattic/jetpack-assets] Composer package (which also provides an `Automattic\Jetpack\Assets::register_script()` function to easily consume the `.asset.php` file).
-But if for some reason you don't want to use those packages, you can set the plugin's `stateModule` option to point to a different module name, use [Webpack's externals configuration] to externalize it, and appropriate PHP code to provide the corresponding global variable for your externals configuration to retrieve. The state is a JavaScript object with the following properties:
+But if for some reason you don't want to use those packages, you can set the plugin's `loaderModule` and `loaderMethod` options to point to a different module name, use [Webpack's externals configuration] to externalize it, and appropriate PHP code to provide the corresponding global variable for your externals configuration to retrieve.
-- `baseUrl`: The base URL from which to fetch the translations, probably something like `https://yoursite.example.com/wp-content/languages/`. The trailing slash is required.
-- `locale`: The locale used on the page.
-- `domainMap`: An object mapping textdomains. See [Use in Composer packages](#use-in-composer-packages) below for details.
+The loader method might be documented like this:
+```js
+/**
+ * Download and register translations for a bundle.
+ *
+ * @param {string} path - Bundle path being fetched. May have a query part.
+ * @param {string} domain - Text domain to register into.
+ * @param {string} location - Location for the translation: 'plugin', 'theme', or 'core'.
+ * @returns {Promise} Resolved when the translations are registered, or rejected with an `Error`.
+ */
+```
+Most likely the method will separate any query part from the path, hash it, build the download url, fetch it, then register it via `@wordpress/i18n`'s `setLocaleData()` method.
### Webpack context
WordPress's translation infrastructure generates a file for each JS script named like "_textdomain_-_locale_-_hash_.json". The _hash_ is an MD5 hash of the path of the script file relative to the plugin's root.
-I18n-loader-webpack-plugin assumes that [Webpack's context] is the base of the WordPress plugin in which the bundles will be included.
+I18n-loader-webpack-plugin assumes that [Webpack's context] is the base of the WordPress package or plugin in which the bundles will be included, and that the [loader module](#loader-module) will handle mapping from package root to plugin root.
If this is not the case, you'll need to set the plugin's `path` parameter to the relative path from the plugin's root to Webpack's `output.path`.
### Other useful Webpack configuration
@@ -88,9 +98,8 @@ WordPress's plugin infrastructure doesn't natively support Composer packages, so
That won't work for packages needing translation, though, as WordPress's translation infrastructure ignores the `vendor/` directory when looking for strings to be translated.
You'll need to use something like [automattic/jetpack-composer-plugin] so that the composer packages with translated strings are installed to a different path.
-Then, for Webpack builds in the Composer package, you'll need to set i18n-loader-webpack-plugin's `path` option to the path relative to the _plugin's_ root directory. For example, if your Composer package is named "automattic/foobar", will be used with [automattic/jetpack-composer-plugin] which will install the package to `jetpack_vendor/` rather than `vendor/`, and Webpack is building to a `build/` directory within the package, you'd need to set `path` to `jetpack_vendor/automattic/foobar/build/` as that's where the built files will end up relative to the plugin.
-
-The consuming plugin will also need to arrange for the [state module](#state-module)'s `domainMap` to include a mapping from your Composer package's textdomain to the plugin's textdomain (prefixed with "plugins/"), as that's where the translations will end up. This may be done using [automattic/jetpack-assets] along with [automattic/jetpack-composer-plugin], as described in the latter's documentation.
+Also, as the translation file will be named using the plugin's textdomain rather than the Composer package's, the consuming plugin will also need to arrange for the [loader module](#loader-module) to fetch the proper file.
+This may be done using [automattic/jetpack-assets] along with [automattic/jetpack-composer-plugin], as described in the latter's documentation.
## Security
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/changelog/add-fancy-eslint-ignore b/projects/js-packages/i18n-loader-webpack-plugin/changelog/add-fancy-eslint-ignore
deleted file mode 100644
index afe95e481cf32..0000000000000
--- a/projects/js-packages/i18n-loader-webpack-plugin/changelog/add-fancy-eslint-ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Hack eslint config to use `.gitignore` and per-dir `.eslintignore`.
-
-
diff --git a/projects/js-packages/storybook/changelog/renovate-css-loader-6.x b/projects/js-packages/i18n-loader-webpack-plugin/changelog/renovate-wordpress-monorepo
similarity index 100%
rename from projects/js-packages/storybook/changelog/renovate-css-loader-6.x
rename to projects/js-packages/i18n-loader-webpack-plugin/changelog/renovate-wordpress-monorepo
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/changelog/update-project-scripts-no-install b/projects/js-packages/i18n-loader-webpack-plugin/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/js-packages/i18n-loader-webpack-plugin/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/package.json b/projects/js-packages/i18n-loader-webpack-plugin/package.json
index 01926c18198e3..ea6cca53fdf95 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/package.json
+++ b/projects/js-packages/i18n-loader-webpack-plugin/package.json
@@ -1,6 +1,6 @@
{
"name": "@automattic/i18n-loader-webpack-plugin",
- "version": "1.0.3-alpha",
+ "version": "2.0.1-alpha",
"description": "A Webpack plugin to load WordPress i18n when Webpack lazy-loads a bundle.",
"homepage": "https://jetpack.com",
"bugs": {
@@ -17,12 +17,11 @@
"test-coverage": "jest tests --coverage --collectCoverageFrom='src/**/*.js' --coverageDirectory=\"$COVERAGE_DIR\" --coverageReporters=clover"
},
"dependencies": {
- "debug": "^4.3.2",
- "md5-es": "^1.8.2"
+ "debug": "^4.3.2"
},
"devDependencies": {
- "@wordpress/dependency-extraction-webpack-plugin": "3.2.1",
- "@wordpress/i18n": "4.2.4",
+ "@wordpress/dependency-extraction-webpack-plugin": "3.3.0",
+ "@wordpress/i18n": "4.3.0",
"jest": "27.3.1",
"webpack": "5.65.0"
},
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/src/I18nLoaderPlugin.js b/projects/js-packages/i18n-loader-webpack-plugin/src/I18nLoaderPlugin.js
index f96ae59d3e38e..0dfdc85bb16a7 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/src/I18nLoaderPlugin.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/src/I18nLoaderPlugin.js
@@ -31,8 +31,12 @@ const schema = {
},
},
properties: {
- stateModule: {
- description: 'Externalized module supplying the i18n state.',
+ loaderModule: {
+ description: 'Externalized module supplying the i18n loader.',
+ type: 'string',
+ },
+ loaderMethod: {
+ description: 'Method on the loader module to call to load the i18n.',
type: 'string',
},
textdomain: {
@@ -44,7 +48,7 @@ const schema = {
enum: [ 'plugin', 'theme', 'core' ],
},
path: {
- description: 'Path (relative to the plugin) to locate the output assets.',
+ description: 'Path (relative to the package or plugin) to locate the output assets.',
type: 'string',
},
ignoreModules: {
@@ -79,7 +83,8 @@ class I18nLoaderPlugin {
this.options = {
target: 'plugin',
- stateModule: '@wordpress/jp-i18n-state',
+ loaderModule: '@wordpress/jp-i18n-loader',
+ loaderMethod: 'downloadI18n',
...options,
};
@@ -114,51 +119,30 @@ class I18nLoaderPlugin {
} );
// At the "make" hook, inject Dependency objects into the build so we can get the modules we need.
- const stateModuleDep = new I18nLoaderModuleDependency( this.options.stateModule );
- stateModuleDep.optional = true;
- const i18nModuleDep = new I18nLoaderModuleDependency( '@wordpress/i18n' );
- i18nModuleDep.optional = true;
+ const loaderModuleDep = new I18nLoaderModuleDependency( this.options.loaderModule );
+ loaderModuleDep.optional = true;
compiler.hooks.make.tapPromise( PLUGIN_NAME, compilation => {
- return Promise.all( [
- new Promise( ( resolve, reject ) => {
- compilation.addModuleChain( compiler.context, stateModuleDep, ( err, module ) => {
- if ( err ) {
- return reject( err );
- }
- // Webpack bug; Until 5.51.0 it didn't pass the module to the callback.
- if ( ! module && ! compilation.moduleGraph.getModule( stateModuleDep ) ) {
- // prettier-ignore
- let msg = `${ PLUGIN_NAME }:\nFailed to add state module ${ this.options.stateModule } to the build.\n`;
- if ( this.options.stateModule.startsWith( '@wordpress/' ) ) {
- msg +=
- "You'll need to add @wordpress/dependency-extraction-webpack-plugin or an appropriate externals directive to your Webpack config.";
- } else {
- msg +=
- "You'll need to add the appropriate externals directive to your Webpack config.";
- }
- compilation.errors.push( new webpack.WebpackError( msg ) );
- }
- resolve();
- } );
- } ),
- new Promise( ( resolve, reject ) => {
- compilation.addModuleChain( compiler.context, i18nModuleDep, ( err, module ) => {
- if ( err ) {
- return reject( err );
- }
- // Webpack bug; Until 5.51.0 it didn't pass the module to the callback.
- if ( ! module && ! compilation.moduleGraph.getModule( i18nModuleDep ) ) {
- compilation.errors.push(
- new webpack.WebpackError(
- `${ PLUGIN_NAME }:\nFailed to add i18n module @wordpress/i18n to the build.\n` +
- "You'll need to add @wordpress/dependency-extraction-webpack-plugin or an appropriate externals directive to your Webpack config, or add @wordpress/i18n to your package.json."
- )
- );
+ return new Promise( ( resolve, reject ) => {
+ compilation.addModuleChain( compiler.context, loaderModuleDep, ( err, module ) => {
+ if ( err ) {
+ return reject( err );
+ }
+ // Webpack bug; Until 5.51.0 it didn't pass the module to the callback.
+ if ( ! module && ! compilation.moduleGraph.getModule( loaderModuleDep ) ) {
+ // prettier-ignore
+ let msg = `${ PLUGIN_NAME }:\nFailed to add loader module ${ this.options.loaderModule } to the build.\n`;
+ if ( this.options.loaderModule.startsWith( '@wordpress/' ) ) {
+ msg +=
+ "You'll need to add @wordpress/dependency-extraction-webpack-plugin or an appropriate externals directive to your Webpack config.";
+ } else {
+ msg +=
+ "You'll need to add the appropriate externals directive to your Webpack config.";
}
- resolve();
- } );
- } ),
- ] );
+ compilation.errors.push( new webpack.WebpackError( msg ) );
+ }
+ resolve();
+ } );
+ } );
} );
compiler.hooks.thisCompilation.tap( PLUGIN_NAME, compilation => {
@@ -168,10 +152,9 @@ class I18nLoaderPlugin {
* @returns {object} Stuff.
*/
function getStuff() {
- const stateModule = compilation.moduleGraph.getModule( stateModuleDep );
- const i18nModule = compilation.moduleGraph.getModule( i18nModuleDep );
+ const loaderModule = compilation.moduleGraph.getModule( loaderModuleDep );
- const i18nModules = new Set( [ i18nModule ] );
+ const i18nModules = new Set();
for ( const module of compilation.modules ) {
// rawRequest is for a NormalModule. userRequest is for an ExternalModule. Other types we don't really care about.
if (
@@ -183,18 +166,18 @@ class I18nLoaderPlugin {
}
const i18nModulesArr = [ ...i18nModules ];
- return { stateModule, i18nModule, i18nModulesArr, chunkGraph: compilation.chunkGraph };
+ return { loaderModule, i18nModulesArr, chunkGraph: compilation.chunkGraph };
}
// After chunks have been optimized (e.g. chunk splitting happened), determine which chunks need
// our deps and inject them.
compilation.hooks.afterOptimizeChunks.tap( PLUGIN_NAME, chunks => {
- const { stateModule, i18nModule, i18nModulesArr, chunkGraph } = getStuff();
- if ( ! stateModule ) {
- debug( "State module is missing, can't run." );
+ const { loaderModule, i18nModulesArr, chunkGraph } = getStuff();
+ if ( ! loaderModule ) {
+ debug( "Loader module is missing, can't run." );
return;
}
- if ( ! i18nModule || i18nModulesArr.length <= 0 ) {
+ if ( i18nModulesArr.length <= 0 ) {
debug( "I18n module is missing, can't run." );
return;
}
@@ -239,41 +222,28 @@ class I18nLoaderPlugin {
}
// Inject into the chunk!
- if ( chunkGraph.isModuleInChunk( stateModule, chunk ) ) {
- debug( ` Already had ${ this.options.stateModule }` );
+ if ( chunkGraph.isModuleInChunk( loaderModule, chunk ) ) {
+ debug( ` Already had ${ this.options.loaderModule }` );
} else {
- chunkGraph.connectChunkAndModule( chunk, stateModule );
+ chunkGraph.connectChunkAndModule( chunk, loaderModule );
chunkGraph.addModuleRuntimeRequirements(
- stateModule,
+ loaderModule,
chunk.runtime,
new Set( [ RuntimeGlobals.module ] )
);
}
- if ( ! i18nModulesArr.some( m => chunkGraph.isModuleInChunk( m, chunk ) ) ) {
- debug( " Didn't itself use @wordpress/i18n" );
- chunkGraph.connectChunkAndModule( chunk, i18nModule );
- }
// Any chunk using this as a runtime doesn't itself need the injected modules.
for ( const c of chunk.getAllReferencedChunks() ) {
if ( c === chunk ) {
continue;
}
- if ( chunkGraph.isModuleInChunk( stateModule, c ) ) {
+ if ( chunkGraph.isModuleInChunk( loaderModule, c ) ) {
debug(
// prettier-ignore
- ` Removing redundant ${ this.options.stateModule } from chunk ${ c.name || c.id || c.debugId }`
+ ` Removing redundant ${ this.options.loaderModule } from chunk ${ c.name || c.id || c.debugId }`
);
- chunkGraph.disconnectChunkAndModule( c, stateModule );
- }
- for ( const m of i18nModulesArr ) {
- if ( chunkGraph.isModuleInChunk( m, c ) ) {
- debug(
- // prettier-ignore
- ` Removing redundant @wordpress/i18n from chunk ${ c.name || c.id || c.debugId }`
- );
- chunkGraph.disconnectChunkAndModule( c, m );
- }
+ chunkGraph.disconnectChunkAndModule( c, loaderModule );
}
}
}
@@ -281,22 +251,22 @@ class I18nLoaderPlugin {
// This is just for debugging, to see if later optimizations removed our modules.
compilation.hooks.afterOptimizeChunkModules.tap( PLUGIN_NAME, chunks => {
- const { stateModule, i18nModulesArr, chunkGraph } = getStuff();
- if ( ! stateModule || i18nModulesArr.length <= 0 ) {
+ const { loaderModule, i18nModulesArr, chunkGraph } = getStuff();
+ if ( ! loaderModule || i18nModulesArr.length <= 0 ) {
return;
}
debug( 'After optimizations,' );
for ( const chunk of chunks ) {
- if ( chunkGraph.isModuleInChunk( stateModule, chunk ) ) {
+ if ( chunkGraph.isModuleInChunk( loaderModule, chunk ) ) {
debug(
// prettier-ignore
- ` ✅ Chunk ${ chunk.name || chunk.id || chunk.debugId } contains ${ this.options.stateModule }`
+ ` ✅ Chunk ${ chunk.name || chunk.id || chunk.debugId } contains ${ this.options.loaderModule }`
);
} else {
debug(
// prettier-ignore
- ` ❌ Chunk ${ chunk.name || chunk.id || chunk.debugId } does not contain ${ this.options.stateModule }`
+ ` ❌ Chunk ${ chunk.name || chunk.id || chunk.debugId } does not contain ${ this.options.loaderModule }`
);
}
if ( i18nModulesArr.some( m => chunkGraph.isModuleInChunk( m, chunk ) ) ) {
@@ -317,18 +287,19 @@ class I18nLoaderPlugin {
compilation.hooks.runtimeRequirementInTree
.for( webpack.RuntimeGlobals.ensureChunkHandlers )
.tap( PLUGIN_NAME, ( chunk, set ) => {
- const { stateModule, i18nModulesArr } = getStuff();
- if ( ! stateModule || i18nModulesArr.length <= 0 ) {
+ const { loaderModule, i18nModulesArr } = getStuff();
+ if ( ! loaderModule || i18nModulesArr.length <= 0 ) {
return;
}
debug( `Queuing runtime module for ${ chunk.name || chunk.id || chunk.debugId }.` );
+ set.add( webpack.RuntimeGlobals.getChunkScriptFilename );
compilation.addRuntimeModule(
chunk,
new I18nLoaderRuntimeModule( set, {
...this.options,
- stateModuleName: this.options.stateModule,
- stateModule,
+ loaderModuleName: this.options.loaderModule,
+ loaderModule,
i18nModulesArr,
} )
);
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/src/I18nLoaderRuntimeModule.js b/projects/js-packages/i18n-loader-webpack-plugin/src/I18nLoaderRuntimeModule.js
index 273a18f7c207d..bb57a9b381c6b 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/src/I18nLoaderRuntimeModule.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/src/I18nLoaderRuntimeModule.js
@@ -1,4 +1,3 @@
-const { default: md5 } = require( 'md5-es' );
const path = require( 'path' );
const webpack = require( 'webpack' );
const { Template, RuntimeGlobals } = webpack;
@@ -44,12 +43,12 @@ class I18nLoaderRuntimeModule extends webpack.RuntimeModule {
}
/**
- * Get info for chunks we need to care about.
+ * Get set of chunks we need to care about.
*
- * @returns {Map} Map of chunk ID to data.
+ * @returns {Set} Chunk IDs.
*/
- getChunkInfo() {
- const ret = new Map();
+ getChunks() {
+ const ret = new Set();
const { chunkGraph } = this.compilation;
for ( const chunk of this.chunk.getAllAsyncChunks() ) {
@@ -61,12 +60,7 @@ class I18nLoaderRuntimeModule extends webpack.RuntimeModule {
module.userRequest === '@wordpress/i18n' ||
module.dependencies.some( d => d.request === '@wordpress/i18n' )
) {
- const [ chunkPath, query ] = this.getChunkPath( chunk ).split( '?', 2 );
- ret.set( chunk.id, {
- chunkPath,
- query,
- hash: md5.hash( chunkPath ),
- } );
+ ret.add( chunk.id );
continue;
}
}
@@ -76,34 +70,24 @@ class I18nLoaderRuntimeModule extends webpack.RuntimeModule {
generate() {
const { chunk, compilation, runtimeOptions, runtimeRequirements } = this;
- const { stateModule, stateModuleName, i18nModulesArr, textdomain } = runtimeOptions;
+ const { loaderModule, loaderMethod, loaderModuleName, textdomain, target } = runtimeOptions;
const { chunkGraph, runtimeTemplate } = compilation;
- const chunkInfo = this.getChunkInfo();
+ const basepath =
+ this.runtimeOptions.path ||
+ path.relative( compilation.compiler.context, compilation.outputOptions.path );
+ const chunks = this.getChunks();
- if ( ! chunkInfo.size ) {
+ if ( ! chunks.size ) {
debug( `No async submodules using @wordpress/i18n in ${ this.getChunkPath( chunk ) }.` );
return null;
}
debug( `Adding i18n-loading runtime for ${ this.getChunkPath( chunk ) }.` );
- // The hooks should have injected the state and i18n modules. Check to make sure they're still there.
- if ( ! chunkGraph.isModuleInChunk( stateModule, chunk ) ) {
- throw new webpack.WebpackError(
- // prettier-ignore
- `Chunk ${ chunk.name || chunk.id || chunk.debugId } (${ this.getChunkPath( chunk ) }) has submodules using @wordpress/i18n, but is missing the state module ${ stateModuleName }.`
- );
- }
- let i18nModule;
- for ( const m of i18nModulesArr ) {
- if ( chunkGraph.isModuleInChunk( m, chunk ) ) {
- i18nModule = m;
- break;
- }
- }
- if ( ! i18nModule ) {
+ // The hooks should have injected the loader module. Check to make sure it's still there.
+ if ( ! chunkGraph.isModuleInChunk( loaderModule, chunk ) ) {
throw new webpack.WebpackError(
// prettier-ignore
- `Chunk ${ chunk.name || chunk.id || chunk.debugId } (${ this.getChunkPath( chunk ) }) has submodules using @wordpress/i18n, but is itself missing @wordpress/i18n.`
+ `Chunk ${ chunk.name || chunk.id || chunk.debugId } (${ this.getChunkPath( chunk ) }) has submodules using @wordpress/i18n, but is missing the loader module ${ loaderModuleName }.`
);
}
@@ -116,82 +100,46 @@ class I18nLoaderRuntimeModule extends webpack.RuntimeModule {
}
// Determine the WordPress module name that @wordpress/dependency-extraction-webpack-plugin will (by default) use.
- let depName = stateModuleName;
+ let depName = loaderModuleName;
if ( depName.startsWith( '@wordpress/' ) ) {
// prettier-ignore
depName = 'wp.' + depName.substring( 11 ).replace( /-([a-z])/g, ( _, letter ) => letter.toUpperCase() );
}
- const targetcode = {
- plugin: '"plugins/" + ',
- theme: '"themes/" + ',
- core: '',
- }[ this.runtimeOptions.target ];
- const domaincode = `state.domainMap[textdomain] || ( ${ targetcode }textdomain )`;
-
// Build the runtime code.
// prettier-ignore
return Template.asString( [
- `var textdomain = ${ JSON.stringify( textdomain ) };`,
- 'var chunkInfo = {',
- Template.indent(
- Array.from( chunkInfo.entries(), ( [ k, v ] ) => {
- const items = [
- Template.toNormalComment( v.chunkPath ) + ' ' + JSON.stringify( v.hash ),
- v.query ? JSON.stringify( '?' + v.query ) : '""',
- ];
- return `${ JSON.stringify( k ) }: [ ${ items.join( ', ' ) } ]`;
- } ).join( ',\n' )
- ),
+ 'var installedChunks = {',
+ Template.indent( Array.from( chunks.values(), id => `${ JSON.stringify( id ) }: 0` ).join( ',\n' ) ),
'};',
'',
'var loadI18n = ' +
- runtimeTemplate.basicFunction( 'info', [
- 'var i18n = ' + runtimeTemplate.moduleExports( {
- module: i18nModule,
- chunkGraph,
- request: '@wordpress/i18n',
- runtimeRequirements,
- } ) + ';',
- 'var state = ' + runtimeTemplate.moduleExports( {
- module: stateModule,
+ runtimeTemplate.basicFunction( 'chunkId', [
+ 'var loader = ' + runtimeTemplate.moduleExports( {
+ module: loaderModule,
chunkGraph,
- request: stateModuleName,
+ request: loaderModuleName,
runtimeRequirements,
} ) + ';',
- `if ( ! state ) return Promise.reject( new Error( ${ JSON.stringify( 'I18n state is not available. Check that WordPress is exporting ' + depName + '.' ) } ) );`,
- `if ( state.locale === "en_US" ) return Promise.resolve();`,
- 'if ( typeof fetch === "undefined" ) return Promise.reject( new Error( "Fetch API is not available." ) );',
- 'return fetch(',
- Template.indent( [
- runtimeTemplate.supportTemplateLiteral()
- ? '`${ state.baseUrl }${ ' + domaincode + ' }-${ state.locale }-${ info[0] }.json${ info[1] }`'
- : 'state.baseUrl + ( ' + domaincode + ' ) + "-" + state.locale + "-" + info[0] + ".json" + info[1]',
- ] ),
- ').then( ' + runtimeTemplate.basicFunction( 'res', [
- 'if ( ! res.ok ) throw new Error( "HTTP request failed: " + res.status + " " + res.statusText );',
- 'return res.json();',
- ] ) + ' ).then( ' + runtimeTemplate.basicFunction( 'data', [
- 'var data2 = data.locale_data;',
- 'var localeData = data2[ textdomain ] || data2.messages;',
- 'localeData[""].domain = textdomain;',
- 'i18n.setLocaleData( localeData, textdomain );',
- ] ) + ' );',
+ `if ( loader && loader.${ loaderMethod } )`,
+ Template.indent(
+ `return loader.${ loaderMethod }( ${ JSON.stringify( basepath + '/' ) } + ${ RuntimeGlobals.getChunkScriptFilename }( chunkId ), ${ JSON.stringify( textdomain ) }, ${ JSON.stringify( target ) } );`,
+ ),
+ `return Promise.reject( new Error( ${ JSON.stringify( 'I18n loader is not available. Check that WordPress is exporting ' + depName + '.' ) } ) );`,
] ) + ';',
'',
- 'var installedChunks = {};',
`${ RuntimeGlobals.ensureChunkHandlers }.wpI18n = ` + runtimeTemplate.basicFunction( 'chunkId, promises', [
'if ( installedChunks[chunkId] ) {',
Template.indent( 'promises.push( installedChunks[chunkId] );' ),
- '} else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {',
+ '} else if ( installedChunks[chunkId] === 0 ) {',
Template.indent( [
- 'promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then( ',
+ 'promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then( ',
Template.indent( [
- runtimeTemplate.basicFunction( '', [ 'installedChunks[chunkId] = 0;' ] ) + ',',
+ runtimeTemplate.basicFunction( '', [ 'installedChunks[chunkId] = false;' ] ) + ',',
runtimeTemplate.basicFunction( 'e', [
- 'delete installedChunks[chunkId];',
+ 'installedChunks[chunkId] = 0;',
"// Log only, we don't want i18n failure to break the entire page.",
- 'console.error( "Failed to fetch i18n data:", e );',
+ 'console.error( "Failed to fetch i18n data: ", e );',
] ),
] ),
') );',
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/__snapshots__/build.test.js.snap b/projects/js-packages/i18n-loader-webpack-plugin/tests/__snapshots__/build.test.js.snap
index b2d4005a0cc8d..8058cc6db87a4 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/__snapshots__/build.test.js.snap
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/__snapshots__/build.test.js.snap
@@ -8,88 +8,61 @@ Object {
"bar.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"baz.asset.php": Object {},
"baz.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"hasI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"main.asset.php": Object {},
"main.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!*************************************!*\\\\
- !*** external [\\"wp\\",\\"jpI18nState\\"] ***!
- \\\\*************************************/
+ "jpI18nState": "/***/ \\"@wordpress/jp-i18n-loader\\":
+/*!**************************************!*\\\\
+ !*** external [\\"wp\\",\\"jpI18nLoader\\"] ***!
+ \\\\**************************************/
/***/ ((module) => {
\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"jpI18nState\\"];
+module.exports = window[\\"wp\\"][\\"jpI18nLoader\\"];
",
"loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
/******/ (() => {
-/******/ var textdomain = \\"basic-test\\";
-/******/ var chunkInfo = {
-/******/ \\"hasI18n\\": [ /* dist/hasI18n.js */ \\"c7c3968298452ee95564fb9c05e2de50\\", \\"\\" ]
+/******/ var installedChunks = {
+/******/ \\"hasI18n\\": 0
/******/ };
/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! @wordpress/jp-i18n-loader */ \\"@wordpress/jp-i18n-loader\\");
+/******/ if ( loader && loader.downloadI18n )
+/******/ return loader.downloadI18n( \\"dist/\\" + __webpack_require__.u( chunkId ), \\"basic-test\\", \\"plugin\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.\\" ) );
/******/ };
/******/
-/******/ var installedChunks = {};
/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
/******/ if ( installedChunks[chunkId] ) {
/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
/******/ () => {
-/******/ installedChunks[chunkId] = 0;
+/******/ installedChunks[chunkId] = false;
/******/ },
/******/ (e) => {
-/******/ delete installedChunks[chunkId];
+/******/ installedChunks[chunkId] = 0;
/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
/******/ }
/******/ ) );
/******/ }
/******/ };
/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
},
"noI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
}
`;
@@ -101,90 +74,6 @@ Object {
}
`;
-exports[`Webpack \`fetch-failures\` Build results: Webpack build error 1`] = `null`;
-
-exports[`Webpack \`fetch-failures\` Build results: Webpack build files 1`] = `
-Object {
- "a.asset.php": Object {},
- "a.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!*************************************!*\\\\
- !*** external [\\"wp\\",\\"jpI18nState\\"] ***!
- \\\\*************************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"jpI18nState\\"];
-",
- "loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
-/******/ (() => {
-/******/ var textdomain = \\"fetch-failures\\";
-/******/ var chunkInfo = {
-/******/ \\"hasI18n\\": [ /* dist/hasI18n.js */ \\"c7c3968298452ee95564fb9c05e2de50\\", \\"\\" ]
-/******/ };
-/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
-/******/ };
-/******/
-/******/ var installedChunks = {};
-/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
-/******/ if ( installedChunks[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
-/******/ () => {
-/******/ installedChunks[chunkId] = 0;
-/******/ },
-/******/ (e) => {
-/******/ delete installedChunks[chunkId];
-/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
-/******/ }
-/******/ ) );
-/******/ }
-/******/ };
-/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
- },
- "hasI18n.js": Object {
- "jpI18nState": null,
- "loader": null,
- "wpI18n": null,
- },
-}
-`;
-
-exports[`Webpack \`fetch-failures\` Build results: Webpack build stats 1`] = `
-Object {
- "errors": Array [],
- "warnings": Array [],
-}
-`;
-
exports[`Webpack \`ignore-modules\` Build results: Webpack build error 1`] = `null`;
exports[`Webpack \`ignore-modules\` Build results: Webpack build files 1`] = `
@@ -192,398 +81,238 @@ Object {
"array/hasI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"array/hasI18n2.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"array/main.asset.php": Object {},
"array/main.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
},
"array/main2.asset.php": Object {},
"array/main2.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!*************************************!*\\\\
- !*** external [\\"wp\\",\\"jpI18nState\\"] ***!
- \\\\*************************************/
+ "jpI18nState": "/***/ \\"@wordpress/jp-i18n-loader\\":
+/*!**************************************!*\\\\
+ !*** external [\\"wp\\",\\"jpI18nLoader\\"] ***!
+ \\\\**************************************/
/***/ ((module) => {
\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"jpI18nState\\"];
+module.exports = window[\\"wp\\"][\\"jpI18nLoader\\"];
",
"loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
/******/ (() => {
-/******/ var textdomain = \\"ignore-modules\\";
-/******/ var chunkInfo = {
-/******/ \\"hasI18n2\\": [ /* dist/array/hasI18n2.js */ \\"e4f47276932fd5fd64de200952c28fa0\\", \\"\\" ]
+/******/ var installedChunks = {
+/******/ \\"hasI18n2\\": 0
/******/ };
/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! @wordpress/jp-i18n-loader */ \\"@wordpress/jp-i18n-loader\\");
+/******/ if ( loader && loader.downloadI18n )
+/******/ return loader.downloadI18n( \\"dist/\\" + __webpack_require__.u( chunkId ), \\"ignore-modules\\", \\"plugin\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.\\" ) );
/******/ };
/******/
-/******/ var installedChunks = {};
/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
/******/ if ( installedChunks[chunkId] ) {
/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
/******/ () => {
-/******/ installedChunks[chunkId] = 0;
+/******/ installedChunks[chunkId] = false;
/******/ },
/******/ (e) => {
-/******/ delete installedChunks[chunkId];
+/******/ installedChunks[chunkId] = 0;
/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
/******/ }
/******/ ) );
/******/ }
/******/ };
/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
- },
- "array/md5.js": Object {
- "jpI18nState": null,
- "loader": null,
- "wpI18n": null,
},
"array/noI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"function/hasI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"function/hasI18n2.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"function/main.asset.php": Object {},
"function/main.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
},
"function/main2.asset.php": Object {},
"function/main2.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!*************************************!*\\\\
- !*** external [\\"wp\\",\\"jpI18nState\\"] ***!
- \\\\*************************************/
+ "jpI18nState": "/***/ \\"@wordpress/jp-i18n-loader\\":
+/*!**************************************!*\\\\
+ !*** external [\\"wp\\",\\"jpI18nLoader\\"] ***!
+ \\\\**************************************/
/***/ ((module) => {
\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"jpI18nState\\"];
+module.exports = window[\\"wp\\"][\\"jpI18nLoader\\"];
",
"loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
/******/ (() => {
-/******/ var textdomain = \\"ignore-modules\\";
-/******/ var chunkInfo = {
-/******/ \\"hasI18n2\\": [ /* dist/function/hasI18n2.js */ \\"2f34b700832755e4f97db2c8639d355f\\", \\"\\" ]
+/******/ var installedChunks = {
+/******/ \\"hasI18n2\\": 0
/******/ };
/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! @wordpress/jp-i18n-loader */ \\"@wordpress/jp-i18n-loader\\");
+/******/ if ( loader && loader.downloadI18n )
+/******/ return loader.downloadI18n( \\"dist/\\" + __webpack_require__.u( chunkId ), \\"ignore-modules\\", \\"plugin\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.\\" ) );
/******/ };
/******/
-/******/ var installedChunks = {};
/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
/******/ if ( installedChunks[chunkId] ) {
/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
/******/ () => {
-/******/ installedChunks[chunkId] = 0;
+/******/ installedChunks[chunkId] = false;
/******/ },
/******/ (e) => {
-/******/ delete installedChunks[chunkId];
+/******/ installedChunks[chunkId] = 0;
/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
/******/ }
/******/ ) );
/******/ }
/******/ };
/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
- },
- "function/md5.js": Object {
- "jpI18nState": null,
- "loader": null,
- "wpI18n": null,
},
"function/noI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"regex/hasI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"regex/hasI18n2.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"regex/main.asset.php": Object {},
"regex/main.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
},
"regex/main2.asset.php": Object {},
"regex/main2.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!*************************************!*\\\\
- !*** external [\\"wp\\",\\"jpI18nState\\"] ***!
- \\\\*************************************/
+ "jpI18nState": "/***/ \\"@wordpress/jp-i18n-loader\\":
+/*!**************************************!*\\\\
+ !*** external [\\"wp\\",\\"jpI18nLoader\\"] ***!
+ \\\\**************************************/
/***/ ((module) => {
\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"jpI18nState\\"];
+module.exports = window[\\"wp\\"][\\"jpI18nLoader\\"];
",
"loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
/******/ (() => {
-/******/ var textdomain = \\"ignore-modules\\";
-/******/ var chunkInfo = {
-/******/ \\"hasI18n2\\": [ /* dist/regex/hasI18n2.js */ \\"b9b66de4c25a21524d7efa38f93bbf38\\", \\"\\" ]
+/******/ var installedChunks = {
+/******/ \\"hasI18n2\\": 0
/******/ };
/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! @wordpress/jp-i18n-loader */ \\"@wordpress/jp-i18n-loader\\");
+/******/ if ( loader && loader.downloadI18n )
+/******/ return loader.downloadI18n( \\"dist/\\" + __webpack_require__.u( chunkId ), \\"ignore-modules\\", \\"plugin\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.\\" ) );
/******/ };
/******/
-/******/ var installedChunks = {};
/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
/******/ if ( installedChunks[chunkId] ) {
/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
/******/ () => {
-/******/ installedChunks[chunkId] = 0;
+/******/ installedChunks[chunkId] = false;
/******/ },
/******/ (e) => {
-/******/ delete installedChunks[chunkId];
+/******/ installedChunks[chunkId] = 0;
/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
/******/ }
/******/ ) );
/******/ }
/******/ };
/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
- },
- "regex/md5.js": Object {
- "jpI18nState": null,
- "loader": null,
- "wpI18n": null,
},
"regex/noI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"string/hasI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"string/hasI18n2.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"string/main.asset.php": Object {},
"string/main.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
},
"string/main2.asset.php": Object {},
"string/main2.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!*************************************!*\\\\
- !*** external [\\"wp\\",\\"jpI18nState\\"] ***!
- \\\\*************************************/
+ "jpI18nState": "/***/ \\"@wordpress/jp-i18n-loader\\":
+/*!**************************************!*\\\\
+ !*** external [\\"wp\\",\\"jpI18nLoader\\"] ***!
+ \\\\**************************************/
/***/ ((module) => {
\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"jpI18nState\\"];
+module.exports = window[\\"wp\\"][\\"jpI18nLoader\\"];
",
"loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
/******/ (() => {
-/******/ var textdomain = \\"ignore-modules\\";
-/******/ var chunkInfo = {
-/******/ \\"hasI18n2\\": [ /* dist/string/hasI18n2.js */ \\"8ccd788351d9b86e5ae5a8a7bb8acd02\\", \\"\\" ]
+/******/ var installedChunks = {
+/******/ \\"hasI18n2\\": 0
/******/ };
/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! @wordpress/jp-i18n-loader */ \\"@wordpress/jp-i18n-loader\\");
+/******/ if ( loader && loader.downloadI18n )
+/******/ return loader.downloadI18n( \\"dist/\\" + __webpack_require__.u( chunkId ), \\"ignore-modules\\", \\"plugin\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.\\" ) );
/******/ };
/******/
-/******/ var installedChunks = {};
/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
/******/ if ( installedChunks[chunkId] ) {
/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
/******/ () => {
-/******/ installedChunks[chunkId] = 0;
+/******/ installedChunks[chunkId] = false;
/******/ },
/******/ (e) => {
-/******/ delete installedChunks[chunkId];
+/******/ installedChunks[chunkId] = 0;
/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
/******/ }
/******/ ) );
/******/ }
/******/ };
/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
- },
- "string/md5.js": Object {
- "jpI18nState": null,
- "loader": null,
- "wpI18n": null,
},
"string/noI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
}
`;
@@ -617,210 +346,114 @@ Object {
}
`;
-exports[`Webpack \`manual-externals\` Build results: Webpack build error 1`] = `null`;
+exports[`Webpack \`loader-failures\` Build results: Webpack build error 1`] = `null`;
-exports[`Webpack \`manual-externals\` Build results: Webpack build files 1`] = `
+exports[`Webpack \`loader-failures\` Build results: Webpack build files 1`] = `
Object {
- "hasI18n.js": Object {
- "jpI18nState": null,
- "loader": null,
- "wpI18n": null,
- },
- "main.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!******************************!*\\\\
- !*** external \\"jpI18nState\\" ***!
- \\\\******************************/
+ "a.asset.php": Object {},
+ "a.js": Object {
+ "jpI18nState": "/***/ \\"@wordpress/jp-i18n-loader\\":
+/*!**************************************!*\\\\
+ !*** external [\\"wp\\",\\"jpI18nLoader\\"] ***!
+ \\\\**************************************/
/***/ ((module) => {
\\"use strict\\";
-module.exports = global[\\"jpI18nState\\"];
+module.exports = window[\\"wp\\"][\\"jpI18nLoader\\"];
",
"loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
/******/ (() => {
-/******/ var textdomain = \\"manual-externals\\";
-/******/ var chunkInfo = {
-/******/ \\"hasI18n\\": [ /* dist/hasI18n.js */ \\"c7c3968298452ee95564fb9c05e2de50\\", \\"\\" ]
+/******/ var installedChunks = {
+/******/ \\"hasI18n\\": 0
/******/ };
/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! @wordpress/jp-i18n-loader */ \\"@wordpress/jp-i18n-loader\\");
+/******/ if ( loader && loader.downloadI18n )
+/******/ return loader.downloadI18n( \\"dist/\\" + __webpack_require__.u( chunkId ), \\"loader-failures\\", \\"plugin\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.\\" ) );
/******/ };
/******/
-/******/ var installedChunks = {};
/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
/******/ if ( installedChunks[chunkId] ) {
/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
/******/ () => {
-/******/ installedChunks[chunkId] = 0;
+/******/ installedChunks[chunkId] = false;
/******/ },
/******/ (e) => {
-/******/ delete installedChunks[chunkId];
+/******/ installedChunks[chunkId] = 0;
/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
/******/ }
/******/ ) );
/******/ }
/******/ };
/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!*************************!*\\\\
- !*** external \\"wpI18n\\" ***!
- \\\\*************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = global[\\"wpI18n\\"];
-",
},
- "main2.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!**********************************************************************************************!*\\\\
- !*** external \\"{\\\\\\"baseUrl\\\\\\":\\\\\\"http://example.org/\\\\\\",\\\\\\"locale\\\\\\":\\\\\\"en_US\\\\\\",\\\\\\"domainMap\\\\\\":{}}\\" ***!
- \\\\**********************************************************************************************/
-/***/ ((module) => {
+ "hasI18n.js": Object {
+ "jpI18nState": null,
+ "loader": null,
+ },
+}
+`;
-\\"use strict\\";
-if(typeof {\\"baseUrl\\":\\"http://example.org/\\",\\"locale\\":\\"en_US\\",\\"domainMap\\":{}} === 'undefined') { var e = new Error(\\"Cannot find module '{\\\\\\"baseUrl\\\\\\":\\\\\\"http://example.org/\\\\\\",\\\\\\"locale\\\\\\":\\\\\\"en_US\\\\\\",\\\\\\"domainMap\\\\\\":{}}'\\"); e.code = 'MODULE_NOT_FOUND'; throw e; }
+exports[`Webpack \`loader-failures\` Build results: Webpack build stats 1`] = `
+Object {
+ "errors": Array [],
+ "warnings": Array [],
+}
+`;
-module.exports = {\\"baseUrl\\":\\"http://example.org/\\",\\"locale\\":\\"en_US\\",\\"domainMap\\":{}};
-",
- "loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
-/******/ (() => {
-/******/ var textdomain = \\"manual-externals\\";
-/******/ var chunkInfo = {
-/******/ \\"hasI18n\\": [ /* dist/hasI18n.js */ \\"c7c3968298452ee95564fb9c05e2de50\\", \\"\\" ]
-/******/ };
-/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
-/******/ };
-/******/
-/******/ var installedChunks = {};
-/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
-/******/ if ( installedChunks[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
-/******/ () => {
-/******/ installedChunks[chunkId] = 0;
-/******/ },
-/******/ (e) => {
-/******/ delete installedChunks[chunkId];
-/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
-/******/ }
-/******/ ) );
-/******/ }
-/******/ };
-/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!*************************!*\\\\
- !*** external \\"wpI18n\\" ***!
- \\\\*************************/
-/***/ ((module) => {
+exports[`Webpack \`manual-externals\` Build results: Webpack build error 1`] = `null`;
-\\"use strict\\";
-module.exports = global[\\"wpI18n\\"];
-",
+exports[`Webpack \`manual-externals\` Build results: Webpack build files 1`] = `
+Object {
+ "hasI18n.js": Object {
+ "jpI18nState": null,
+ "loader": null,
},
- "main3.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!**************************************************************************************************************************************!*\\\\
- !*** external \\"{\\\\\\"baseUrl\\\\\\":\\\\\\"http://example.org/\\\\\\",\\\\\\"locale\\\\\\":\\\\\\"en_us\\\\\\",\\\\\\"domainMap\\\\\\":{\\\\\\"manual-externals\\\\\\":\\\\\\"themes/remapped\\\\\\"}}\\" ***!
- \\\\**************************************************************************************************************************************/
+ "main.js": Object {
+ "jpI18nState": "/***/ \\"@wordpress/jp-i18n-loader\\":
+/*!*******************************!*\\\\
+ !*** external \\"jpI18nLoader\\" ***!
+ \\\\*******************************/
/***/ ((module) => {
\\"use strict\\";
-if(typeof {\\"baseUrl\\":\\"http://example.org/\\",\\"locale\\":\\"en_us\\",\\"domainMap\\":{\\"manual-externals\\":\\"themes/remapped\\"}} === 'undefined') { var e = new Error(\\"Cannot find module '{\\\\\\"baseUrl\\\\\\":\\\\\\"http://example.org/\\\\\\",\\\\\\"locale\\\\\\":\\\\\\"en_us\\\\\\",\\\\\\"domainMap\\\\\\":{\\\\\\"manual-externals\\\\\\":\\\\\\"themes/remapped\\\\\\"}}'\\"); e.code = 'MODULE_NOT_FOUND'; throw e; }
-
-module.exports = {\\"baseUrl\\":\\"http://example.org/\\",\\"locale\\":\\"en_us\\",\\"domainMap\\":{\\"manual-externals\\":\\"themes/remapped\\"}};
+module.exports = global[\\"jpI18nLoader\\"];
",
"loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
/******/ (() => {
-/******/ var textdomain = \\"manual-externals\\";
-/******/ var chunkInfo = {
-/******/ \\"hasI18n\\": [ /* dist/hasI18n.js */ \\"c7c3968298452ee95564fb9c05e2de50\\", \\"\\" ]
+/******/ var installedChunks = {
+/******/ \\"hasI18n\\": 0
/******/ };
/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! @wordpress/jp-i18n-loader */ \\"@wordpress/jp-i18n-loader\\");
+/******/ if ( loader && loader.downloadI18n )
+/******/ return loader.downloadI18n( \\"dist/\\" + __webpack_require__.u( chunkId ), \\"manual-externals\\", \\"plugin\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.\\" ) );
/******/ };
/******/
-/******/ var installedChunks = {};
/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
/******/ if ( installedChunks[chunkId] ) {
/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
/******/ () => {
-/******/ installedChunks[chunkId] = 0;
+/******/ installedChunks[chunkId] = false;
/******/ },
/******/ (e) => {
-/******/ delete installedChunks[chunkId];
+/******/ installedChunks[chunkId] = 0;
/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
/******/ }
/******/ ) );
/******/ }
/******/ };
/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!*************************!*\\\\
- !*** external \\"wpI18n\\" ***!
- \\\\*************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = global[\\"wpI18n\\"];
-",
},
}
`;
@@ -833,16 +466,6 @@ Object {
"name": undefined,
"warnings": Array [],
},
- Object {
- "errors": Array [],
- "name": undefined,
- "warnings": Array [],
- },
- Object {
- "errors": Array [],
- "name": undefined,
- "warnings": Array [],
- },
],
"errors": Array [],
"warnings": Array [],
@@ -853,17 +476,64 @@ exports[`Webpack \`missing-externals\` Build results: Webpack build error 1`] =
exports[`Webpack \`missing-externals\` Build results: Webpack build stats 1`] = `
Object {
+ "children": Array [
+ Object {
+ "errors": Array [
+ Object {
+ "message": "@automattic/i18n-loader-webpack-plugin:
+Failed to add loader module @wordpress/jp-i18n-loader to the build.
+You'll need to add @wordpress/dependency-extraction-webpack-plugin or an appropriate externals directive to your Webpack config.",
+ },
+ ],
+ "name": undefined,
+ "warnings": Array [
+ Object {
+ "loc": "",
+ "message": "Module not found: Error: Can't resolve '@wordpress/jp-i18n-loader' in '/path/to/i18n-loader-webpack-plugin/tests/fixtures/missing-externals'",
+ },
+ ],
+ },
+ Object {
+ "errors": Array [
+ Object {
+ "message": "@automattic/i18n-loader-webpack-plugin:
+Failed to add loader module missingloader to the build.
+You'll need to add the appropriate externals directive to your Webpack config.",
+ },
+ ],
+ "name": undefined,
+ "warnings": Array [
+ Object {
+ "loc": "",
+ "message": "Module not found: Error: Can't resolve 'missingloader' in '/path/to/i18n-loader-webpack-plugin/tests/fixtures/missing-externals'",
+ },
+ ],
+ },
+ ],
"errors": Array [
Object {
+ "compilerPath": undefined,
"message": "@automattic/i18n-loader-webpack-plugin:
-Failed to add state module @wordpress/jp-i18n-state to the build.
+Failed to add loader module @wordpress/jp-i18n-loader to the build.
You'll need to add @wordpress/dependency-extraction-webpack-plugin or an appropriate externals directive to your Webpack config.",
},
+ Object {
+ "compilerPath": undefined,
+ "message": "@automattic/i18n-loader-webpack-plugin:
+Failed to add loader module missingloader to the build.
+You'll need to add the appropriate externals directive to your Webpack config.",
+ },
],
"warnings": Array [
Object {
+ "compilerPath": undefined,
+ "loc": "",
+ "message": "Module not found: Error: Can't resolve '@wordpress/jp-i18n-loader' in '/path/to/i18n-loader-webpack-plugin/tests/fixtures/missing-externals'",
+ },
+ Object {
+ "compilerPath": undefined,
"loc": "",
- "message": "Module not found: Error: Can't resolve '@wordpress/jp-i18n-state' in '/path/to/i18n-loader-webpack-plugin/tests/fixtures/missing-externals'",
+ "message": "Module not found: Error: Can't resolve 'missingloader' in '/path/to/i18n-loader-webpack-plugin/tests/fixtures/missing-externals'",
},
],
}
@@ -873,179 +543,126 @@ exports[`Webpack \`multiple-runtime\` Build results: Webpack build error 1`] = `
exports[`Webpack \`multiple-runtime\` Build results: Webpack build files 1`] = `
Object {
+ "bar.asset.php": Object {},
"bar.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
+ "baz.asset.php": Object {},
"baz.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"hasI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"hasI18n2.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
+ "main.asset.php": Object {},
"main.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
+ "main2.asset.php": Object {},
"main2.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"noI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
- "runtime~bar.asset.php": Object {},
"runtime~bar.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
- "runtime~baz.asset.php": Object {},
"runtime~baz.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
- "runtime~main.asset.php": Object {},
"runtime~main.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!*************************************!*\\\\
- !*** external [\\"wp\\",\\"jpI18nState\\"] ***!
- \\\\*************************************/
+ "jpI18nState": "/***/ \\"@wordpress/jp-i18n-loader\\":
+/*!**************************************!*\\\\
+ !*** external [\\"wp\\",\\"jpI18nLoader\\"] ***!
+ \\\\**************************************/
/***/ ((module) => {
-module.exports = window[\\"wp\\"][\\"jpI18nState\\"];
+module.exports = window[\\"wp\\"][\\"jpI18nLoader\\"];
",
"loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
/******/ (() => {
-/******/ var textdomain = \\"multiple-runtime\\";
-/******/ var chunkInfo = {
-/******/ \\"hasI18n\\": [ /* dist/hasI18n.js */ \\"c7c3968298452ee95564fb9c05e2de50\\", \\"\\" ]
+/******/ var installedChunks = {
+/******/ \\"hasI18n\\": 0
/******/ };
/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! @wordpress/jp-i18n-loader */ \\"@wordpress/jp-i18n-loader\\");
+/******/ if ( loader && loader.downloadI18n )
+/******/ return loader.downloadI18n( \\"dist/\\" + __webpack_require__.u( chunkId ), \\"multiple-runtime\\", \\"plugin\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.\\" ) );
/******/ };
/******/
-/******/ var installedChunks = {};
/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
/******/ if ( installedChunks[chunkId] ) {
/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
/******/ () => {
-/******/ installedChunks[chunkId] = 0;
+/******/ installedChunks[chunkId] = false;
/******/ },
/******/ (e) => {
-/******/ delete installedChunks[chunkId];
+/******/ installedChunks[chunkId] = 0;
/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
/******/ }
/******/ ) );
/******/ }
/******/ };
/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
},
- "runtime~main2.asset.php": Object {},
"runtime~main2.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!*************************************!*\\\\
- !*** external [\\"wp\\",\\"jpI18nState\\"] ***!
- \\\\*************************************/
+ "jpI18nState": "/***/ \\"@wordpress/jp-i18n-loader\\":
+/*!**************************************!*\\\\
+ !*** external [\\"wp\\",\\"jpI18nLoader\\"] ***!
+ \\\\**************************************/
/***/ ((module) => {
-module.exports = window[\\"wp\\"][\\"jpI18nState\\"];
+module.exports = window[\\"wp\\"][\\"jpI18nLoader\\"];
",
"loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
/******/ (() => {
-/******/ var textdomain = \\"multiple-runtime\\";
-/******/ var chunkInfo = {
-/******/ \\"hasI18n\\": [ /* dist/hasI18n.js */ \\"c7c3968298452ee95564fb9c05e2de50\\", \\"\\" ],
-/******/ \\"hasI18n2\\": [ /* dist/hasI18n2.js */ \\"71d7bd53fc0961d2785c69cde5d9fead\\", \\"\\" ]
+/******/ var installedChunks = {
+/******/ \\"hasI18n\\": 0,
+/******/ \\"hasI18n2\\": 0
/******/ };
/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! @wordpress/jp-i18n-loader */ \\"@wordpress/jp-i18n-loader\\");
+/******/ if ( loader && loader.downloadI18n )
+/******/ return loader.downloadI18n( \\"dist/\\" + __webpack_require__.u( chunkId ), \\"multiple-runtime\\", \\"plugin\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.\\" ) );
/******/ };
/******/
-/******/ var installedChunks = {};
/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
/******/ if ( installedChunks[chunkId] ) {
/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
/******/ () => {
-/******/ installedChunks[chunkId] = 0;
+/******/ installedChunks[chunkId] = false;
/******/ },
/******/ (e) => {
-/******/ delete installedChunks[chunkId];
+/******/ installedChunks[chunkId] = 0;
/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
/******/ }
/******/ ) );
/******/ }
/******/ };
/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
},
}
`;
@@ -1064,82 +681,56 @@ Object {
"hasI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"indirect1.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"indirect2.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"main.asset.php": Object {},
"main.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!*************************************!*\\\\
- !*** external [\\"wp\\",\\"jpI18nState\\"] ***!
- \\\\*************************************/
+ "jpI18nState": "/***/ \\"@wordpress/jp-i18n-loader\\":
+/*!**************************************!*\\\\
+ !*** external [\\"wp\\",\\"jpI18nLoader\\"] ***!
+ \\\\**************************************/
/***/ ((module) => {
\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"jpI18nState\\"];
+module.exports = window[\\"wp\\"][\\"jpI18nLoader\\"];
",
"loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
/******/ (() => {
-/******/ var textdomain = \\"nested-includes\\";
-/******/ var chunkInfo = {
-/******/ \\"hasI18n\\": [ /* dist/hasI18n.js */ \\"c7c3968298452ee95564fb9c05e2de50\\", \\"\\" ]
+/******/ var installedChunks = {
+/******/ \\"hasI18n\\": 0
/******/ };
/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! @wordpress/jp-i18n-loader */ \\"@wordpress/jp-i18n-loader\\");
+/******/ if ( loader && loader.downloadI18n )
+/******/ return loader.downloadI18n( \\"dist/\\" + __webpack_require__.u( chunkId ), \\"nested-includes\\", \\"plugin\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.\\" ) );
/******/ };
/******/
-/******/ var installedChunks = {};
/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
/******/ if ( installedChunks[chunkId] ) {
/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
/******/ () => {
-/******/ installedChunks[chunkId] = 0;
+/******/ installedChunks[chunkId] = false;
/******/ },
/******/ (e) => {
-/******/ delete installedChunks[chunkId];
+/******/ installedChunks[chunkId] = 0;
/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
/******/ }
/******/ ) );
/******/ }
/******/ };
/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
},
}
`;
@@ -1174,18 +765,15 @@ Object {
"bar.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"baz.asset.php": Object {},
"baz.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"noI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
}
`;
@@ -1204,69 +792,91 @@ Object {
"hasI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"main.js": Object {
"jpI18nState": null,
"loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
/******/ (() => {
-/******/ var textdomain = \\"options\\";
-/******/ var chunkInfo = {
-/******/ \\"hasI18n\\": [ /* jetpack_vendor/automattic/jetpack-foobar/dist/hasI18n.js */ \\"e54fa518d005a9d82f46a567822e8600\\", \\"\\" ]
+/******/ var installedChunks = {
+/******/ \\"hasI18n\\": 0
/******/ };
/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! state */ \\"state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting state.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! loader */ \\"loader\\");
+/******/ if ( loader && loader.doload )
+/******/ return loader.doload( \\"jetpack_vendor/automattic/jetpack-foobar/dist/\\" + __webpack_require__.u( chunkId ), \\"options\\", \\"core\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting loader.\\" ) );
/******/ };
/******/
-/******/ var installedChunks = {};
/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
/******/ if ( installedChunks[chunkId] ) {
/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
/******/ () => {
+/******/ installedChunks[chunkId] = false;
+/******/ },
+/******/ (e) => {
/******/ installedChunks[chunkId] = 0;
+/******/ // Log only, we don't want i18n failure to break the entire page.
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
+/******/ }
+/******/ ) );
+/******/ }
+/******/ };
+/******/ })();",
+ },
+ "main2.asset.php": Object {},
+ "main2.js": Object {
+ "jpI18nState": null,
+ "loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
+/******/ (() => {
+/******/ var installedChunks = {
+/******/ \\"hasI18n\\": 0
+/******/ };
+/******/
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
+/******/ if ( loader && loader.downloadI18n )
+/******/ return loader.downloadI18n( \\"dist/\\" + __webpack_require__.u( chunkId ), \\"options\\", \\"plugin\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting wp.i18n.\\" ) );
+/******/ };
+/******/
+/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
+/******/ if ( installedChunks[chunkId] ) {
+/******/ promises.push( installedChunks[chunkId] );
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
+/******/ () => {
+/******/ installedChunks[chunkId] = false;
/******/ },
/******/ (e) => {
-/******/ delete installedChunks[chunkId];
+/******/ installedChunks[chunkId] = 0;
/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
/******/ }
/******/ ) );
/******/ }
/******/ };
/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!*************************!*\\\\
- !*** external \\"wpI18n\\" ***!
- \\\\*************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = global[\\"wpI18n\\"];
-",
},
}
`;
exports[`Webpack \`options\` Build results: Webpack build stats 1`] = `
Object {
+ "children": Array [
+ Object {
+ "errors": Array [],
+ "name": undefined,
+ "warnings": Array [],
+ },
+ Object {
+ "errors": Array [],
+ "name": undefined,
+ "warnings": Array [],
+ },
+ ],
"errors": Array [],
"warnings": Array [],
}
@@ -1276,104 +886,78 @@ exports[`Webpack \`single-runtime\` Build results: Webpack build error 1`] = `nu
exports[`Webpack \`single-runtime\` Build results: Webpack build files 1`] = `
Object {
+ "bar.asset.php": Object {},
"bar.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
+ "baz.asset.php": Object {},
"baz.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"hasI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"hasI18n2.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
+ "main.asset.php": Object {},
"main.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
+ "main2.asset.php": Object {},
"main2.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"noI18n.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
- "runtime.asset.php": Object {},
"runtime.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!*************************************!*\\\\
- !*** external [\\"wp\\",\\"jpI18nState\\"] ***!
- \\\\*************************************/
+ "jpI18nState": "/***/ \\"@wordpress/jp-i18n-loader\\":
+/*!**************************************!*\\\\
+ !*** external [\\"wp\\",\\"jpI18nLoader\\"] ***!
+ \\\\**************************************/
/***/ ((module) => {
-module.exports = window[\\"wp\\"][\\"jpI18nState\\"];
+module.exports = window[\\"wp\\"][\\"jpI18nLoader\\"];
",
"loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
/******/ (() => {
-/******/ var textdomain = \\"single-runtime\\";
-/******/ var chunkInfo = {
-/******/ \\"hasI18n\\": [ /* dist/hasI18n.js */ \\"c7c3968298452ee95564fb9c05e2de50\\", \\"\\" ],
-/******/ \\"hasI18n2\\": [ /* dist/hasI18n2.js */ \\"71d7bd53fc0961d2785c69cde5d9fead\\", \\"\\" ]
+/******/ var installedChunks = {
+/******/ \\"hasI18n\\": 0,
+/******/ \\"hasI18n2\\": 0
/******/ };
/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! @wordpress/jp-i18n-loader */ \\"@wordpress/jp-i18n-loader\\");
+/******/ if ( loader && loader.downloadI18n )
+/******/ return loader.downloadI18n( \\"dist/\\" + __webpack_require__.u( chunkId ), \\"single-runtime\\", \\"plugin\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.\\" ) );
/******/ };
/******/
-/******/ var installedChunks = {};
/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
/******/ if ( installedChunks[chunkId] ) {
/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
/******/ () => {
-/******/ installedChunks[chunkId] = 0;
+/******/ installedChunks[chunkId] = false;
/******/ },
/******/ (e) => {
-/******/ delete installedChunks[chunkId];
+/******/ installedChunks[chunkId] = 0;
/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
/******/ }
/******/ ) );
/******/ }
/******/ };
/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
},
}
`;
@@ -1392,147 +976,98 @@ Object {
"indirect1.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"indirect2.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
"main.asset.php": Object {},
"main.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!*************************************!*\\\\
- !*** external [\\"wp\\",\\"jpI18nState\\"] ***!
- \\\\*************************************/
+ "jpI18nState": "/***/ \\"@wordpress/jp-i18n-loader\\":
+/*!**************************************!*\\\\
+ !*** external [\\"wp\\",\\"jpI18nLoader\\"] ***!
+ \\\\**************************************/
/***/ ((module) => {
\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"jpI18nState\\"];
+module.exports = window[\\"wp\\"][\\"jpI18nLoader\\"];
",
"loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
/******/ (() => {
-/******/ var textdomain = \\"splitting\\";
-/******/ var chunkInfo = {
-/******/ \\"src_hasI18n_js-src_hasI18n2_js\\": [ /* dist/src_hasI18n_js-src_hasI18n2_js.js */ \\"ec0e6fc1bae1bb5c76db79838b127a36\\", \\"\\" ]
+/******/ var installedChunks = {
+/******/ \\"src_hasI18n_js-src_hasI18n2_js\\": 0
/******/ };
/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! @wordpress/jp-i18n-loader */ \\"@wordpress/jp-i18n-loader\\");
+/******/ if ( loader && loader.downloadI18n )
+/******/ return loader.downloadI18n( \\"dist/\\" + __webpack_require__.u( chunkId ), \\"splitting\\", \\"plugin\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.\\" ) );
/******/ };
/******/
-/******/ var installedChunks = {};
/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
/******/ if ( installedChunks[chunkId] ) {
/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
/******/ () => {
-/******/ installedChunks[chunkId] = 0;
+/******/ installedChunks[chunkId] = false;
/******/ },
/******/ (e) => {
-/******/ delete installedChunks[chunkId];
+/******/ installedChunks[chunkId] = 0;
/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
/******/ }
/******/ ) );
/******/ }
/******/ };
/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
},
"main2.asset.php": Object {},
"main2.js": Object {
- "jpI18nState": "/***/ \\"@wordpress/jp-i18n-state\\":
-/*!*************************************!*\\\\
- !*** external [\\"wp\\",\\"jpI18nState\\"] ***!
- \\\\*************************************/
+ "jpI18nState": "/***/ \\"@wordpress/jp-i18n-loader\\":
+/*!**************************************!*\\\\
+ !*** external [\\"wp\\",\\"jpI18nLoader\\"] ***!
+ \\\\**************************************/
/***/ ((module) => {
\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"jpI18nState\\"];
+module.exports = window[\\"wp\\"][\\"jpI18nLoader\\"];
",
"loader": "/******/ /* webpack/runtime/loading @automattic/i18n-loader-webpack-plugin */
/******/ (() => {
-/******/ var textdomain = \\"splitting\\";
-/******/ var chunkInfo = {
-/******/ \\"src_hasI18n_js-src_hasI18n2_js\\": [ /* dist/src_hasI18n_js-src_hasI18n2_js.js */ \\"ec0e6fc1bae1bb5c76db79838b127a36\\", \\"\\" ]
+/******/ var installedChunks = {
+/******/ \\"src_hasI18n_js-src_hasI18n2_js\\": 0
/******/ };
/******/
-/******/ var loadI18n = (info) => {
-/******/ var i18n = __webpack_require__(/*! @wordpress/i18n */ \\"@wordpress/i18n\\");
-/******/ var state = __webpack_require__(/*! @wordpress/jp-i18n-state */ \\"@wordpress/jp-i18n-state\\");
-/******/ if ( ! state ) return Promise.reject( new Error( \\"I18n state is not available. Check that WordPress is exporting wp.jpI18nState.\\" ) );
-/******/ if ( state.locale === \\"en_US\\" ) return Promise.resolve();
-/******/ if ( typeof fetch === \\"undefined\\" ) return Promise.reject( new Error( \\"Fetch API is not available.\\" ) );
-/******/ return fetch(
-/******/ state.baseUrl + ( state.domainMap[textdomain] || ( \\"plugins/\\" + textdomain ) ) + \\"-\\" + state.locale + \\"-\\" + info[0] + \\".json\\" + info[1]
-/******/ ).then( (res) => {
-/******/ if ( ! res.ok ) throw new Error( \\"HTTP request failed: \\" + res.status + \\" \\" + res.statusText );
-/******/ return res.json();
-/******/ } ).then( (data) => {
-/******/ var data2 = data.locale_data;
-/******/ var localeData = data2[ textdomain ] || data2.messages;
-/******/ localeData[\\"\\"].domain = textdomain;
-/******/ i18n.setLocaleData( localeData, textdomain );
-/******/ } );
+/******/ var loadI18n = (chunkId) => {
+/******/ var loader = __webpack_require__(/*! @wordpress/jp-i18n-loader */ \\"@wordpress/jp-i18n-loader\\");
+/******/ if ( loader && loader.downloadI18n )
+/******/ return loader.downloadI18n( \\"dist/\\" + __webpack_require__.u( chunkId ), \\"splitting\\", \\"plugin\\" );
+/******/ return Promise.reject( new Error( \\"I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.\\" ) );
/******/ };
/******/
-/******/ var installedChunks = {};
/******/ __webpack_require__.f.wpI18n = (chunkId, promises) => {
/******/ if ( installedChunks[chunkId] ) {
/******/ promises.push( installedChunks[chunkId] );
-/******/ } else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {
-/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then(
+/******/ } else if ( installedChunks[chunkId] === 0 ) {
+/******/ promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then(
/******/ () => {
-/******/ installedChunks[chunkId] = 0;
+/******/ installedChunks[chunkId] = false;
/******/ },
/******/ (e) => {
-/******/ delete installedChunks[chunkId];
+/******/ installedChunks[chunkId] = 0;
/******/ // Log only, we don't want i18n failure to break the entire page.
-/******/ console.error( \\"Failed to fetch i18n data:\\", e );
+/******/ console.error( \\"Failed to fetch i18n data: \\", e );
/******/ }
/******/ ) );
/******/ }
/******/ };
/******/ })();",
- "wpI18n": "/***/ \\"@wordpress/i18n\\":
-/*!******************************!*\\\\
- !*** external [\\"wp\\",\\"i18n\\"] ***!
- \\\\******************************/
-/***/ ((module) => {
-
-\\"use strict\\";
-module.exports = window[\\"wp\\"][\\"i18n\\"];
-",
},
"src_hasI18n_js-src_hasI18n2_js.js": Object {
"jpI18nState": null,
"loader": null,
- "wpI18n": null,
},
}
`;
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/build.test.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/build.test.js
index ecc407988b2c0..73011112c665d 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/build.test.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/build.test.js
@@ -42,7 +42,7 @@ function lsFiles( dir ) {
}
beforeEach( () => {
- fetch.urls = {};
+ global.jpI18nLoader.expect = {};
} );
const fixturesPath = path.join( __dirname, 'fixtures' );
@@ -104,10 +104,9 @@ describe.each( configFixtures )( 'Webpack `%s`', fixture => {
const data = {};
if ( file.endsWith( '.js' ) ) {
const content = fs.readFileSync( path.join( builddir, file ), { encoding: 'utf8' } );
- data.wpI18n = extractSection( content, '/***/ "@wordpress/i18n":\n', '/***/ })' );
data.jpI18nState = extractSection(
content,
- '/***/ "@wordpress/jp-i18n-state":\n',
+ '/***/ "@wordpress/jp-i18n-loader":\n',
'/***/ })'
);
data.loader = extractSection(
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/basic-test/tests.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/basic-test/tests.js
index 1c157fced3d99..56d4ee9cdeef1 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/basic-test/tests.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/basic-test/tests.js
@@ -1,3 +1,5 @@
+/* global jpI18nLoader */
+
test( 'Tests', async () => {
const main = require( './dist/main.js' );
const bar = require( './dist/bar.js' );
@@ -6,10 +8,7 @@ test( 'Tests', async () => {
expect( await main.noI18n() ).toEqual( 'No i18n here' );
- fetch.expectUrl(
- 'http://test.example.com/wp-content/languages/plugins/basic-test-en_piglatin-c7c3968298452ee95564fb9c05e2de50.json',
- translations
- );
+ jpI18nLoader.expectI18n( 'dist/hasI18n.js', translations );
expect( await main.hasI18n() ).toEqual( 'is-Thay is-way anslated-tray' );
expect( await main.hasI18n() ).toEqual( 'is-Thay is-way anslated-tray' );
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/fetch-failures/src/hasI18n.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/fetch-failures/src/hasI18n.js
deleted file mode 100644
index 9c9a9d62e6155..0000000000000
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/fetch-failures/src/hasI18n.js
+++ /dev/null
@@ -1,3 +0,0 @@
-const i18n = require( '@wordpress/i18n' );
-
-module.exports = i18n.__( 'This is translated', 'fetch-failures' );
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/fetch-failures/tests.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/fetch-failures/tests.js
deleted file mode 100644
index 02e0a1028e30e..0000000000000
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/fetch-failures/tests.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/* global wpI18n */
-
-describe( 'Tests', () => {
- let spy;
-
- beforeEach( () => {
- spy = jest.spyOn( global.console, 'error' ).mockImplementation( () => {} );
- wpI18n.resetLocaleData( {}, 'fetch-failures' );
- } );
- afterEach( () => {
- spy.mockRestore();
- } );
-
- const getBundle = () => {
- let a;
- jest.isolateModules( () => {
- a = require( './dist/a.js' );
- } );
- return a;
- };
-
- test( 'Control', async () => {
- const a = getBundle();
- fetch.expectUrl(
- 'http://test.example.com/wp-content/languages/plugins/fetch-failures-en_piglatin-c7c3968298452ee95564fb9c05e2de50.json',
- require( './en_piglatin.json' )
- );
- expect( await a.hasI18n() ).toEqual( 'is-Thay is-way anslated-tray' );
- expect( spy ).not.toHaveBeenCalled();
- } );
-
- test( 'No fetch', async () => {
- const a = getBundle();
- const fetch = global.fetch;
- try {
- delete global.fetch;
- expect( await a.hasI18n() ).toEqual( 'This is translated' );
- } finally {
- global.fetch = fetch;
- }
- expect( spy ).toHaveBeenCalledTimes( 1 );
- expect( spy ).toHaveBeenCalledWith( 'Failed to fetch i18n data:', expect.any( Error ) );
- expect( spy.mock.calls[ 0 ][ 1 ].message ).toEqual( 'Fetch API is not available.' );
- } );
-
- test( 'An HTTP 404 error', async () => {
- const a = getBundle();
- fetch.expectUrl(
- 'http://test.example.com/wp-content/languages/plugins/fetch-failures-en_piglatin-c7c3968298452ee95564fb9c05e2de50.json',
- 'The specified document was not found.',
- { status: 404, statusText: 'Not found' }
- );
- expect( await a.hasI18n() ).toEqual( 'This is translated' );
- expect( spy ).toHaveBeenCalledTimes( 1 );
- expect( spy ).toHaveBeenCalledWith( 'Failed to fetch i18n data:', expect.any( Error ) );
- expect( spy.mock.calls[ 0 ][ 1 ].message ).toEqual( 'HTTP request failed: 404 Not found' );
- } );
-
- test( 'A fetch error', async () => {
- const a = getBundle();
- const err = new Error( 'Test error' );
- fetch.expectError(
- 'http://test.example.com/wp-content/languages/plugins/fetch-failures-en_piglatin-c7c3968298452ee95564fb9c05e2de50.json',
- err
- );
- expect( await a.hasI18n() ).toEqual( 'This is translated' );
- expect( spy ).toHaveBeenCalledTimes( 1 );
- expect( spy ).toHaveBeenCalledWith( 'Failed to fetch i18n data:', err );
- } );
-
- test( 'Invalid JSON data', async () => {
- const a = getBundle();
- fetch.expectUrl(
- 'http://test.example.com/wp-content/languages/plugins/fetch-failures-en_piglatin-c7c3968298452ee95564fb9c05e2de50.json',
- 'Invalid JSON'
- );
- expect( await a.hasI18n() ).toEqual( 'This is translated' );
- expect( spy ).toHaveBeenCalledTimes( 1 );
- expect( spy ).toHaveBeenCalledWith( 'Failed to fetch i18n data:', expect.any( Error ) );
- expect( spy.mock.calls[ 0 ][ 1 ].message ).toEqual(
- 'Unexpected token I in JSON at position 0'
- );
- } );
-} );
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/ignore-modules/src/index.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/ignore-modules/src/index.js
index 6100739627033..9e109f268fd37 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/ignore-modules/src/index.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/ignore-modules/src/index.js
@@ -1,5 +1,4 @@
module.exports = {
- md5: async () => ( await import( /* webpackChunkName: "md5" */ 'md5-es' ) ).default.hash,
noI18n: async () => ( await import( /* webpackChunkName: "noI18n" */ './noI18n.js' ) ).default,
hasI18n: async () => ( await import( /* webpackChunkName: "hasI18n" */ './hasI18n.js' ) ).default,
};
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/ignore-modules/tests.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/ignore-modules/tests.js
index bc05b2f2e40b9..6fe52c832295f 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/ignore-modules/tests.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/ignore-modules/tests.js
@@ -1,5 +1,4 @@
-/* global wpI18n */
-const { default: md5 } = require( 'md5-es' );
+/* global jpI18nLoader, wpI18n */
describe( 'Test ignoreModules as', () => {
beforeEach( () => {
@@ -10,16 +9,12 @@ describe( 'Test ignoreModules as', () => {
test.each( whats )( 'a %s', async what => {
const main = require( `./dist/${ what }/main.js` );
const main2 = require( `./dist/${ what }/main2.js` );
- const hash = md5.hash( `dist/${ what }/hasI18n2.js` );
expect( await main.noI18n() ).toEqual( 'No i18n here' );
expect( await main.hasI18n() ).toEqual( 'This is translated' );
expect( await main2.hasI18n() ).toEqual( 'This is translated' );
- fetch.expectUrl(
- `http://test.example.com/wp-content/languages/plugins/ignore-modules-en_piglatin-${ hash }.json`,
- require( './en_piglatin.json' )
- );
+ jpI18nLoader.expectI18n( `dist/${ what }/hasI18n2.js`, require( './en_piglatin.json' ) );
expect( await main2.hasI18n2() ).toEqual( 'is-Thay is-way anslated-tray oo-tay' );
} );
} );
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/fetch-failures/en_piglatin.json b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/loader-failures/en_piglatin.json
similarity index 100%
rename from projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/fetch-failures/en_piglatin.json
rename to projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/loader-failures/en_piglatin.json
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/loader-failures/src/hasI18n.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/loader-failures/src/hasI18n.js
new file mode 100644
index 0000000000000..184f6de9048c3
--- /dev/null
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/loader-failures/src/hasI18n.js
@@ -0,0 +1,3 @@
+const i18n = require( '@wordpress/i18n' );
+
+module.exports = i18n.__( 'This is translated', 'loader-failures' );
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/fetch-failures/src/index.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/loader-failures/src/index.js
similarity index 100%
rename from projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/fetch-failures/src/index.js
rename to projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/loader-failures/src/index.js
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/loader-failures/tests.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/loader-failures/tests.js
new file mode 100644
index 0000000000000..58cc09e6a823f
--- /dev/null
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/loader-failures/tests.js
@@ -0,0 +1,67 @@
+/* global jpI18nLoader, wpI18n */
+
+describe( 'Tests', () => {
+ let spy;
+
+ beforeEach( () => {
+ spy = jest.spyOn( global.console, 'error' ).mockImplementation( () => {} );
+ wpI18n.resetLocaleData( {}, 'loader-failures' );
+ } );
+ afterEach( () => {
+ spy.mockRestore();
+ } );
+
+ const getBundle = () => {
+ let a;
+ jest.isolateModules( () => {
+ a = require( './dist/a.js' );
+ } );
+ return a;
+ };
+
+ test( 'Control', async () => {
+ const a = getBundle();
+ jpI18nLoader.expectI18n( 'dist/hasI18n.js', require( './en_piglatin.json' ) );
+ expect( await a.hasI18n() ).toEqual( 'is-Thay is-way anslated-tray' );
+ expect( spy ).not.toHaveBeenCalled();
+ } );
+
+ test( 'Missing loader object', async () => {
+ delete window.wp.jpI18nLoader;
+ try {
+ const a = getBundle();
+ expect( await a.hasI18n() ).toEqual( 'This is translated' );
+ expect( spy ).toHaveBeenCalledTimes( 1 );
+ expect( spy ).toHaveBeenCalledWith( 'Failed to fetch i18n data: ', expect.any( Error ) );
+ expect( spy.mock.calls[ 0 ][ 1 ].message ).toEqual( 'I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.' );
+ } finally {
+ window.wp.jpI18nLoader = global.jpI18nLoader;
+ }
+ } );
+
+ test( 'Missing loader method', async () => {
+ window.wp.jpI18nLoader = {};
+ try {
+ const a = getBundle();
+ expect( await a.hasI18n() ).toEqual( 'This is translated' );
+ expect( spy ).toHaveBeenCalledTimes( 1 );
+ expect( spy ).toHaveBeenCalledWith( 'Failed to fetch i18n data: ', expect.any( Error ) );
+ expect( spy.mock.calls[ 0 ][ 1 ].message ).toEqual( 'I18n loader is not available. Check that WordPress is exporting wp.jpI18nLoader.' );
+ } finally {
+ window.wp.jpI18nLoader = global.jpI18nLoader;
+ }
+ } );
+
+ test( 'Failed download', async () => {
+ const a = getBundle();
+ jpI18nLoader.expectError(
+ 'dist/hasI18n.js',
+ new Error( 'The specified document was not found.' )
+ );
+ expect( await a.hasI18n() ).toEqual( 'This is translated' );
+ expect( spy ).toHaveBeenCalledTimes( 1 );
+ expect( spy ).toHaveBeenCalledWith( 'Failed to fetch i18n data: ', expect.any( Error ) );
+ expect( spy.mock.calls[ 0 ][ 1 ].message ).toEqual( 'The specified document was not found.' );
+ } );
+
+} );
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/fetch-failures/webpack.config.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/loader-failures/webpack.config.js
similarity index 87%
rename from projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/fetch-failures/webpack.config.js
rename to projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/loader-failures/webpack.config.js
index edb7ca6504ba5..2d819393723f6 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/fetch-failures/webpack.config.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/loader-failures/webpack.config.js
@@ -15,7 +15,7 @@ module.exports = {
},
},
plugins: [
- new I18nLoaderPlugin( { textdomain: 'fetch-failures' } ),
+ new I18nLoaderPlugin( { textdomain: 'loader-failures' } ),
new DependencyExtractionPlugin(),
],
};
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/manual-externals/tests.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/manual-externals/tests.js
index 32bdbe0fa3461..1dc5193287650 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/manual-externals/tests.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/manual-externals/tests.js
@@ -1,4 +1,4 @@
-/* global wpI18n */
+/* global jpI18nLoader, wpI18n */
describe( 'Tests', () => {
afterEach( () => {
@@ -7,24 +7,8 @@ describe( 'Tests', () => {
test( 'External from the global', async () => {
const main = require( './dist/main.js' );
- fetch.expectUrl(
- 'http://test.example.com/wp-content/languages/plugins/manual-externals-en_piglatin-c7c3968298452ee95564fb9c05e2de50.json',
- require( './en_piglatin.json' )
- );
+ jpI18nLoader.expectI18n( 'dist/hasI18n.js', require( './en_piglatin.json' ) );
expect( await main.hasI18n() ).toEqual( 'is-Thay is-way anslated-tray' );
} );
- test( 'Hard-coded "external", with locale en_US', async () => {
- const main2 = require( './dist/main2.js' );
- expect( await main2.hasI18n() ).toEqual( 'This is translated' );
- } );
-
- test( 'Hard-coded "external", with different baseUrl, locale, and domainMap', async () => {
- const main3 = require( './dist/main3.js' );
- fetch.expectUrl(
- 'http://example.org/themes/remapped-en_us-c7c3968298452ee95564fb9c05e2de50.json',
- require( './en_us.json' )
- );
- expect( await main3.hasI18n() ).toEqual( 'Thus us trunslutud' );
- } );
} );
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/manual-externals/webpack.config.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/manual-externals/webpack.config.js
index f03df7a7f4114..6c991a3193b5c 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/manual-externals/webpack.config.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/manual-externals/webpack.config.js
@@ -21,41 +21,7 @@ module.exports = [
},
externals: {
'@wordpress/i18n': 'global wpI18n',
- '@wordpress/jp-i18n-state': 'global jpI18nState',
- },
- },
- {
- ...shared,
- entry: {
- main2: './src/index.js',
- },
- externals: {
- '@wordpress/i18n': 'global wpI18n',
- '@wordpress/jp-i18n-state':
- 'var ' +
- JSON.stringify( {
- baseUrl: 'http://example.org/',
- locale: 'en_US',
- domainMap: {},
- } ),
- },
- },
- {
- ...shared,
- entry: {
- main3: './src/index.js',
- },
- externals: {
- '@wordpress/i18n': 'global wpI18n',
- '@wordpress/jp-i18n-state':
- 'var ' +
- JSON.stringify( {
- baseUrl: 'http://example.org/',
- locale: 'en_us',
- domainMap: {
- 'manual-externals': 'themes/remapped',
- },
- } ),
+ '@wordpress/jp-i18n-loader': 'global jpI18nLoader',
},
},
];
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/missing-externals/webpack.config.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/missing-externals/webpack.config.js
index 38ce5910c3f1f..52c897ec2134a 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/missing-externals/webpack.config.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/missing-externals/webpack.config.js
@@ -1,20 +1,40 @@
const I18nLoaderPlugin = require( '../../../src/I18nLoaderPlugin.js' );
-module.exports = {
- target: 'async-node',
- mode: 'development',
- devtool: false,
- entry: {
- main: './src/index.js',
- },
- output: {
- chunkFilename: '[name].js',
- library: {
- type: 'commonjs2',
+module.exports = [
+ {
+ target: 'async-node',
+ mode: 'development',
+ devtool: false,
+ entry: {
+ main: './src/index.js',
+ },
+ output: {
+ chunkFilename: '[name].js',
+ library: {
+ type: 'commonjs2',
+ },
+ },
+ plugins: [ new I18nLoaderPlugin( { textdomain: 'missing-externals' } ) ],
+ externals: {
+ '@wordpress/i18n': 'global wpI18n',
},
},
- plugins: [ new I18nLoaderPlugin( { textdomain: 'missing-externals' } ) ],
- externals: {
- '@wordpress/i18n': 'global wpI18n',
+ {
+ target: 'async-node',
+ mode: 'development',
+ devtool: false,
+ entry: {
+ main: './src/index.js',
+ },
+ output: {
+ chunkFilename: '[name].js',
+ library: {
+ type: 'commonjs2',
+ },
+ },
+ plugins: [ new I18nLoaderPlugin( { textdomain: 'missing-externals', loaderModule: 'missingloader' } ) ],
+ externals: {
+ '@wordpress/i18n': 'global wpI18n',
+ },
},
-};
+];
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/multiple-runtime/tests.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/multiple-runtime/tests.js
index 8740d6721440d..66c3b4ae68ca8 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/multiple-runtime/tests.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/multiple-runtime/tests.js
@@ -1,3 +1,4 @@
+/* global jpI18nLoader */
test( 'Tests', async () => {
const main = require( './dist/main.js' );
const main2 = require( './dist/main2.js' );
@@ -7,21 +8,12 @@ test( 'Tests', async () => {
expect( await main.noI18n() ).toEqual( 'No i18n here' );
- fetch.expectUrl(
- 'http://test.example.com/wp-content/languages/plugins/multiple-runtime-en_piglatin-c7c3968298452ee95564fb9c05e2de50.json',
- translations
- );
+ jpI18nLoader.expectI18n( 'dist/hasI18n.js', translations );
expect( await main.hasI18n() ).toEqual( 'is-Thay is-way anslated-tray' );
- fetch.expectUrl(
- 'http://test.example.com/wp-content/languages/plugins/multiple-runtime-en_piglatin-c7c3968298452ee95564fb9c05e2de50.json',
- translations
- );
+ jpI18nLoader.expectI18n( 'dist/hasI18n.js', translations );
expect( await main2.hasI18n() ).toEqual( 'is-Thay is-way anslated-tray' );
- fetch.expectUrl(
- 'http://test.example.com/wp-content/languages/plugins/multiple-runtime-en_piglatin-71d7bd53fc0961d2785c69cde5d9fead.json',
- translations
- );
+ jpI18nLoader.expectI18n( 'dist/hasI18n2.js', translations );
expect( await main2.hasI18n2() ).toEqual( 'is-Thay is-way anslated-tray oo-tay' );
expect( bar ).toEqual( 'No submodules here' );
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/nested-includes/tests.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/nested-includes/tests.js
index 711976d1a8fdc..d8c663e36f56b 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/nested-includes/tests.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/nested-includes/tests.js
@@ -1,9 +1,7 @@
+/* global jpI18nLoader */
test( 'Tests', async () => {
const main = require( './dist/main.js' );
- fetch.expectUrl(
- 'http://test.example.com/wp-content/languages/plugins/nested-includes-en_piglatin-c7c3968298452ee95564fb9c05e2de50.json',
- require( './en_piglatin.json' )
- );
+ jpI18nLoader.expectI18n( 'dist/hasI18n.js', require( './en_piglatin.json' ) );
expect( await main.hasI18n() ).toEqual( 'is-Thay is-way anslated-tray' );
} );
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/options/tests.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/options/tests.js
index a531db460b51a..c605e5e581d13 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/options/tests.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/options/tests.js
@@ -1,8 +1,28 @@
-test( 'Tests', async () => {
- const main = require( './dist/main.js' );
- fetch.expectUrl(
- 'http://example.org/options-en_piglatin-e54fa518d005a9d82f46a567822e8600.json',
- require( './en_piglatin.json' )
- );
- expect( await main.hasI18n() ).toEqual( 'is-Thay is-way anslated-tray' );
+/* global I18nLoader, jpI18nLoader, wpI18n */
+
+test( 'Options', async () => {
+ global.optionLoader = new I18nLoader();
+ optionLoader.doload = optionLoader.downloadI18n;
+ delete optionLoader.downloadI18n;
+
+ try {
+ const main = require( './dist/main.js' );
+ optionLoader.expectI18n( 'jetpack_vendor/automattic/jetpack-foobar/dist/hasI18n.js', require( './en_piglatin.json' ) );
+ expect( await main.hasI18n() ).toEqual( 'is-Thay is-way anslated-tray' );
+ } finally {
+ delete global.optionLoader;
+ }
+} );
+
+test( 'Loader is @wordpress/i18n', async () => {
+ delete global.window.wp.jpI18nLoader;
+ wpI18n.downloadI18n = jpI18nLoader.downloadI18n.bind( jpI18nLoader );
+ try {
+ const main2 = require( './dist/main2.js' );
+ jpI18nLoader.expectI18n( 'dist/hasI18n.js', require( './en_piglatin.json' ) );
+ expect( await main2.hasI18n() ).toEqual( 'is-Thay is-way anslated-tray' );
+ } finally {
+ global.window.wp.jpI18nLoader = global.jpI18nLoader;
+ delete wpI18n.downloadI18n;
+ }
} );
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/options/webpack.config.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/options/webpack.config.js
index e5db088104eb7..fc299a47d7df1 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/options/webpack.config.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/options/webpack.config.js
@@ -1,34 +1,53 @@
const I18nLoaderPlugin = require( '../../../src/I18nLoaderPlugin.js' );
+const DependencyExtractionPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' );
-module.exports = {
- target: 'async-node',
- mode: 'development',
- devtool: false,
- entry: {
- main: './src/index.js',
- },
- output: {
- chunkFilename: '[name].js',
- library: {
- type: 'commonjs2',
+module.exports = [
+ {
+ target: 'async-node',
+ mode: 'development',
+ devtool: false,
+ entry: {
+ main: './src/index.js',
+ },
+ output: {
+ chunkFilename: '[name].js',
+ library: {
+ type: 'commonjs2',
+ },
+ },
+ plugins: [
+ new I18nLoaderPlugin( {
+ textdomain: 'options',
+ loaderModule: 'loader',
+ loaderMethod: 'doload',
+ target: 'core',
+ path: 'jetpack_vendor/automattic/jetpack-foobar/dist',
+ } ),
+ ],
+ externals: {
+ '@wordpress/i18n': 'global wpI18n',
+ loader: 'global optionLoader',
},
},
- plugins: [
- new I18nLoaderPlugin( {
- textdomain: 'options',
- stateModule: 'state',
- target: 'core',
- path: 'jetpack_vendor/automattic/jetpack-foobar/dist',
- } ),
- ],
- externals: {
- '@wordpress/i18n': 'global wpI18n',
- state:
- 'var ' +
- JSON.stringify( {
- baseUrl: 'http://example.org/',
- locale: 'en_piglatin',
- domainMap: {},
+ {
+ target: 'async-node',
+ mode: 'development',
+ devtool: false,
+ entry: {
+ main2: './src/index.js',
+ },
+ output: {
+ chunkFilename: '[name].js',
+ library: {
+ type: 'commonjs2',
+ },
+ },
+ plugins: [
+ new I18nLoaderPlugin( {
+ textdomain: 'options',
+ loaderModule: '@wordpress/i18n',
} ),
+ new DependencyExtractionPlugin(),
+ ],
},
-};
+];
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/single-runtime/tests.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/single-runtime/tests.js
index ee98107ad52a9..7bdf0eede469e 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/single-runtime/tests.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/single-runtime/tests.js
@@ -1,3 +1,4 @@
+/* global jpI18nLoader */
test( 'Tests', async () => {
const main = require( './dist/main.js' );
const main2 = require( './dist/main2.js' );
@@ -7,16 +8,10 @@ test( 'Tests', async () => {
expect( await main.noI18n() ).toEqual( 'No i18n here' );
- fetch.expectUrl(
- 'http://test.example.com/wp-content/languages/plugins/single-runtime-en_piglatin-c7c3968298452ee95564fb9c05e2de50.json',
- translations
- );
+ jpI18nLoader.expectI18n( 'dist/hasI18n.js', translations );
expect( await main.hasI18n() ).toEqual( 'is-Thay is-way anslated-tray' );
- fetch.expectUrl(
- 'http://test.example.com/wp-content/languages/plugins/single-runtime-en_piglatin-71d7bd53fc0961d2785c69cde5d9fead.json',
- translations
- );
+ jpI18nLoader.expectI18n( 'dist/hasI18n2.js', translations );
expect( await main2.hasI18n() ).toEqual( 'is-Thay is-way anslated-tray' );
expect( await main2.hasI18n2() ).toEqual( 'is-Thay is-way anslated-tray oo-tay' );
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/splitting/tests.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/splitting/tests.js
index c90ae953c4142..e7ac596dadf8c 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/splitting/tests.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/fixtures/splitting/tests.js
@@ -1,19 +1,14 @@
+/* global jpI18nLoader */
test( 'Tests', async () => {
const main = require( './dist/main.js' );
const main2 = require( './dist/main2.js' );
const translations = require( './en_piglatin.json' );
- fetch.expectUrl(
- 'http://test.example.com/wp-content/languages/plugins/splitting-en_piglatin-ec0e6fc1bae1bb5c76db79838b127a36.json',
- translations
- );
+ jpI18nLoader.expectI18n( 'dist/src_hasI18n_js-src_hasI18n2_js.js', translations );
expect( ( await main() ).a ).toEqual( 'is-Thay is-way anslated-tray' );
expect( ( await main() ).b ).toEqual( 'is-Thay is-way anslated-tray oo-tay' );
- fetch.expectUrl(
- 'http://test.example.com/wp-content/languages/plugins/splitting-en_piglatin-ec0e6fc1bae1bb5c76db79838b127a36.json',
- translations
- );
+ jpI18nLoader.expectI18n( 'dist/src_hasI18n_js-src_hasI18n2_js.js', translations );
expect( ( await main2() ).x ).toEqual( 'is-Thay is-way anslated-tray' );
expect( ( await main2() ).y ).toEqual( 'is-Thay is-way anslated-tray oo-tay' );
} );
diff --git a/projects/js-packages/i18n-loader-webpack-plugin/tests/globals.js b/projects/js-packages/i18n-loader-webpack-plugin/tests/globals.js
index d06d9c32c0607..70e2d97c0bb58 100644
--- a/projects/js-packages/i18n-loader-webpack-plugin/tests/globals.js
+++ b/projects/js-packages/i18n-loader-webpack-plugin/tests/globals.js
@@ -1,69 +1,66 @@
-// Some globals the tests will probably want.
global.wpI18n = require( '@wordpress/i18n' );
-global.jpI18nState = {
- baseUrl: 'http://test.example.com/wp-content/languages/',
- locale: 'en_piglatin',
- domainMap: {},
-};
-global.window = {
- ...global.window,
- wp: {
- i18n: global.wpI18n,
- jpI18nState: global.jpI18nState,
- },
-};
-// Simple fetch mock, sufficient for our purposes.
-global.fetch = jest.fn( url => {
- const ret = fetch.urls[ url ];
- if ( typeof ret === 'undefined' ) {
- throw new Error( `Unexpected URL ${ url }` );
- }
- if ( ret === null ) {
- throw new Error( `URL ${ url } was requested multiple times` );
- }
- fetch.urls[ url ] = null;
- return ret;
-} );
-fetch.urls = {};
+/** Loader class. */
+class I18nLoader {
+ /** Map paths to promise-factory functions. */
+ expect = {};
-/**
- * Mock a URL.
- *
- * @param {string} url - URL.
- * @param {*} body - Response body. If not a string, it will be passed through `JSON.stringify()`.
- * @param {object} init - Additional response data.
- * @param {number} init.status - Response status. Default 200.
- * @param {string} init.statusText - Response status text. Default "OK".
- */
-fetch.expectUrl = ( url, body, init = {} ) => {
- const status = parseInt( init.status || 200 );
- const statusText = init.statusText || 'OK';
-
- let rbody = body;
- if ( typeof rbody !== 'string' ) {
- rbody = JSON.stringify( rbody );
+ /**
+ * "Download" an i18n file.
+ *
+ * Actually just returns a promise from `this.expect`, if any,
+ * or throws an error.
+ *
+ * @param {string} path - Path being "downloaded".
+ * @param {string} domain - Text domain.
+ * @returns {Promise} Promise.
+ */
+ downloadI18n( path, domain ) {
+ const ret = this.expect[ path ];
+ if ( typeof ret === 'undefined' ) {
+ throw new Error( `Unexpected call for ${ path }` );
+ }
+ if ( ret === null ) {
+ throw new Error( `Path ${ path } was requested multiple times` );
+ }
+ this.expect[ expect ] = null;
+ return ret( domain );
}
- if ( status < 200 || status > 599 ) {
- throw new Error( `Invalid status: ${ init.status }` );
+
+ /**
+ * Mock a path.
+ *
+ * @param {string} path - Path.
+ * @param {object} data - I18n data.
+ */
+ expectI18n( path, data ) {
+ this.expect[ path ] = domain => {
+ const localeData = data.locale_data[ domain ] || data.locale_data.messages;
+ localeData[ '' ].domain = domain;
+ global.wpI18n.setLocaleData( localeData, domain );
+ return Promise.resolve();
+ };
}
- fetch.urls[ url ] = Promise.resolve( {
- body: rbody,
- ok: status < 300,
- status: status,
- statusText: statusText,
- url: url,
- json: () => JSON.parse( rbody ),
- } );
-};
+ /**
+ * Mock an error.
+ *
+ * @param {string} path - Path.
+ * @param {Error} err - Error.
+ */
+ expectError = ( path, err ) => {
+ this.expect[ path ] = () => {
+ return Promise.reject( err );
+ };
+ };
+}
-/**
- * Mock a fetch error.
- *
- * @param {string} url - URL.
- * @param {Error} err - Error.
- */
-fetch.expectError = ( url, err ) => {
- fetch.urls[ url ] = Promise.reject( err );
+global.I18nLoader = I18nLoader;
+global.jpI18nLoader = new I18nLoader();
+global.window = {
+ ...global.window,
+ wp: {
+ i18n: global.wpI18n,
+ jpI18nLoader: global.jpI18nLoader,
+ },
};
diff --git a/projects/js-packages/idc/CHANGELOG.md b/projects/js-packages/idc/CHANGELOG.md
index 0dfde7ddddb14..c7a23c53a76e2 100644
--- a/projects/js-packages/idc/CHANGELOG.md
+++ b/projects/js-packages/idc/CHANGELOG.md
@@ -2,6 +2,15 @@
### This is a list detailing changes for the Jetpack RNA IDC package releases.
+## 0.9.1 - 2022-02-02
+### Changed
+- Updated package dependencies.
+
+## 0.9.0 - 2022-01-25
+### Added
+- Additional package customization parameters.
+- Allow customized text to contain HTML tags.
+
## 0.8.2 - 2022-01-18
### Changed
- General: update required node version to v16.13.2
diff --git a/projects/js-packages/idc/changelog/add-fancy-eslint-ignore b/projects/js-packages/idc/changelog/add-fancy-eslint-ignore
deleted file mode 100644
index afe95e481cf32..0000000000000
--- a/projects/js-packages/idc/changelog/add-fancy-eslint-ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Hack eslint config to use `.gitignore` and per-dir `.eslintignore`.
-
-
diff --git a/projects/js-packages/idc/changelog/add-idc-customizate-labels b/projects/js-packages/idc/changelog/add-idc-customizate-labels
deleted file mode 100644
index 0fa1cc11d4121..0000000000000
--- a/projects/js-packages/idc/changelog/add-idc-customizate-labels
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: added
-
-Additional package customization parameters.
diff --git a/projects/js-packages/idc/changelog/add-idc-customizer-tags b/projects/js-packages/idc/changelog/add-idc-customizer-tags
deleted file mode 100644
index 84270131a2ac2..0000000000000
--- a/projects/js-packages/idc/changelog/add-idc-customizer-tags
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: added
-
-Allow customized text to contain HTML tags.
diff --git a/projects/js-packages/storybook/changelog/renovate-css-loader-6.x#2 b/projects/js-packages/idc/changelog/renovate-wordpress-monorepo
similarity index 100%
rename from projects/js-packages/storybook/changelog/renovate-css-loader-6.x#2
rename to projects/js-packages/idc/changelog/renovate-wordpress-monorepo
diff --git a/projects/js-packages/storybook/changelog/2021-10-13-15-55-37-502807 b/projects/js-packages/idc/changelog/renovate-wordpress-monorepo#2
similarity index 100%
rename from projects/js-packages/storybook/changelog/2021-10-13-15-55-37-502807
rename to projects/js-packages/idc/changelog/renovate-wordpress-monorepo#2
diff --git a/projects/js-packages/idc/changelog/update-project-scripts-no-install b/projects/js-packages/idc/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/js-packages/idc/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/js-packages/idc/package.json b/projects/js-packages/idc/package.json
index 24f7dff2a07cb..209c96a35b5a6 100644
--- a/projects/js-packages/idc/package.json
+++ b/projects/js-packages/idc/package.json
@@ -1,21 +1,21 @@
{
"name": "@automattic/jetpack-idc",
- "version": "0.9.0-alpha",
+ "version": "0.9.2-alpha",
"description": "Jetpack Connection Component",
"author": "Automattic",
"license": "GPL-2.0-or-later",
"dependencies": {
- "@automattic/jetpack-analytics": "workspace:^0.1.7-alpha",
- "@automattic/jetpack-api": "workspace:^0.8.3-alpha",
- "@automattic/jetpack-base-styles": "workspace:^0.1.7-alpha",
- "@automattic/jetpack-components": "workspace:^0.10.3-alpha",
- "@wordpress/base-styles": "4.0.4",
- "@wordpress/components": "19.1.6",
- "@wordpress/compose": "5.0.7",
- "@wordpress/data": "6.1.5",
- "@wordpress/element": "4.0.4",
- "@wordpress/i18n": "4.2.4",
- "@wordpress/url": "3.3.1",
+ "@automattic/jetpack-analytics": "workspace:^0.1.7",
+ "@automattic/jetpack-api": "workspace:^0.8.4-alpha",
+ "@automattic/jetpack-base-styles": "workspace:^0.1.8-alpha",
+ "@automattic/jetpack-components": "workspace:^0.10.5-alpha",
+ "@wordpress/base-styles": "4.1.0",
+ "@wordpress/components": "19.3.0",
+ "@wordpress/compose": "5.1.0",
+ "@wordpress/data": "6.2.0",
+ "@wordpress/element": "4.1.0",
+ "@wordpress/i18n": "4.3.0",
+ "@wordpress/url": "3.4.0",
"prop-types": "^15.7.2"
},
"devDependencies": {
diff --git a/projects/js-packages/licensing/CHANGELOG.md b/projects/js-packages/licensing/CHANGELOG.md
index 2294f16227519..fd703e8778e39 100644
--- a/projects/js-packages/licensing/CHANGELOG.md
+++ b/projects/js-packages/licensing/CHANGELOG.md
@@ -5,6 +5,14 @@ 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.4.5 - 2022-02-02
+### Changed
+- Updated package dependencies.
+
+## 0.4.4 - 2022-01-25
+### Added
+- Add missing dev dependency on `nyc` for code coverage.
+
## 0.4.3 - 2022-01-18
### Changed
- General: update required node version to v16.13.2
diff --git a/projects/js-packages/licensing/changelog/add-fancy-eslint-ignore b/projects/js-packages/licensing/changelog/add-fancy-eslint-ignore
deleted file mode 100644
index afe95e481cf32..0000000000000
--- a/projects/js-packages/licensing/changelog/add-fancy-eslint-ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Hack eslint config to use `.gitignore` and per-dir `.eslintignore`.
-
-
diff --git a/projects/js-packages/storybook/changelog/renovate-emotion-monorepo b/projects/js-packages/licensing/changelog/renovate-wordpress-monorepo
similarity index 100%
rename from projects/js-packages/storybook/changelog/renovate-emotion-monorepo
rename to projects/js-packages/licensing/changelog/renovate-wordpress-monorepo
diff --git a/projects/js-packages/storybook/changelog/2021-10-26-11-37-27-239390 b/projects/js-packages/licensing/changelog/renovate-wordpress-monorepo#2
similarity index 100%
rename from projects/js-packages/storybook/changelog/2021-10-26-11-37-27-239390
rename to projects/js-packages/licensing/changelog/renovate-wordpress-monorepo#2
diff --git a/projects/js-packages/licensing/changelog/update-license-activation-dialog-text b/projects/js-packages/licensing/changelog/update-license-activation-dialog-text
new file mode 100644
index 0000000000000..c5a4bafa414e7
--- /dev/null
+++ b/projects/js-packages/licensing/changelog/update-license-activation-dialog-text
@@ -0,0 +1,4 @@
+Significance: patch
+Type: changed
+
+Update wording of Activation Dialog window
diff --git a/projects/js-packages/licensing/changelog/update-project-scripts-no-install b/projects/js-packages/licensing/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/js-packages/licensing/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/js-packages/licensing/components/activation-screen-success-info/product-details/index.js b/projects/js-packages/licensing/components/activation-screen-success-info/product-details/index.js
index 7a1816bea52aa..aef0c7dae2ba0 100644
--- a/projects/js-packages/licensing/components/activation-screen-success-info/product-details/index.js
+++ b/projects/js-packages/licensing/components/activation-screen-success-info/product-details/index.js
@@ -33,14 +33,14 @@ const JetpackProductDetails = props => {
const productInfoMap = {
jetpack_anti_spam: {
- title: __( 'Your Jetpack Anti-spam is active!', 'jetpack' ),
+ title: __( 'Jetpack Anti-spam is active!', 'jetpack' ),
text: __(
"We'll take care of everything from here. Now you can enjoy a spam-free site!",
'jetpack'
),
},
jetpack_backup: {
- title: __( 'Your Jetpack Backup is active!', 'jetpack' ),
+ title: __( 'Jetpack Backup is active!', 'jetpack' ),
text: createInterpolateElement(
__(
'You can see your backups and restore your site on cloud.jetpack.com . If you ever lose access to your site, you can restore it there.',
@@ -52,7 +52,7 @@ const JetpackProductDetails = props => {
),
},
jetpack_complete: {
- title: __( 'Your Jetpack Complete is active!', 'jetpack' ),
+ title: __( 'Jetpack Complete is active!', 'jetpack' ),
text: createInterpolateElement(
__(
'You can see your backups, security scans, and restore your site on cloud.jetpack.com . If you ever lose access to your site, you can restore it there.',
@@ -64,7 +64,7 @@ const JetpackProductDetails = props => {
),
},
jetpack_scan: {
- title: __( 'Your Jetpack Scan is active!', 'jetpack' ),
+ title: __( 'Jetpack Scan is active!', 'jetpack' ),
text: createInterpolateElement(
__( 'You can see your security scans on cloud.jetpack.com .', 'jetpack' ),
{
@@ -73,14 +73,14 @@ const JetpackProductDetails = props => {
),
},
jetpack_search: {
- title: __( 'Your Jetpack Search is active!', 'jetpack' ),
+ title: __( 'Jetpack Search is active!', 'jetpack' ),
text: __(
- "Next, we'll help you customize your Search experience for your visitors.",
+ "Next, we'll help you customize the Search experience for your visitors.",
'jetpack'
),
},
jetpack_security: {
- title: __( 'Your Jetpack Security is active!', 'jetpack' ),
+ title: __( 'Jetpack Security is active!', 'jetpack' ),
text: createInterpolateElement(
__(
'You can see your backups, security scans, and restore your site on cloud.jetpack.com . If you ever lose access to your site, you can restore it there.',
@@ -92,7 +92,7 @@ const JetpackProductDetails = props => {
),
},
jetpack_videopress: {
- title: __( 'Your Jetpack Videopress is active!', 'jetpack' ),
+ title: __( 'Jetpack VideoPress is active!', 'jetpack' ),
text: __(
'Experience high-quality, ad-free video built specifically for WordPress.',
'jetpack'
diff --git a/projects/js-packages/licensing/components/activation-screen-success-info/test/component.jsx b/projects/js-packages/licensing/components/activation-screen-success-info/test/component.jsx
index 5f33c444cd9b7..e2c92feb6a37f 100644
--- a/projects/js-packages/licensing/components/activation-screen-success-info/test/component.jsx
+++ b/projects/js-packages/licensing/components/activation-screen-success-info/test/component.jsx
@@ -27,9 +27,7 @@ describe( 'ActivationSuccessInfo', () => {
} );
it( 'shows the correct product name', () => {
- expect( jetpackProductDetailsComponent.text() ).to.contain(
- 'Your Jetpack Backup is active!'
- );
+ expect( jetpackProductDetailsComponent.text() ).to.contain( 'Jetpack Backup is active!' );
} );
} );
} );
diff --git a/projects/js-packages/licensing/package.json b/projects/js-packages/licensing/package.json
index b58052ac35b23..20cb698af6836 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-alpha",
+ "version": "0.4.6-alpha",
"description": "Jetpack licensing flow",
"homepage": "https://jetpack.com",
"bugs": {
@@ -20,9 +20,10 @@
"@babel/core": "7.16.0",
"@babel/preset-react": "7.16.0",
"jetpack-js-test-runner": "workspace:*",
+ "nyc": "15.1.0",
"react": "17.0.2",
"react-test-renderer": "17.0.2",
- "@automattic/jetpack-base-styles": "workspace:^0.1.7-alpha"
+ "@automattic/jetpack-base-styles": "workspace:^0.1.8-alpha"
},
"engines": {
"node": "^14.18.3 || ^16.13.2",
@@ -35,12 +36,12 @@
"./action-types": "./src/state/action-types"
},
"dependencies": {
- "@automattic/jetpack-api": "workspace:^0.8.3-alpha",
- "@automattic/jetpack-components": "workspace:^0.10.3-alpha",
- "@wordpress/i18n": "4.2.4",
- "@wordpress/element": "4.0.4",
+ "@automattic/jetpack-api": "workspace:^0.8.4-alpha",
+ "@automattic/jetpack-components": "workspace:^0.10.5-alpha",
+ "@wordpress/i18n": "4.3.0",
+ "@wordpress/element": "4.1.0",
"prop-types": "15.7.2",
- "@wordpress/components": "19.1.6",
- "@wordpress/icons": "6.1.1"
+ "@wordpress/components": "19.3.0",
+ "@wordpress/icons": "6.2.0"
}
}
diff --git a/projects/js-packages/partner-coupon/CHANGELOG.md b/projects/js-packages/partner-coupon/CHANGELOG.md
index e6c9055c4aee0..33b1eba84856b 100644
--- a/projects/js-packages/partner-coupon/CHANGELOG.md
+++ b/projects/js-packages/partner-coupon/CHANGELOG.md
@@ -5,6 +5,17 @@ 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.1.7 - 2022-02-02
+### Changed
+- Updated package dependencies.
+
+## 0.1.6 - 2022-01-25
+### Added
+- Add missing dev dependency on `nyc` for code coverage.
+
+### Changed
+- Updated package dependencies.
+
## 0.1.5 - 2022-01-18
### Changed
- General: update required node version to v16.13.2
diff --git a/projects/js-packages/partner-coupon/changelog/add-fancy-eslint-ignore b/projects/js-packages/partner-coupon/changelog/add-fancy-eslint-ignore
deleted file mode 100644
index afe95e481cf32..0000000000000
--- a/projects/js-packages/partner-coupon/changelog/add-fancy-eslint-ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Hack eslint config to use `.gitignore` and per-dir `.eslintignore`.
-
-
diff --git a/projects/js-packages/storybook/changelog/renovate-pin-dependencies b/projects/js-packages/partner-coupon/changelog/renovate-wordpress-monorepo
similarity index 100%
rename from projects/js-packages/storybook/changelog/renovate-pin-dependencies
rename to projects/js-packages/partner-coupon/changelog/renovate-wordpress-monorepo
diff --git a/projects/js-packages/storybook/changelog/2021-11-02-12-22-14-382297 b/projects/js-packages/partner-coupon/changelog/renovate-wordpress-monorepo#2
similarity index 100%
rename from projects/js-packages/storybook/changelog/2021-11-02-12-22-14-382297
rename to projects/js-packages/partner-coupon/changelog/renovate-wordpress-monorepo#2
diff --git a/projects/js-packages/partner-coupon/changelog/update-project-scripts-no-install b/projects/js-packages/partner-coupon/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/js-packages/partner-coupon/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/js-packages/partner-coupon/package.json b/projects/js-packages/partner-coupon/package.json
index 2d19bc1733d03..de8c17b8ec756 100644
--- a/projects/js-packages/partner-coupon/package.json
+++ b/projects/js-packages/partner-coupon/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@automattic/jetpack-partner-coupon",
- "version": "0.1.6-alpha",
+ "version": "0.1.8-alpha",
"description": "This package aims to add components to make it easier to redeem partner coupons",
"homepage": "https://jetpack.com",
"bugs": {
@@ -20,15 +20,16 @@
"@babel/core": "7.16.0",
"@babel/preset-react": "7.16.0",
"jetpack-js-test-runner": "workspace:*",
+ "nyc": "15.1.0",
"react": "17.0.2"
},
"peerDependencies": {
"react": "^17.0.2"
},
"dependencies": {
- "@automattic/jetpack-connection": "workspace:^0.14.0-alpha",
- "@automattic/jetpack-components": "workspace:^0.10.3-alpha",
- "@wordpress/i18n": "4.2.4",
+ "@automattic/jetpack-connection": "workspace:^0.15.1-alpha",
+ "@automattic/jetpack-components": "workspace:^0.10.5-alpha",
+ "@wordpress/i18n": "4.3.0",
"classnames": "2.3.1",
"prop-types": "15.7.2"
},
diff --git a/projects/js-packages/remove-asset-webpack-plugin/CHANGELOG.md b/projects/js-packages/remove-asset-webpack-plugin/CHANGELOG.md
index 53ce0bd42d302..5f77db3cc9eeb 100644
--- a/projects/js-packages/remove-asset-webpack-plugin/CHANGELOG.md
+++ b/projects/js-packages/remove-asset-webpack-plugin/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).
+## [1.0.3] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [1.0.2] - 2022-01-18
### Changed
- General: update required node version to v16.13.2
@@ -25,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Initial release.
+[1.0.3]: https://github.com/Automattic/remove-asset-webpack-plugin/compare/v1.0.2...v1.0.3
[1.0.2]: https://github.com/Automattic/remove-asset-webpack-plugin/compare/v1.0.1...v1.0.2
[1.0.1]: https://github.com/Automattic/remove-asset-webpack-plugin/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/Automattic/remove-asset-webpack-plugin/compare/v0.1.1...v1.0.0
diff --git a/projects/js-packages/remove-asset-webpack-plugin/changelog/update-project-scripts-no-install b/projects/js-packages/remove-asset-webpack-plugin/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/js-packages/remove-asset-webpack-plugin/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/js-packages/remove-asset-webpack-plugin/package.json b/projects/js-packages/remove-asset-webpack-plugin/package.json
index c72feafae1f4c..a6718a5563ca6 100644
--- a/projects/js-packages/remove-asset-webpack-plugin/package.json
+++ b/projects/js-packages/remove-asset-webpack-plugin/package.json
@@ -1,6 +1,6 @@
{
"name": "@automattic/remove-asset-webpack-plugin",
- "version": "1.0.3-alpha",
+ "version": "1.0.3",
"description": "A Webpack plugin to remove assets from the build.",
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/remove-asset-webpack-plugin/README.md#readme",
"bugs": {
diff --git a/projects/js-packages/shared-extension-utils/.gitattributes b/projects/js-packages/shared-extension-utils/.gitattributes
new file mode 100644
index 0000000000000..7e44bd96454e9
--- /dev/null
+++ b/projects/js-packages/shared-extension-utils/.gitattributes
@@ -0,0 +1,7 @@
+# Files not needed to be distributed in the package.
+.gitattributes export-ignore
+node_modules export-ignore
+
+# Files to exclude from the mirror repo
+/changelog/** production-exclude
+/.eslintrc.cjs production-exclude
diff --git a/projects/js-packages/shared-extension-utils/.gitignore b/projects/js-packages/shared-extension-utils/.gitignore
new file mode 100644
index 0000000000000..140fd587d2d52
--- /dev/null
+++ b/projects/js-packages/shared-extension-utils/.gitignore
@@ -0,0 +1,2 @@
+vendor/
+node_modules/
diff --git a/projects/js-packages/shared-extension-utils/.nycrc b/projects/js-packages/shared-extension-utils/.nycrc
new file mode 100644
index 0000000000000..71db1fbf54c98
--- /dev/null
+++ b/projects/js-packages/shared-extension-utils/.nycrc
@@ -0,0 +1,5 @@
+{
+ "extensions": [".js", ".jsx"],
+ "exclude": "**/test/*.jsx",
+ "reporter": "clover"
+}
diff --git a/projects/js-packages/shared-extension-utils/CHANGELOG.md b/projects/js-packages/shared-extension-utils/CHANGELOG.md
new file mode 100644
index 0000000000000..721294abd00ad
--- /dev/null
+++ b/projects/js-packages/shared-extension-utils/CHANGELOG.md
@@ -0,0 +1,7 @@
+# Changelog
+
+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).
+
diff --git a/projects/js-packages/shared-extension-utils/README.md b/projects/js-packages/shared-extension-utils/README.md
new file mode 100644
index 0000000000000..94c8bab09bd14
--- /dev/null
+++ b/projects/js-packages/shared-extension-utils/README.md
@@ -0,0 +1,26 @@
+# Shared Extension Utilities
+
+Utility functions used by the block editor extensions.
+
+This package is the new home for the code in [the `extensions/shared`
+directory](https://github.com/Automattic/jetpack/tree/master/projects/plugins/jetpack/extensions/shared)
+of the Jetpack plugin, so that plugins can share it. To begin with, we moving
+the code used by the Publicize editor extension, but the goal is to bring over
+all the shared code.
+
+## How to install shared-extension-utils
+
+### Installation From Git Repo
+
+## Contribute
+
+## Get Help
+
+## Security
+
+Need to report a security vulnerability? Go to [https://automattic.com/security/](https://automattic.com/security/) or directly to our security bug bounty site [https://hackerone.com/automattic](https://hackerone.com/automattic).
+
+## License
+
+shared-extension-utils is licensed under [GNU General Public License v2 (or later)](./LICENSE.txt)
+
diff --git a/projects/js-packages/shared-extension-utils/changelog/.gitkeep b/projects/js-packages/shared-extension-utils/changelog/.gitkeep
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/projects/js-packages/shared-extension-utils/changelog/add-shared-extension-utils b/projects/js-packages/shared-extension-utils/changelog/add-shared-extension-utils
new file mode 100644
index 0000000000000..d502d76a51958
--- /dev/null
+++ b/projects/js-packages/shared-extension-utils/changelog/add-shared-extension-utils
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Created the shared-extension-utils library
diff --git a/projects/js-packages/storybook/changelog/fix-style-inconsistencies-in-action-button b/projects/js-packages/shared-extension-utils/changelog/update-shared-extension-utils-prepare-release
similarity index 50%
rename from projects/js-packages/storybook/changelog/fix-style-inconsistencies-in-action-button
rename to projects/js-packages/shared-extension-utils/changelog/update-shared-extension-utils-prepare-release
index c47cb18e82997..df7ae220b753e 100644
--- a/projects/js-packages/storybook/changelog/fix-style-inconsistencies-in-action-button
+++ b/projects/js-packages/shared-extension-utils/changelog/update-shared-extension-utils-prepare-release
@@ -1,4 +1,4 @@
Significance: patch
Type: changed
-Updated package dependencies.
+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
new file mode 100644
index 0000000000000..5e88b1125fe17
--- /dev/null
+++ b/projects/js-packages/shared-extension-utils/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "automattic/jetpack-shared-extension-utils",
+ "description": "Utility functions used by the block editor extensions",
+ "type": "library",
+ "license": "GPL-2.0-or-later",
+ "require": {},
+ "require-dev": {
+ "yoast/phpunit-polyfills": "1.0.3",
+ "automattic/jetpack-changelogger": "^3.0"
+ },
+ "scripts": {
+ "test-js": [
+ "pnpm run test"
+ ],
+ "test-coverage": [
+ "pnpx nyc --report-dir=\"$COVERAGE_DIR\" pnpm run test"
+ ]
+ },
+ "repositories": [
+ {
+ "type": "path",
+ "url": "../../packages/*",
+ "options": {
+ "monorepo": true
+ }
+ }
+ ],
+ "minimum-stability": "dev",
+ "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
new file mode 100644
index 0000000000000..c05e0bfd3ef22
--- /dev/null
+++ b/projects/js-packages/shared-extension-utils/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "@automattic/jetpack-shared-extension-utils",
+ "version": "0.1.0-alpha",
+ "description": "Utility functions used by the block editor extensions",
+ "homepage": "https://jetpack.com",
+ "bugs": {
+ "url": "https://github.com/Automattic/jetpack/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/Automattic/jetpack.git"
+ },
+ "license": "GPL-2.0-or-later",
+ "author": "Automattic",
+ "scripts": {
+ "test": "NODE_ENV=test NODE_PATH=tests:. js-test-runner --jsdom --initfile=test-main.jsx 'glob:./!(node_modules)/**/test/*.@(jsx|js)'"
+ },
+ "devDependencies": {
+ "jetpack-js-test-runner": "workspace:*",
+ "nyc": "15.1.0"
+ },
+ "engines": {
+ "node": "^14.18.3 || ^16.13.2",
+ "pnpm": "^6.23.6",
+ "yarn": "use pnpm instead - see docs/yarn-upgrade.md"
+ },
+ "exports": {
+ ".": "./index.js"
+ }
+}
diff --git a/projects/js-packages/shared-extension-utils/test-main.jsx b/projects/js-packages/shared-extension-utils/test-main.jsx
new file mode 100644
index 0000000000000..c04304cbc5455
--- /dev/null
+++ b/projects/js-packages/shared-extension-utils/test-main.jsx
@@ -0,0 +1,7 @@
+/**
+ * External dependencies
+ */
+import Enzyme from 'enzyme';
+import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
+
+Enzyme.configure( { adapter: new Adapter() } );
diff --git a/projects/js-packages/storybook/CHANGELOG.md b/projects/js-packages/storybook/CHANGELOG.md
index 721294abd00ad..a10c506bb6109 100644
--- a/projects/js-packages/storybook/CHANGELOG.md
+++ b/projects/js-packages/storybook/CHANGELOG.md
@@ -5,3 +5,29 @@ 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.1.0 - 2022-02-01
+### Added
+- Added addons-essentials to the dependencies
+- Added storybook package for generating component previews
+- Add Gutenberg components tree to the storybook
+- Add jetpack-connection package to Storybook config.
+- Add support for base-style stories
+- Add support for the IDC package stories.
+- Storybook: Expose my-jetpack components
+
+### Changed
+- Allow Node ^14.17.6 to be used in this project. This shouldn't change the behavior of the code itself.
+- General: update required node version to v16.13.2
+- Publish to a mirror repo rather than the `gh-pages` branch.
+- Tests: update PHPUnit polyfills dependency (yoast/phpunit-polyfills).
+- Updated package dependencies.
+- Update webpack version to match other monorepo packages
+- Use Node 16.7.0 in tooling. This shouldn't change the behavior of the code itself.
+
+### Removed
+- removed knobs dependency
+- Remove use of deprecated `~` in sass-loader imports.
+
+### Fixed
+- fixed babel/preset-react dependency
+- GH only allows pages to be in `/` or `/docs`, so build to `/docs`.
diff --git a/projects/js-packages/storybook/changelog/add-@wordpress-eslint-plugin b/projects/js-packages/storybook/changelog/add-@wordpress-eslint-plugin
deleted file mode 100644
index 8434a35594759..0000000000000
--- a/projects/js-packages/storybook/changelog/add-@wordpress-eslint-plugin
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Update eslint configuration.
-
-
diff --git a/projects/js-packages/storybook/changelog/add-connection-button-spinner b/projects/js-packages/storybook/changelog/add-connection-button-spinner
deleted file mode 100644
index d7106586de2ce..0000000000000
--- a/projects/js-packages/storybook/changelog/add-connection-button-spinner
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Package version bump.
-
-
diff --git a/projects/js-packages/storybook/changelog/add-fancy-eslint-ignore b/projects/js-packages/storybook/changelog/add-fancy-eslint-ignore
deleted file mode 100644
index afe95e481cf32..0000000000000
--- a/projects/js-packages/storybook/changelog/add-fancy-eslint-ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Hack eslint config to use `.gitignore` and per-dir `.eslintignore`.
-
-
diff --git a/projects/js-packages/storybook/changelog/add-gutenberg-components b/projects/js-packages/storybook/changelog/add-gutenberg-components
deleted file mode 100644
index d404e4477b115..0000000000000
--- a/projects/js-packages/storybook/changelog/add-gutenberg-components
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: added
-
-Add Gutenberg components tree to the storybook
diff --git a/projects/js-packages/storybook/changelog/add-rna-connect-screen-required-plan b/projects/js-packages/storybook/changelog/add-rna-connect-screen-required-plan
deleted file mode 100644
index 339c0db7713f1..0000000000000
--- a/projects/js-packages/storybook/changelog/add-rna-connect-screen-required-plan
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: added
-
-Add jetpack-connection package to Storybook config.
diff --git a/projects/js-packages/storybook/changelog/add-storybook b/projects/js-packages/storybook/changelog/add-storybook
deleted file mode 100644
index b3fc796685da6..0000000000000
--- a/projects/js-packages/storybook/changelog/add-storybook
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: major
-Type: added
-
-Added storybook package for generating component previews
diff --git a/projects/js-packages/storybook/changelog/fix-babel-react-preset-dep b/projects/js-packages/storybook/changelog/fix-babel-react-preset-dep
deleted file mode 100644
index e8515cb792f1a..0000000000000
--- a/projects/js-packages/storybook/changelog/fix-babel-react-preset-dep
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fixed
-
-fixed babel/preset-react dependency
diff --git a/projects/js-packages/storybook/changelog/fix-rm-deprecated-sass-tilde b/projects/js-packages/storybook/changelog/fix-rm-deprecated-sass-tilde
deleted file mode 100644
index cf6ad99ec3974..0000000000000
--- a/projects/js-packages/storybook/changelog/fix-rm-deprecated-sass-tilde
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: removed
-
-Remove use of deprecated `~` in sass-loader imports.
diff --git a/projects/js-packages/storybook/changelog/fix-storybook-in-mirror-repo b/projects/js-packages/storybook/changelog/fix-storybook-in-mirror-repo
deleted file mode 100644
index 77b211dd23dd6..0000000000000
--- a/projects/js-packages/storybook/changelog/fix-storybook-in-mirror-repo
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fixed
-
-GH only allows pages to be in `/` or `/docs`, so build to `/docs`.
diff --git a/projects/js-packages/storybook/changelog/fix-storybook-stories-tab b/projects/js-packages/storybook/changelog/fix-storybook-stories-tab
deleted file mode 100644
index 3f748425caf45..0000000000000
--- a/projects/js-packages/storybook/changelog/fix-storybook-stories-tab
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: fixed
-Comment: Changed related to storybook, not a product feature
-
-
diff --git a/projects/js-packages/storybook/changelog/jetpack-branch-10.5 b/projects/js-packages/storybook/changelog/jetpack-branch-10.5
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/jetpack-branch-10.5
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/js-redirects-add-docs-and-tests b/projects/js-packages/storybook/changelog/js-redirects-add-docs-and-tests
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/js-redirects-add-docs-and-tests
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/prepare-10.4.a.7-release b/projects/js-packages/storybook/changelog/prepare-10.4.a.7-release
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/prepare-10.4.a.7-release
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/remove-es5-skeleton b/projects/js-packages/storybook/changelog/remove-es5-skeleton
deleted file mode 100644
index f63d54c7397b6..0000000000000
--- a/projects/js-packages/storybook/changelog/remove-es5-skeleton
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Removed unused ES5 validation command
-
-
diff --git a/projects/js-packages/storybook/changelog/remove-knobs-from-stories b/projects/js-packages/storybook/changelog/remove-knobs-from-stories
deleted file mode 100644
index f80c5fdb63c1e..0000000000000
--- a/projects/js-packages/storybook/changelog/remove-knobs-from-stories
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: removed
-
-removed knobs dependency
diff --git a/projects/js-packages/storybook/changelog/renovate-storybook-monorepo#7 b/projects/js-packages/storybook/changelog/renovate-storybook-monorepo#7
deleted file mode 100644
index 302aa8cf46b5c..0000000000000
--- a/projects/js-packages/storybook/changelog/renovate-storybook-monorepo#7
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies
diff --git a/projects/js-packages/storybook/changelog/renovate-webpack-5.x b/projects/js-packages/storybook/changelog/renovate-webpack-5.x
deleted file mode 100644
index 302aa8cf46b5c..0000000000000
--- a/projects/js-packages/storybook/changelog/renovate-webpack-5.x
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies
diff --git a/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#2 b/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#2
index 302aa8cf46b5c..c47cb18e82997 100644
--- a/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#2
+++ b/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#2
@@ -1,4 +1,4 @@
Significance: patch
Type: changed
-Updated package dependencies
+Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#4 b/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#4
deleted file mode 100644
index 302aa8cf46b5c..0000000000000
--- a/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#4
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies
diff --git a/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#5 b/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#5
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#5
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#6 b/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#6
deleted file mode 100644
index 302aa8cf46b5c..0000000000000
--- a/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#6
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies
diff --git a/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#7 b/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#7
deleted file mode 100644
index 302aa8cf46b5c..0000000000000
--- a/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#7
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies
diff --git a/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#8 b/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#8
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#8
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#9 b/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#9
deleted file mode 100644
index 302aa8cf46b5c..0000000000000
--- a/projects/js-packages/storybook/changelog/renovate-wordpress-monorepo#9
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies
diff --git a/projects/js-packages/storybook/changelog/renovate-yoast-phpunit-polyfills-1.x b/projects/js-packages/storybook/changelog/renovate-yoast-phpunit-polyfills-1.x
deleted file mode 100644
index 76075df8b7415..0000000000000
--- a/projects/js-packages/storybook/changelog/renovate-yoast-phpunit-polyfills-1.x
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Tests: update PHPUnit polyfills dependency (yoast/phpunit-polyfills).
diff --git a/projects/js-packages/storybook/changelog/renovate-yoast-phpunit-polyfills-1.x#2 b/projects/js-packages/storybook/changelog/renovate-yoast-phpunit-polyfills-1.x#2
deleted file mode 100644
index 302aa8cf46b5c..0000000000000
--- a/projects/js-packages/storybook/changelog/renovate-yoast-phpunit-polyfills-1.x#2
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies
diff --git a/projects/js-packages/storybook/changelog/renovate-yoast-phpunit-polyfills-1.x#3 b/projects/js-packages/storybook/changelog/renovate-yoast-phpunit-polyfills-1.x#3
deleted file mode 100644
index 302aa8cf46b5c..0000000000000
--- a/projects/js-packages/storybook/changelog/renovate-yoast-phpunit-polyfills-1.x#3
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies
diff --git a/projects/js-packages/storybook/changelog/try-allow-node-14 b/projects/js-packages/storybook/changelog/try-allow-node-14
deleted file mode 100644
index ae4eabbe48728..0000000000000
--- a/projects/js-packages/storybook/changelog/try-allow-node-14
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Allow Node ^14.17.6 to be used in this project. This shouldn't change the behavior of the code itself.
diff --git a/projects/js-packages/storybook/changelog/try-html-only-stories b/projects/js-packages/storybook/changelog/try-html-only-stories
deleted file mode 100644
index f138e8c20beab..0000000000000
--- a/projects/js-packages/storybook/changelog/try-html-only-stories
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: added
-
-Add support for base-style stories
diff --git a/projects/js-packages/storybook/changelog/try-rna-connection-store-export b/projects/js-packages/storybook/changelog/try-rna-connection-store-export
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/try-rna-connection-store-export
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/try-update-to-node-16 b/projects/js-packages/storybook/changelog/try-update-to-node-16
deleted file mode 100644
index ab9d2ba5149bc..0000000000000
--- a/projects/js-packages/storybook/changelog/try-update-to-node-16
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Use Node 16.7.0 in tooling. This shouldn't change the behavior of the code itself.
diff --git a/projects/js-packages/storybook/changelog/try-update-webpack-globally b/projects/js-packages/storybook/changelog/try-update-webpack-globally
deleted file mode 100644
index db3b9b8a2c639..0000000000000
--- a/projects/js-packages/storybook/changelog/try-update-webpack-globally
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Update webpack version to match other monorepo packages
diff --git a/projects/js-packages/storybook/changelog/update-backport-connection-package b/projects/js-packages/storybook/changelog/update-backport-connection-package
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/update-backport-connection-package
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/update-changelog-cherry-pick b/projects/js-packages/storybook/changelog/update-changelog-cherry-pick
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/update-changelog-cherry-pick
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/update-connect-button-visual-component b/projects/js-packages/storybook/changelog/update-connect-button-visual-component
deleted file mode 100644
index d41bfb7e83798..0000000000000
--- a/projects/js-packages/storybook/changelog/update-connect-button-visual-component
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: added
-
-Added addons-essentials to the dependencies
diff --git a/projects/js-packages/storybook/changelog/update-css-modules b/projects/js-packages/storybook/changelog/update-css-modules
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/update-css-modules
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/update-extend-disconnect-dialog-component b/projects/js-packages/storybook/changelog/update-extend-disconnect-dialog-component
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/update-extend-disconnect-dialog-component
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/update-idc-storybook b/projects/js-packages/storybook/changelog/update-idc-storybook
deleted file mode 100644
index fb6ace52a6e5d..0000000000000
--- a/projects/js-packages/storybook/changelog/update-idc-storybook
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: added
-
-Add support for the IDC package stories.
diff --git a/projects/js-packages/storybook/changelog/update-idc_text b/projects/js-packages/storybook/changelog/update-idc_text
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/update-idc_text
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/update-jetpack-base-styles b/projects/js-packages/storybook/changelog/update-jetpack-base-styles
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/update-jetpack-base-styles
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/update-jetpack-color-primary b/projects/js-packages/storybook/changelog/update-jetpack-color-primary
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/update-jetpack-color-primary
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/update-js-pkg-idc-v0.8.0 b/projects/js-packages/storybook/changelog/update-js-pkg-idc-v0.8.0
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/update-js-pkg-idc-v0.8.0
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/update-min-pnpm-version b/projects/js-packages/storybook/changelog/update-min-pnpm-version
deleted file mode 100644
index d824134990470..0000000000000
--- a/projects/js-packages/storybook/changelog/update-min-pnpm-version
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Update minimum pnpm version.
-
-
diff --git a/projects/js-packages/storybook/changelog/update-my-jetpack-storybook b/projects/js-packages/storybook/changelog/update-my-jetpack-storybook
deleted file mode 100644
index 25bc5ef232cea..0000000000000
--- a/projects/js-packages/storybook/changelog/update-my-jetpack-storybook
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: added
-
-Storybook: Expose my-jetpack components
diff --git a/projects/js-packages/storybook/changelog/update-node-version-16132 b/projects/js-packages/storybook/changelog/update-node-version-16132
deleted file mode 100644
index feb2fb77ea11d..0000000000000
--- a/projects/js-packages/storybook/changelog/update-node-version-16132
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-General: update required node version to v16.13.2
diff --git a/projects/js-packages/storybook/changelog/update-project-scripts-no-install b/projects/js-packages/storybook/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/js-packages/storybook/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/js-packages/storybook/changelog/update-public-base-styles b/projects/js-packages/storybook/changelog/update-public-base-styles
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/update-public-base-styles
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/update-refactor-connection-button b/projects/js-packages/storybook/changelog/update-refactor-connection-button
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/update-refactor-connection-button
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/update-refactor-number-format-location b/projects/js-packages/storybook/changelog/update-refactor-number-format-location
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/update-refactor-number-format-location
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/update-rna-connection-move-register-to-store b/projects/js-packages/storybook/changelog/update-rna-connection-move-register-to-store
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/update-rna-connection-move-register-to-store
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/update-rna-pricing-card-same-price b/projects/js-packages/storybook/changelog/update-rna-pricing-card-same-price
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/update-rna-pricing-card-same-price
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/update-storybook-add-jetpack-dashboard-bg-color b/projects/js-packages/storybook/changelog/update-storybook-add-jetpack-dashboard-bg-color
new file mode 100644
index 0000000000000..731644122d202
--- /dev/null
+++ b/projects/js-packages/storybook/changelog/update-storybook-add-jetpack-dashboard-bg-color
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Add 'Jetpack Dashboard' background color
diff --git a/projects/js-packages/storybook/changelog/update-storybook-in-mirror-repo b/projects/js-packages/storybook/changelog/update-storybook-in-mirror-repo
deleted file mode 100644
index edb7c1dfdd387..0000000000000
--- a/projects/js-packages/storybook/changelog/update-storybook-in-mirror-repo
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Publish to a mirror repo rather than the `gh-pages` branch.
diff --git a/projects/js-packages/storybook/changelog/update-user-data-on-initial-state b/projects/js-packages/storybook/changelog/update-user-data-on-initial-state
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/js-packages/storybook/changelog/update-user-data-on-initial-state
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/package.json b/projects/js-packages/storybook/package.json
index 953c7d894c9f2..7cb2265914931 100644
--- a/projects/js-packages/storybook/package.json
+++ b/projects/js-packages/storybook/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@automattic/jetpack-storybook",
- "version": "0.1.0-alpha",
+ "version": "0.2.0-alpha",
"description": "Jetpack components storybook",
"homepage": "https://jetpack.com",
"bugs": {
@@ -23,7 +23,7 @@
"storybook:dev": "concurrently \"pnpm run dev:packages\" \"start-storybook -c ./storybook -p 50240 -s ./public\""
},
"devDependencies": {
- "@automattic/jetpack-base-styles": "workspace:^0.1.7-alpha",
+ "@automattic/jetpack-base-styles": "workspace:^0.1.8-alpha",
"@babel/core": "7.16.0",
"@babel/plugin-syntax-jsx": "7.16.0",
"@babel/preset-react": "7.16.0",
@@ -45,12 +45,12 @@
"@storybook/builder-webpack5": "6.4.10",
"@storybook/manager-webpack5": "6.4.10",
"@storybook/react": "6.4.10",
- "@wordpress/babel-preset-default": "6.4.1",
- "@wordpress/block-editor": "8.0.12",
- "@wordpress/block-library": "6.0.17",
- "@wordpress/components": "19.1.6",
- "@wordpress/format-library": "3.0.18",
- "@wordpress/postcss-plugins-preset": "3.2.5",
+ "@wordpress/babel-preset-default": "6.5.0",
+ "@wordpress/block-editor": "8.1.0",
+ "@wordpress/block-library": "6.1.0",
+ "@wordpress/components": "19.3.0",
+ "@wordpress/format-library": "3.1.0",
+ "@wordpress/postcss-plugins-preset": "3.3.0",
"babel-plugin-inline-json-import": "0.3.2",
"concurrently": "6.0.2",
"css-loader": "6.5.1",
diff --git a/projects/js-packages/storybook/storybook/preview.js b/projects/js-packages/storybook/storybook/preview.js
index 0974d9021fb93..25af1a2e3c5cb 100644
--- a/projects/js-packages/storybook/storybook/preview.js
+++ b/projects/js-packages/storybook/storybook/preview.js
@@ -17,3 +17,23 @@ import { withA11y } from '@storybook/addon-a11y';
import './style.scss';
addDecorator( withA11y );
+
+export const parameters = {
+ backgrounds: {
+ default: 'Jetpack Dashboard',
+ values: [
+ {
+ name: 'Jetpack Dashboard',
+ value: 'var(--jp-white-off)',
+ },
+ {
+ name: 'Dark',
+ value: 'rgb(51, 51, 51)',
+ },
+ {
+ name: 'Light',
+ value: '#FFF',
+ },
+ ],
+ },
+};
diff --git a/projects/js-packages/webpack-config/CHANGELOG.md b/projects/js-packages/webpack-config/CHANGELOG.md
index 67427c4edbc97..4fcc3415a323a 100644
--- a/projects/js-packages/webpack-config/CHANGELOG.md
+++ b/projects/js-packages/webpack-config/CHANGELOG.md
@@ -5,6 +5,17 @@ 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).
+## 1.1.1 - 2022-01-27
+### Changed
+- Updated package dependencies.
+
+## 1.1.0 - 2022-01-25
+### Added
+- Add missing dev dependency on `nyc` for code coverage.
+
+### Changed
+- Updated package dependencies. Major version bump for i18n-loader-webpack-plugin.
+
## 1.0.2 - 2022-01-18
### Changed
- General: update required node version to v16.13.2
diff --git a/projects/js-packages/storybook/changelog/renovate-sass-loader-12.x b/projects/js-packages/webpack-config/changelog/renovate-wordpress-monorepo
similarity index 100%
rename from projects/js-packages/storybook/changelog/renovate-sass-loader-12.x
rename to projects/js-packages/webpack-config/changelog/renovate-wordpress-monorepo
diff --git a/projects/js-packages/storybook/changelog/2021-11-09-14-35-04-196965 b/projects/js-packages/webpack-config/changelog/renovate-wordpress-monorepo#2
similarity index 100%
rename from projects/js-packages/storybook/changelog/2021-11-09-14-35-04-196965
rename to projects/js-packages/webpack-config/changelog/renovate-wordpress-monorepo#2
diff --git a/projects/js-packages/webpack-config/changelog/update-project-scripts-no-install b/projects/js-packages/webpack-config/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/js-packages/webpack-config/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/js-packages/webpack-config/package.json b/projects/js-packages/webpack-config/package.json
index 970aeff61fb5c..23b6924661a75 100644
--- a/projects/js-packages/webpack-config/package.json
+++ b/projects/js-packages/webpack-config/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@automattic/jetpack-webpack-config",
- "version": "1.0.3-alpha",
+ "version": "1.1.2-alpha",
"description": "Library of pieces for webpack config in Jetpack projects.",
"homepage": "https://jetpack.com",
"bugs": {
@@ -18,9 +18,9 @@
},
"dependencies": {
"@automattic/babel-plugin-preserve-i18n": "1.0.0",
- "@automattic/babel-plugin-replace-textdomain": "workspace:^1.0.2-alpha",
- "@automattic/i18n-check-webpack-plugin": "workspace:^1.0.3-alpha",
- "@automattic/i18n-loader-webpack-plugin": "workspace:^1.0.3-alpha",
+ "@automattic/babel-plugin-replace-textdomain": "workspace:^1.0.2",
+ "@automattic/i18n-check-webpack-plugin": "workspace:^1.0.4",
+ "@automattic/i18n-loader-webpack-plugin": "workspace:^2.0.1-alpha",
"@automattic/webpack-rtl-plugin": "5.1.0",
"@babel/compat-data": "7.16.4",
"@babel/helper-compilation-targets": "7.16.3",
@@ -30,7 +30,7 @@
"@babel/preset-react": "7.16.0",
"@babel/preset-typescript": "7.16.0",
"@wordpress/browserslist-config": "4.1.0",
- "@wordpress/dependency-extraction-webpack-plugin": "3.2.1",
+ "@wordpress/dependency-extraction-webpack-plugin": "3.3.0",
"babel-loader": "8.2.3",
"browserslist": "4.19.1",
"css-loader": "6.5.1",
@@ -43,6 +43,7 @@
"devDependencies": {
"@babel/core": "7.16.0",
"@babel/runtime": "7.16.3",
+ "nyc": "15.1.0",
"webpack": "5.65.0",
"webpack-cli": "4.9.1"
},
diff --git a/projects/packages/a8c-mc-stats/CHANGELOG.md b/projects/packages/a8c-mc-stats/CHANGELOG.md
index 15ab7fa59f9f5..465b913c9db05 100644
--- a/projects/packages/a8c-mc-stats/CHANGELOG.md
+++ b/projects/packages/a8c-mc-stats/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).
+## [1.4.12] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [1.4.11] - 2022-01-04
### Changed
- Switch to pcov for code coverage.
@@ -83,6 +87,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Creates the MC Stats package
+[1.4.12]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.11...v1.4.12
[1.4.11]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.10...v1.4.11
[1.4.10]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.9...v1.4.10
[1.4.9]: https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v1.4.8...v1.4.9
diff --git a/projects/packages/a8c-mc-stats/changelog/update-project-scripts-no-install b/projects/packages/a8c-mc-stats/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/a8c-mc-stats/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/abtest/CHANGELOG.md b/projects/packages/abtest/CHANGELOG.md
index 1c0a26243c720..398465f0b94bf 100644
--- a/projects/packages/abtest/CHANGELOG.md
+++ b/projects/packages/abtest/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).
+## [1.9.20] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [1.9.19] - 2022-01-18
### Changed
- Updated package dependencies.
@@ -208,6 +212,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Packages: Introduce a simple A/B test package
+[1.9.20]: https://github.com/Automattic/jetpack-abtest/compare/v1.9.19...v1.9.20
[1.9.19]: https://github.com/Automattic/jetpack-abtest/compare/v1.9.18...v1.9.19
[1.9.18]: https://github.com/Automattic/jetpack-abtest/compare/v1.9.17...v1.9.18
[1.9.17]: https://github.com/Automattic/jetpack-abtest/compare/v1.9.16...v1.9.17
diff --git a/projects/packages/abtest/changelog/update-project-scripts-no-install b/projects/packages/abtest/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/abtest/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/admin-ui/.phpcs.dir.xml b/projects/packages/admin-ui/.phpcs.dir.xml
new file mode 100644
index 0000000000000..8ebbc7cd35a5f
--- /dev/null
+++ b/projects/packages/admin-ui/.phpcs.dir.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/projects/packages/admin-ui/CHANGELOG.md b/projects/packages/admin-ui/CHANGELOG.md
index a0d80d685c00a..dbfcc276d447e 100644
--- a/projects/packages/admin-ui/CHANGELOG.md
+++ b/projects/packages/admin-ui/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.2.3] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [0.2.2] - 2022-01-18
### Changed
- General: update required node version to v16.13.2
@@ -33,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixing menu visibility issues.
+[0.2.3]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.2...0.2.3
[0.2.2]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.1...0.2.2
[0.2.1]: https://github.com/Automattic/jetpack-admin-ui/compare/0.2.0...0.2.1
[0.2.0]: https://github.com/Automattic/jetpack-admin-ui/compare/0.1.1...0.2.0
diff --git a/projects/packages/admin-ui/changelog/add-anti-spam-my-jetpack b/projects/packages/admin-ui/changelog/add-anti-spam-my-jetpack
new file mode 100644
index 0000000000000..903c769c75af2
--- /dev/null
+++ b/projects/packages/admin-ui/changelog/add-anti-spam-my-jetpack
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Support for akismet menu with stand-alone plugins
diff --git a/projects/packages/admin-ui/changelog/update-project-scripts-no-install b/projects/packages/admin-ui/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/admin-ui/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/admin-ui/changelog/workaround-jetpack-submenu-order b/projects/packages/admin-ui/changelog/workaround-jetpack-submenu-order
new file mode 100644
index 0000000000000..c910c58a41d9f
--- /dev/null
+++ b/projects/packages/admin-ui/changelog/workaround-jetpack-submenu-order
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Fixes menu order working around a bug in add_submenu_page
diff --git a/projects/packages/admin-ui/composer.json b/projects/packages/admin-ui/composer.json
index 770d8b70c8382..d0e8902fe75b3 100644
--- a/projects/packages/admin-ui/composer.json
+++ b/projects/packages/admin-ui/composer.json
@@ -40,6 +40,7 @@
"extra": {
"autotagger": true,
"mirror-repo": "Automattic/jetpack-admin-ui",
+ "textdomain": "jetpack-admin-ui",
"changelogger": {
"link-template": "https://github.com/Automattic/jetpack-admin-ui/compare/${old}...${new}"
},
diff --git a/projects/packages/admin-ui/package.json b/projects/packages/admin-ui/package.json
index 63c4b8e7d6b9f..3dc6eaf75aa40 100644
--- a/projects/packages/admin-ui/package.json
+++ b/projects/packages/admin-ui/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@automattic/jetpack-admin-ui",
- "version": "0.2.3-alpha",
+ "version": "0.2.4-alpha",
"description": "Generic Jetpack wp-admin UI elements",
"homepage": "https://jetpack.com",
"bugs": {
diff --git a/projects/packages/admin-ui/src/class-admin-menu.php b/projects/packages/admin-ui/src/class-admin-menu.php
index 0156f22a6592a..f62b3ee5fbb51 100644
--- a/projects/packages/admin-ui/src/class-admin-menu.php
+++ b/projects/packages/admin-ui/src/class-admin-menu.php
@@ -13,7 +13,7 @@
*/
class Admin_Menu {
- const PACKAGE_VERSION = '0.2.3-alpha';
+ const PACKAGE_VERSION = '0.2.4-alpha';
/**
* Whether this class has been initialized
@@ -37,10 +37,34 @@ class Admin_Menu {
public static function init() {
if ( ! self::$initialized ) {
self::$initialized = true;
+ self::handle_akismet_menu();
add_action( 'admin_menu', array( __CLASS__, 'admin_menu_hook_callback' ), 1000 ); // Jetpack uses 998.
}
}
+ /**
+ * Handles the Akismet menu item when used alongside other stand-alone plugins
+ *
+ * When Jetpack plugin is present, Akismet menu item is moved under the Jetpack top level menu, but if Akismet is active alongside other stand-alone plugins,
+ * we use this method to move the menu item.
+ */
+ private static function handle_akismet_menu() {
+ if ( class_exists( 'Akismet_Admin' ) ) {
+ // Prevent Akismet from adding a menu item.
+ add_action(
+ 'admin_menu',
+ function () {
+ remove_action( 'admin_menu', array( 'Akismet_Admin', 'admin_menu' ), 5 );
+ },
+ 4
+ );
+
+ // Add an Anti-spam menu item for Jetpack.
+ self::add_menu( __( 'Anti-Spam', 'jetpack-admin-ui' ), __( 'Anti-Spam', 'jetpack-admin-ui' ), 'manage_options', 'akismet-key-config', array( 'Akismet_Admin', 'display_page' ) );
+
+ }
+ }
+
/**
* Enqueue styles for the top level menu
*
@@ -80,6 +104,22 @@ public static function admin_menu_hook_callback() {
$can_see_toplevel_menu = false;
}
+ /**
+ * The add_sub_menu function has a bug and will not keep the right order of menu items.
+ *
+ * @see https://core.trac.wordpress.org/ticket/52035
+ * Let's order the items before registering them.
+ * Since this all happens after the Jetpack plugin menu items were added, all items will be added after Jetpack plugin items - unless position is very low number (smaller than the number of menu items present in Jetpack plugin).
+ */
+ usort(
+ self::$menu_items,
+ function ( $a, $b ) {
+ $position_a = empty( $a['position'] ) ? 0 : $a['position'];
+ $position_b = empty( $b['position'] ) ? 0 : $b['position'];
+ return $position_a - $position_b;
+ }
+ );
+
foreach ( self::$menu_items as $menu_item ) {
if ( ! current_user_can( $menu_item['capability'] ) ) {
continue;
diff --git a/projects/packages/analyzer/.phpcs.dir.phpcompatibility.xml b/projects/packages/analyzer/.phpcs.dir.phpcompatibility.xml
new file mode 100644
index 0000000000000..72dd08d90e209
--- /dev/null
+++ b/projects/packages/analyzer/.phpcs.dir.phpcompatibility.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+ /*
+
+
diff --git a/projects/packages/analyzer/.phpcs.dir.xml b/projects/packages/analyzer/.phpcs.dir.xml
new file mode 100644
index 0000000000000..4985d18e46171
--- /dev/null
+++ b/projects/packages/analyzer/.phpcs.dir.xml
@@ -0,0 +1,7 @@
+
+
+
+
+ /*
+
+
diff --git a/projects/packages/my-jetpack/changelog/add-analytics-to-my-jetpack b/projects/packages/analyzer/changelog/add-analyzer-api
similarity index 57%
rename from projects/packages/my-jetpack/changelog/add-analytics-to-my-jetpack
rename to projects/packages/analyzer/changelog/add-analyzer-api
index d89d62345a926..e06b5eec33bc8 100644
--- a/projects/packages/my-jetpack/changelog/add-analytics-to-my-jetpack
+++ b/projects/packages/analyzer/changelog/add-analyzer-api
@@ -1,4 +1,4 @@
Significance: patch
Type: added
-Added useAnalytics hook
+API for anayzer service
diff --git a/projects/packages/analyzer/composer.json b/projects/packages/analyzer/composer.json
index ed089d06f70e6..14968ca6a233d 100644
--- a/projects/packages/analyzer/composer.json
+++ b/projects/packages/analyzer/composer.json
@@ -7,6 +7,7 @@
"nikic/php-parser": "4.13.2"
},
"require-dev": {
+ "php": "^8.0",
"automattic/jetpack-changelogger": "^3.0"
},
"autoload": {
diff --git a/projects/packages/analyzer/scripts/jp-analyze-parallel.php b/projects/packages/analyzer/scripts/jp-analyze-parallel.php
new file mode 100644
index 0000000000000..1336f4531d6ee
--- /dev/null
+++ b/projects/packages/analyzer/scripts/jp-analyze-parallel.php
@@ -0,0 +1,99 @@
+model = new Model();
+ }
+
+ public function start_proc( $folder_name ) {
+ echo 'Starting ' . basename( $folder_name ) . "\n";
+ $cmd = array( 'php', dirname( __DIR__ ) . '/scripts/jp-warnings-job.php', $folder_name );
+ $this->count++;
+
+ $descriptorspec = array(
+ 0 => array( 'file', '/dev/null', 'r' ),
+ 1 => array( 'file', dirname( __DIR__ ) . '/output.txt', 'a' ),
+ 2 => array( 'file', dirname( __DIR__ ) . '/output.txt', 'a' ),
+ );
+ $process = proc_open( $cmd, $descriptorspec, $pipes );
+ $this->procs[] = $process;
+ $this->model->update_process( 0, basename( $folder_name ) );
+ }
+
+ public function check_procs() {
+ foreach ( $this->procs as $id => $proc ) {
+ $status = proc_get_status( $proc );
+ if ( ! $status['running'] ) {
+ $folder_name = str_replace("'", '', explode( ' ', $status['command'] ))[2];
+ $this->model->update_process( -1, null, basename( $folder_name ) );
+
+ $this->count--;
+
+ proc_close( $proc );
+ unset( $this->procs[ $id ] );
+
+ echo 'Done with ' . basename( $folder_name ) . "\n";
+ }
+ }
+ }
+
+ public function wait_for_procs( $count = 10 ) {
+ $this->check_procs();
+ while ( $this->count > $count ) {
+ sleep( 1 );
+ $this->check_procs();
+ }
+ }
+
+ public function get_count() {
+ return $this->count;
+ }
+ public function scan( $arr ) {
+ $this->model->update_process( count( $arr ) );
+ foreach ( $arr as $folder_name ) {
+ $this->wait_for_procs();
+ $this->start_proc( $folder_name );
+ }
+
+ $this->wait_for_procs( 0 );
+
+ echo 'Analysis finished' . "\n";
+
+ $this->model->toggle_status();
+ $this->model->load_result();
+ Locker::unlock();
+ }
+}
+
+$type = $argv[1];
+$old = $argv[2];
+$new = $argv[3];
+
+$pd = new PluginDownloader($type);
+$old_path = $pd->get_version($old);
+$new_path = $pd->get_version($new);
+
+Scripts::cleanup();
+Scripts::get_differences( $new_path, $old_path );
+
+// $arr = glob( $slurper_path . '/*' );
+$arr = array( $slurper_path . '/connect-for-woocommerce', $slurper_path . '/easyreservations' );
+$mngr = new ScanManager();
+$mngr->scan( $arr );
diff --git a/projects/packages/analyzer/scripts/jp-warnings-job.php b/projects/packages/analyzer/scripts/jp-warnings-job.php
new file mode 100755
index 0000000000000..c552b9320c10e
--- /dev/null
+++ b/projects/packages/analyzer/scripts/jp-warnings-job.php
@@ -0,0 +1,14 @@
+old_declaration->display_name(),
);
}
+
+ /**
+ * Returns an array of the Warnings item.
+ *
+ * @return array mapped array ready for JSONification.
+ */
+ public function to_map() {
+ return array(
+ 'warn_type' => $this->type,
+ 'path' => $this->path,
+ 'line' => $this->line,
+ 'message' => $this->message,
+ 'display_name' => $this->old_declaration->display_name(),
+ );
+ }
}
diff --git a/projects/packages/analyzer/src/api/class-analyze-controller.php b/projects/packages/analyzer/src/api/class-analyze-controller.php
new file mode 100644
index 0000000000000..87638f400c2da
--- /dev/null
+++ b/projects/packages/analyzer/src/api/class-analyze-controller.php
@@ -0,0 +1,51 @@
+getMessage() ) {
+ return $this->model->get_status();
+ }
+ throw $th;
+ }
+
+ $this->model->reset();
+ $this->model->toggle_status();
+
+ $this->start_scan();
+
+ return $this->model->get_status();
+ }
+
+ private function start_scan() {
+ // TODO: maybe sanitize params?
+ $params = $this->params->get_params();
+ $args = $params['type'] . ' ' . $params['old'] . ' ' . $params['new'] . ' ';
+ $analyzer_folder = realpath( dirname( __DIR__ ) . '/../' );
+ // $cmd = 'nohup php ' . escapeshellarg( $analyzer_folder . '/scripts/jp-analyze-parallel.php' ) . ' ' . $args . ' &';
+ $cmd = array(
+ 'nohup',
+ 'php',
+ $analyzer_folder . '/scripts/jp-analyze-parallel.php',
+ $params['type'],
+ $params['old'],
+ $params['new'],
+ '&'
+ );
+ $descriptor_spec = array(
+ 0 => array( 'file', '/dev/null', 'r' ),
+ 1 => array( 'file', $analyzer_folder . '/output.txt', 'a' ),
+ 2 => array( 'file', $analyzer_folder . '/output.txt', 'a' ),
+ );
+
+ proc_open( $cmd, $descriptor_spec, $pipes );
+ }
+
+ public function post() {
+ return array();
+ }
+}
diff --git a/projects/packages/analyzer/src/api/class-controller.php b/projects/packages/analyzer/src/api/class-controller.php
new file mode 100644
index 0000000000000..703b8b65a02e3
--- /dev/null
+++ b/projects/packages/analyzer/src/api/class-controller.php
@@ -0,0 +1,35 @@
+model = new Model();
+ $this->params = New Params();
+ }
+
+ public function process() {
+ $out = array( 'result' => 'OK' );
+ $data = '';
+ switch ( $_SERVER['REQUEST_METHOD'] ) {
+ case 'GET':
+ $data = $this->get();
+ break;
+ case 'POST':
+ $data = $this->post();
+ break;
+
+ default:
+ break;
+ }
+
+ $out['data'] = $data;
+
+ return $out;
+ }
+}
diff --git a/projects/packages/analyzer/src/api/class-model.php b/projects/packages/analyzer/src/api/class-model.php
new file mode 100644
index 0000000000000..5aa928ff455d2
--- /dev/null
+++ b/projects/packages/analyzer/src/api/class-model.php
@@ -0,0 +1,96 @@
+db_file = dirname( __DIR__ ) . '/data-store.json';
+ }
+
+ public function get_status() {
+ $this->load();
+ return $this->content;
+ }
+
+ public function toggle_status() {
+ $this->load();
+ if ($this->content['status'] === 'finished') {
+ $this->content['status'] = 'in_progress';
+ } else {
+ $this->content['status'] = 'finished';
+ }
+ $this->persist();
+ }
+
+ public function get() {
+ return $this->content;
+ }
+
+ public function update_process( $increment, $started_plugin = null, $finished_plugin = null ) {
+ $this->load();
+ if ( ! isset( $this->content['progress'] ) ) {
+ $this->content['progress'] = array(
+ 'plugins_left' => 0,
+ 'in_progress' => array(),
+ );
+ }
+
+ $current_list = $this->content['progress']['in_progress'];
+
+ if ( null !== $finished_plugin && false !== ( $key = array_search( $finished_plugin, $current_list ) ) ) {
+ array_splice( $current_list, $key, 1 );
+ }
+
+ if ( null !== $started_plugin ) {
+ array_push( $current_list, $started_plugin );
+ }
+
+ $this->content['progress']['in_progress'] = $current_list;
+ $this->content['progress']['plugins_left'] += $increment;
+ $this->persist();
+ }
+
+ public function load_result() {
+ $this->load();
+ $warn_folder = dirname( dirname( __DIR__ ) ) . '/output/warnings';
+ $warnings = array_values ( array_diff( scandir( $warn_folder ), array( '.', '..' ) ) );
+
+ $this->content['result'] = array_reduce(
+ $warnings,
+ function ( $acc, $file ) use ( $warn_folder ) {
+ $file_data = json_decode( file_get_contents( $warn_folder . '/' . $file ) );
+ $key = pathinfo($file)['filename'];
+ $acc[$key] = $file_data;
+ return $acc;
+ },
+ array()
+ );
+ $this->persist();
+ }
+
+ public function load() {
+ if ( ! file_exists( $this->db_file ) ) {
+ $this->reset();
+ }
+ $json = file_get_contents( $this->db_file );
+ $this->content = json_decode( $json, true );
+ echo 'DEBUG OUTPUT: ';
+ print_r( $json );
+ echo ' ';
+
+ return $this->content;
+ }
+
+ public function persist( $arr = null ) {
+ if ( $arr === null ) {
+ $arr = $this->content;
+ }
+ file_put_contents( $this->db_file, json_encode( $arr ) );
+ }
+
+ public function reset() {
+ $this->persist( array( 'status' => 'finished' ) );
+ }
+}
diff --git a/projects/packages/analyzer/src/api/class-params.php b/projects/packages/analyzer/src/api/class-params.php
new file mode 100644
index 0000000000000..83c55921bcbe6
--- /dev/null
+++ b/projects/packages/analyzer/src/api/class-params.php
@@ -0,0 +1,20 @@
+get_params();
+ if( array_key_exists( $param, $params ) ) {
+ return $params[$param];
+ }
+ return false;
+ }
+}
diff --git a/projects/packages/analyzer/src/api/class-plugin-downloader.php b/projects/packages/analyzer/src/api/class-plugin-downloader.php
new file mode 100644
index 0000000000000..7e499ee0db294
--- /dev/null
+++ b/projects/packages/analyzer/src/api/class-plugin-downloader.php
@@ -0,0 +1,104 @@
+type = $type;
+ $this->output_dir = realpath( dirname( __DIR__ ) . '/../output' ) . '/';
+ }
+
+ public function get_version($version) {
+ echo 'Getting plugin ' . $this->type . '::' . $version . "\n";
+
+ $target_path = $this->get_target_path($version);
+ if (file_exists($target_path)) {
+ if( $version !== 'master' ) {
+ return $target_path;
+ }
+
+ echo "Removing " . $target_path . "\n";
+ Scripts::rm($target_path);
+ }
+
+ $url = $this->build_url($version);
+ $file_path = $this->download_raw($url);
+ $target_path = $this->unzip($file_path, $version);
+ echo 'Done with plugin ' . $this->type . '::' . $version . "\n";
+ return $target_path;
+ }
+
+ private function get_target_path($version) {
+ $target_folder = $this->type . '__' . $version;
+ return $this->output_dir . $target_folder;
+ }
+
+ private function download_raw($url) {
+ $url_filename = basename( parse_url( $url, PHP_URL_PATH ) );
+
+ $file_path = $this->output_dir . $url_filename;
+ if (false === strpos($url_filename, '.zip')) {
+ $file_path = $file_path . '.zip';
+ }
+ $context = stream_context_create(array('http' => array(
+ 'header' => 'User-Agent: jp-analyzer',
+ )));
+ $out = file_get_contents( $url, false, $context );
+ file_put_contents($file_path, $out);
+
+ return $file_path;
+ }
+
+ private function unzip($file_path, $version) {
+ $target_path = $this->get_target_path($version);
+ $zip = new \ZipArchive;
+
+ if ($zip->open($file_path) !== true) {
+ throw new \Exception("Failed to open plugin's zip file " . $file_path);
+ }
+ for($i = 0; $i < $zip->numFiles; $i++) {
+ $filename = $target_path . '/' . substr_replace($zip->getNameIndex($i), '', 0, strlen($zip->getNameIndex(0)));
+
+ if (substr( $filename, -1 ) === '/' && !file_exists($filename)) {
+ mkdir($filename);
+ } else {
+ copy("zip://" . $file_path . "#" . $zip->getNameIndex($i), $filename);
+ }
+ }
+ $zip->close();
+ return $target_path;
+ }
+
+ private function build_url($version) {
+ if ($this->type === 'jetpack') {
+ // https://api.github.com/repos/Automattic/jetpack-production/tags
+ // https://betadownload.jetpack.me/data/jetpack/master/jetpack-dev.zip
+ if ($version === 'master') {
+ return 'https://betadownload.jetpack.me/data/jetpack/master/jetpack-dev.zip';
+ }
+
+ $context = stream_context_create(array('http' => array( 'header' => 'User-Agent: jp-analyzer')));
+ $out = file_get_contents( 'https://api.github.com/repos/Automattic/jetpack-production/tags', false, $context );
+ $json = json_decode($out);
+ $version_obj = null;
+ foreach ($json as $key => $obj) {
+ if ($obj->name === $version) {
+ $version_obj = $obj;
+ break;
+ }
+ }
+
+ if ($version_obj === null) {
+ throw new \Exception($version . ' not found for ' . $this->type);
+ }
+
+ return $version_obj->zipball_url;
+ } else {
+ throw new \Exception( $this->type . ' is unsupported.');
+ }
+ }
+}
diff --git a/projects/packages/analyzer/src/api/class-router.php b/projects/packages/analyzer/src/api/class-router.php
new file mode 100644
index 0000000000000..e81feab9e72aa
--- /dev/null
+++ b/projects/packages/analyzer/src/api/class-router.php
@@ -0,0 +1,29 @@
+status = new Status_Controller();
+ $this->analyze = new Analyze_Controller();
+ $this->params = new Params();
+ }
+
+
+ public function handle_request() {
+ if ( $this->params->get_param( 'status' ) !== false ) {
+ return $this->status->process();
+ } elseif ( $this->params->get_param( 'analyze' ) !== false ) {
+ return $this->analyze->process();
+ } else {
+ throw new \Exception( 'Unknown route' );
+ }
+ }
+}
diff --git a/projects/packages/analyzer/src/api/class-status-controller.php b/projects/packages/analyzer/src/api/class-status-controller.php
new file mode 100644
index 0000000000000..9de147e8ec4f5
--- /dev/null
+++ b/projects/packages/analyzer/src/api/class-status-controller.php
@@ -0,0 +1,15 @@
+model->get_status();
+ }
+
+ public function post() {
+ return 'Status_Controller post';
+ }
+
+}
diff --git a/projects/packages/analyzer/src/class-PersistentList.php b/projects/packages/analyzer/src/class-PersistentList.php
index 54d5e2efc7f90..f818120eb3b3d 100644
--- a/projects/packages/analyzer/src/class-PersistentList.php
+++ b/projects/packages/analyzer/src/class-PersistentList.php
@@ -41,7 +41,7 @@ public function save( $file_path, $allow_empty = true ) {
return '';
}
- if ( ! file_exists( dirname( $file_path ) ) ) {
+ if ( 'php://memory' !== $file_path && ! file_exists( dirname( $file_path ) ) ) {
mkdir( dirname( $file_path ), 0777, true );
}
diff --git a/projects/packages/analyzer/src/class-declarations.php b/projects/packages/analyzer/src/class-declarations.php
index 79e4bf42c1abd..dad7b694578fb 100644
--- a/projects/packages/analyzer/src/class-declarations.php
+++ b/projects/packages/analyzer/src/class-declarations.php
@@ -126,7 +126,7 @@ public function load( $file_path ) {
break;
default:
// TODO: Implement handlers to other difference types.
- echo $obj->decl_type . " not implemented!\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ echo "Declaration not implemented: " . $obj->decl_type . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
break;
}
}
diff --git a/projects/packages/analyzer/src/class-differences.php b/projects/packages/analyzer/src/class-differences.php
index 5ec7e970803d2..304b2ec9ccc59 100644
--- a/projects/packages/analyzer/src/class-differences.php
+++ b/projects/packages/analyzer/src/class-differences.php
@@ -155,25 +155,33 @@ public function load( $file_path ) {
case 'function_missing':
$this->add( new Differences\Function_Missing( Declarations\Function_::from_map( $obj->old_declaration ) ) );
break;
+ case 'function_moved':
+ $this->add(new Differences\Function_Moved( Declarations\Function_::from_map( $obj->old_declaration ), Declarations\Function_::from_map( $obj->new_declaration ) ) );
+ break;
case 'method_missing':
$this->add( new Differences\Class_Method_Missing( Declarations\Class_Method::from_map( $obj->old_declaration ) ) );
break;
-
+ case 'method_moved':
+ $this->add(new Differences\Class_Method_Moved( Declarations\Class_Method::from_map( $obj->old_declaration ), Declarations\Class_Method::from_map( $obj->new_declaration ) ) );
+ break;
case 'class_missing':
$this->add( new Differences\Class_Missing( Declarations\Class_::from_map( $obj->old_declaration ) ) );
break;
-
+ case 'class_moved':
+ $this->add(new Differences\Class_Moved( Declarations\Class_::from_map( $obj->old_declaration ), Declarations\Class_::from_map( $obj->new_declaration ) ) );
+ break;
case 'class_const_missing':
$this->add( new Differences\Class_Const_Missing( Declarations\Class_Const::from_map( $obj->old_declaration ) ) );
break;
-
case 'property_missing':
$this->add( new Differences\Class_Property_Missing( Declarations\Class_Property::from_map( $obj->old_declaration ) ) );
break;
-
+ case 'property_moved':
+ $this->add(new Differences\Class_Property_Moved( Declarations\Class_Property::from_map( $obj->old_declaration ), Declarations\Class_Property::from_map( $obj->new_declaration ) ) );
+ break;
default:
// TODO: Implement handlers to other difference types.
- echo $obj->diff_type . " not implemented!\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ echo "Difference not implemented: " . $obj->diff_type . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
break;
}
}
diff --git a/projects/packages/analyzer/src/diff-generator.php b/projects/packages/analyzer/src/diff-generator.php
new file mode 100644
index 0000000000000..6c0c653b6674f
--- /dev/null
+++ b/projects/packages/analyzer/src/diff-generator.php
@@ -0,0 +1,124 @@
+load( $new_out_path );
+ } else {
+ $jetpack_new_declarations->scan( $new_path, $excludes );
+ $jetpack_new_declarations->save_json( $new_out_path, false );
+ }
+
+ echo "Scanning old declarations\n";
+ if ( file_exists( $old_out_path ) ) {
+ $jetpack_old_declarations->load( $old_out_path );
+ } else {
+ $jetpack_old_declarations->scan( $old_path, $excludes );
+ $jetpack_old_declarations->save_json( $old_out_path, false );
+ }
+
+ echo "Looking for differences\n";
+ if ( file_exists( $diff_path ) ) {
+ $differences->load( $diff_path );
+ } else {
+ $differences->find( $jetpack_new_declarations, $jetpack_old_declarations, $new_path );
+ $differences->save_json( $diff_path, false );
+ }
+
+ return $differences;
+ }
+
+ static function load_differences() {
+ $diff_path = dirname( __DIR__ ) . '/output/differences/diff.json';
+ $differences = new Differences();
+
+ if ( !file_exists( $diff_path ) ) {
+ throw new \Exception('Failed to load differences. File does not exist: ' . $diff_path);
+ }
+ $differences->load( $diff_path );
+ return $differences;
+ }
+
+ static function get_warnings( $folder_name, $differences, $excludes ) {
+ $warnings_folder = dirname( __DIR__ ) . '/output/warnings/';
+ $invocations_folder = dirname( __DIR__ ) . '/output/invocations/';
+
+ echo "Looking for invocations in:\n${folder_name}\n\n";
+ $invocations = new Invocations();
+ $invocations->scan( $folder_name, $excludes );
+ $invocations->save( $invocations_folder . basename( $folder_name ) . '.json', false );
+
+ echo "Generate warnings\n";
+ $warnings = new Warnings();
+ $warnings->generate( $invocations, $differences );
+ $warnings->output();
+ $warnings->save_json( $warnings_folder . basename( $folder_name ) . '.json', false );
+ }
+
+ static function cleanup() {
+ $diff_folder = dirname( __DIR__ ) . '/output/differences/';
+ $warnings_folder = dirname( __DIR__ ) . '/output/warnings/';
+ $invocations_folder = dirname( __DIR__ ) . '/output/invocations/';
+ echo "Removing " . $diff_folder . "\n";
+ self::rm($diff_folder);
+
+ echo "Removing " . $invocations_folder . "\n";
+ self::rm($invocations_folder);
+
+ echo "Removing " . $warnings_folder . "\n";
+ self::rm($warnings_folder);
+ }
+
+ static function rm($folder) {
+ $iter = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator( escapeshellarg( $folder ), \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS ),
+ \RecursiveIteratorIterator::CHILD_FIRST
+ );
+ foreach ( $iter as $path ) {
+ if ( is_dir( $path ) ) {
+ rmdir( $path );
+ } else {
+ unlink( $path );
+ }
+ }
+ }
+}
+
+class Locker {
+ public static $__lock_file = 'jp-analyzer.pid';
+
+ static function lock() {
+ $fp = @fopen( self::lock_file(), 'x' );
+ if ( ! $fp ) {
+ throw new \Exception( 'Locked already' );
+ }
+ fwrite( $fp, (string) getmypid() );
+ fclose( $fp );
+ }
+ static function unlock() {
+ if ( self::is_locked() ) {
+ unlink( self::lock_file() );
+ } else {
+ throw new \Exception( 'Not locked' );
+ }
+ }
+ static function is_locked() {
+ return file_exists( self::lock_file() );
+ }
+
+ static function lock_file() {
+ return '/tmp/' . self::$__lock_file;
+ }
+
+}
diff --git a/projects/packages/assets/.gitattributes b/projects/packages/assets/.gitattributes
index c4febe626d8c4..84cb90e530515 100644
--- a/projects/packages/assets/.gitattributes
+++ b/projects/packages/assets/.gitattributes
@@ -1,10 +1,15 @@
# Files not needed to be distributed in the package.
-.gitattributes export-ignore
-.github/ export-ignore
-phpunit.xml.dist export-ignore
-tests/ export-ignore
+.gitattributes export-ignore
+.github/ export-ignore
+phpunit.xml.dist export-ignore
+tests/ export-ignore
+webpack.config.js export-ignore
# Files not needed in the production build.
-.phpcs.dir.xml production-exclude
-.phpcsignore production-exclude
-/changelog/** production-exclude
+.gitignore production-exclude
+.phpcs.dir.xml production-exclude
+.phpcsignore production-exclude
+/changelog/** production-exclude
+
+# Files to include in the production build.
+/build/** production-include
diff --git a/projects/packages/assets/.gitignore b/projects/packages/assets/.gitignore
new file mode 100644
index 0000000000000..b0c36702cd4c6
--- /dev/null
+++ b/projects/packages/assets/.gitignore
@@ -0,0 +1,3 @@
+.cache/
+build/
+node_modules/
diff --git a/projects/packages/assets/CHANGELOG.md b/projects/packages/assets/CHANGELOG.md
index 833591466aefd..034ec94857943 100644
--- a/projects/packages/assets/CHANGELOG.md
+++ b/projects/packages/assets/CHANGELOG.md
@@ -5,6 +5,26 @@ 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).
+## [1.17.3] - 2022-02-02
+### Fixed
+- Fixed minor coding standard violation.
+
+## [1.17.2] - 2022-02-01
+### Changed
+- Build: remove unneeded files from production build.
+
+## [1.17.1] - 2022-01-27
+### Changed
+- Updated package dependencies.
+
+## [1.17.0] - 2022-01-25
+### Added
+- Accept package path prefixes from jetpack-composer-plugin and use them when lazy-loading JS translations.
+- Generate the `wp-jp-i18n-loader` module needed by the new i18n-loader-webpack-plugin.
+
+### Deprecated
+- Deprecated the `wp-jp-i18n-state` module.
+
## [1.16.2] - 2022-01-18
### Fixed
- Handle the case where `WP_LANG_DIR` is in `WP_CONTENT_DIR`, but `WP_CONTENT_DIR` is not in `ABSPATH`.
@@ -170,6 +190,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Statically access asset tools
+[1.17.3]: https://github.com/Automattic/jetpack-assets/compare/v1.17.2...v1.17.3
+[1.17.2]: https://github.com/Automattic/jetpack-assets/compare/v1.17.1...v1.17.2
+[1.17.1]: https://github.com/Automattic/jetpack-assets/compare/v1.17.0...v1.17.1
+[1.17.0]: https://github.com/Automattic/jetpack-assets/compare/v1.16.2...v1.17.0
[1.16.2]: https://github.com/Automattic/jetpack-assets/compare/v1.16.1...v1.16.2
[1.16.1]: https://github.com/Automattic/jetpack-assets/compare/v1.16.0...v1.16.1
[1.16.0]: https://github.com/Automattic/jetpack-assets/compare/v1.15.0...v1.16.0
diff --git a/projects/packages/assets/README.md b/projects/packages/assets/README.md
index 68b283657e02b..ab84f8dfb941c 100644
--- a/projects/packages/assets/README.md
+++ b/projects/packages/assets/README.md
@@ -16,7 +16,7 @@ Includes manipulation of paths, enqueuing async scripts, and DNS resource hintin
* `::alias_textdomains_from_file( $file )` -- Use data recorded by [automattic/jetpack-composer-plugin](https://packagist.org/packages/automattic/jetpack-composer-plugin) to enable use of translations for shared Composer libraries.
* `::alias_textdomains( $from, $to, $totype, $ver )` -- Manually add a textdomain alias, if for some reason `::alias_textdomains_from_file()` is insufficient.
-The Assets package also provides a `wp-jp-i18n-state` script to support Webpack lazy-loaded bundles using [@automattic/i18n-loader-webpack-plugin](https://www.npmjs.com/package/@automattic/i18n-loader-webpack-plugin). No initialization is required, other than calling `::alias_textdomains_from_file()` or `::alias_textdomains()` if said bundles are coming from shared Composer libraries.
+The Assets package also provides a `wp-jp-i18n-loader` script to support Webpack lazy-loaded bundles using [@automattic/i18n-loader-webpack-plugin](https://www.npmjs.com/package/@automattic/i18n-loader-webpack-plugin). No initialization is required, other than calling `::alias_textdomains_from_file()` or `::alias_textdomains()` if said bundles are coming from shared Composer libraries.
## Testing
diff --git a/projects/js-packages/storybook/changelog/2021-11-16-15-55-31-535583 b/projects/packages/assets/changelog/renovate-wordpress-monorepo
similarity index 100%
rename from projects/js-packages/storybook/changelog/2021-11-16-15-55-31-535583
rename to projects/packages/assets/changelog/renovate-wordpress-monorepo
diff --git a/projects/packages/assets/changelog/update-project-scripts-no-install b/projects/packages/assets/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/assets/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/assets/composer.json b/projects/packages/assets/composer.json
index ccf8c81006263..3265ec5826105 100644
--- a/projects/packages/assets/composer.json
+++ b/projects/packages/assets/composer.json
@@ -21,11 +21,21 @@
]
},
"scripts": {
+ "build-development": [
+ "pnpm run build"
+ ],
+ "build-production": [
+ "pnpm run build-production"
+ ],
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
],
"test-coverage": [
- "php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
+ "php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/php/clover.xml\"",
+ "pnpm run test-coverage"
+ ],
+ "test-js": [
+ "pnpm run test"
],
"test-php": [
"@composer phpunit"
@@ -50,7 +60,7 @@
"link-template": "https://github.com/Automattic/jetpack-assets/compare/v${old}...v${new}"
},
"branch-alias": {
- "dev-master": "1.16.x-dev"
+ "dev-master": "1.17.x-dev"
}
}
}
diff --git a/projects/packages/assets/package.json b/projects/packages/assets/package.json
new file mode 100644
index 0000000000000..995f19dd161fe
--- /dev/null
+++ b/projects/packages/assets/package.json
@@ -0,0 +1,25 @@
+{
+ "private": true,
+ "browserslist": "extends @wordpress/browserslist-config",
+ "scripts": {
+ "build": "pnpm run clean && pnpm run build-js",
+ "build-js": "webpack",
+ "build-production": "pnpm run clean && pnpm run build-production-js && pnpm run validate",
+ "build-production-js": "NODE_ENV=production BABEL_ENV=production pnpm run build-js",
+ "clean": "rm -rf build",
+ "test": "jest tests",
+ "test-coverage": "jest tests --coverage --collectCoverageFrom='src/**/*.js' --coverageDirectory=\"$COVERAGE_DIR/js\" --coverageReporters=clover",
+ "validate": "pnpm exec validate-es build/"
+ },
+ "devDependencies": {
+ "@automattic/jetpack-webpack-config": "workspace:^1.1.2-alpha",
+ "md5-es": "1.8.2",
+ "jest": "27.3.1",
+ "webpack": "5.65.0"
+ },
+ "engines": {
+ "node": "^14.18.3 || ^16.13.2",
+ "pnpm": "^6.23.6",
+ "yarn": "use pnpm instead - see docs/yarn-upgrade.md"
+ }
+}
diff --git a/projects/packages/assets/src/class-assets.php b/projects/packages/assets/src/class-assets.php
index 92883f2c12852..b2771eac784bf 100644
--- a/projects/packages/assets/src/class-assets.php
+++ b/projects/packages/assets/src/class-assets.php
@@ -32,7 +32,7 @@ class Assets {
/**
* The registered textdomain mappings.
*
- * @var array `array( mapped_domain => array( string target_domain, string target_type, string semver ) )`.
+ * @var array `array( mapped_domain => array( string target_domain, string target_type, string semver, string path_prefix ) )`.
*/
private static $domain_map = array();
@@ -358,7 +358,7 @@ public static function register_script( $handle, $path, $relative_to, array $opt
}
if ( $options['asset_path'] && file_exists( "$dir/{$options['asset_path']}" ) ) {
- $asset = require "$dir/{$options['asset_path']}";
+ $asset = require "$dir/{$options['asset_path']}"; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.NotAbsolutePath
$options['dependencies'] = array_merge( $asset['dependencies'], $options['dependencies'] );
$options['css_dependencies'] = array_merge(
array_filter(
@@ -430,7 +430,7 @@ public static function enqueue_script( $handle ) {
/**
* 'wp_default_scripts' action handler.
*
- * This registers the `wp-jp-i18n-state` script for use by Webpack bundles built with
+ * This registers the `wp-jp-i18n-loader` script for use by Webpack bundles built with
* `@automattic/i18n-loader-webpack-plugin`.
*
* @since 1.14.0
@@ -438,9 +438,10 @@ public static function enqueue_script( $handle ) {
*/
public static function wp_default_scripts_hook( $wp_scripts ) {
$data = array(
- 'baseUrl' => false,
- 'locale' => determine_locale(),
- 'domainMap' => array(),
+ 'baseUrl' => false,
+ 'locale' => determine_locale(),
+ 'domainMap' => array(),
+ 'domainPaths' => array(),
);
$lang_dir = Jetpack_Constants::get_constant( 'WP_LANG_DIR' );
@@ -453,8 +454,11 @@ public static function wp_default_scripts_hook( $wp_scripts ) {
$data['baseUrl'] = site_url( substr( trailingslashit( $lang_dir ), strlen( untrailingslashit( $abspath ) ) ) );
}
- foreach ( self::$domain_map as $from => list( $to, $type ) ) {
+ foreach ( self::$domain_map as $from => list( $to, $type, , $path ) ) {
$data['domainMap'][ $from ] = ( 'core' === $type ? '' : "{$type}/" ) . $to;
+ if ( '' !== $path ) {
+ $data['domainPaths'][ $from ] = trailingslashit( $path );
+ }
}
/**
@@ -468,25 +472,44 @@ public static function wp_default_scripts_hook( $wp_scripts ) {
* - `locale`: (string) The locale for the page.
* - `domainMap`: (string[]) A mapping from Composer package textdomains to the corresponding
* `plugins/textdomain` or `themes/textdomain` (or core `textdomain`, but that's unlikely).
+ * - `domainPaths`: (string[]) A mapping from Composer package textdomains to the corresponding package
+ * paths.
*/
$data = apply_filters( 'jetpack_i18n_state', $data );
+ // Can't use self::register_script(), this action is called too early.
+ if ( file_exists( __DIR__ . '/../build/i18n-loader.asset.php' ) ) {
+ $path = '../build/i18n-loader.js';
+ $asset = require __DIR__ . '/../build/i18n-loader.asset.php';
+ } else {
+ $path = 'js/i18n-loader.js';
+ $asset = array(
+ 'dependencies' => array( 'wp-i18n' ),
+ 'version' => filemtime( __DIR__ . "/$path" ),
+ );
+ }
+ $url = self::normalize_path( plugins_url( $path, __FILE__ ) );
+ $url = add_query_arg( 'minify', 'true', $url );
+ $wp_scripts->add( 'wp-jp-i18n-loader', $url, $asset['dependencies'], $asset['version'] );
if ( ! is_array( $data ) ||
! isset( $data['baseUrl'] ) || ! ( is_string( $data['baseUrl'] ) || false === $data['baseUrl'] ) ||
! isset( $data['locale'] ) || ! is_string( $data['locale'] ) ||
- ! isset( $data['domainMap'] ) || ! is_array( $data['domainMap'] )
+ ! isset( $data['domainMap'] ) || ! is_array( $data['domainMap'] ) ||
+ ! isset( $data['domainPaths'] ) || ! is_array( $data['domainPaths'] )
) {
- $js = 'console.warn( "I18n state deleted by jetpack_i18n_state hook" );';
+ $wp_scripts->add_inline_script( 'wp-jp-i18n-loader', 'console.warn( "I18n state deleted by jetpack_i18n_state hook" );' );
} elseif ( ! $data['baseUrl'] ) {
- $js = 'console.warn( "Failed to determine languages base URL. Is WP_LANG_DIR in the WordPress root?" );';
+ $wp_scripts->add_inline_script( 'wp-jp-i18n-loader', 'console.warn( "Failed to determine languages base URL. Is WP_LANG_DIR in the WordPress root?" );' );
} else {
- $data['domainMap'] = (object) $data['domainMap']; // Ensure it becomes a json object.
- $js = 'wp.jpI18nState = ' . wp_json_encode( $data, JSON_UNESCAPED_SLASHES ) . ';';
+ $data['domainMap'] = (object) $data['domainMap']; // Ensure it becomes a json object.
+ $data['domainPaths'] = (object) $data['domainPaths']; // Ensure it becomes a json object.
+ $wp_scripts->add_inline_script( 'wp-jp-i18n-loader', 'wp.jpI18nLoader.state = ' . wp_json_encode( $data, JSON_UNESCAPED_SLASHES ) . ';' );
}
- // Depend on wp-i18n to ensure global `wp` exists and because anything needing this will need that too.
- $wp_scripts->add( 'wp-jp-i18n-state', null, array( 'wp-i18n' ) );
- $wp_scripts->add_inline_script( 'wp-jp-i18n-state', $js, 'before' );
+ // Deprecated state module: Depend on wp-i18n to ensure global `wp` exists and because anything needing this will need that too.
+ $wp_scripts->add( 'wp-jp-i18n-state', null, array( 'wp-deprecated', 'wp-jp-i18n-loader' ) );
+ $wp_scripts->add_inline_script( 'wp-jp-i18n-state', 'wp.deprecated( "wp-jp-i18n-state", { alternative: "wp-jp-i18n-loader" } );' );
+ $wp_scripts->add_inline_script( 'wp-jp-i18n-state', 'wp.jpI18nState = wp.jpI18nLoader.state;' );
}
// endregion .
@@ -510,9 +533,10 @@ public static function wp_default_scripts_hook( $wp_scripts ) {
* @param string $to Domain to alias it to.
* @param string $totype What is the target of the alias: 'plugins', 'themes', or 'core'.
* @param string $ver Version of the `$from` domain.
+ * @param string $path Path to prepend when lazy-loading from JavaScript.
* @throws InvalidArgumentException If arguments are invalid.
*/
- public static function alias_textdomain( $from, $to, $totype, $ver ) {
+ public static function alias_textdomain( $from, $to, $totype, $ver, $path = '' ) {
if ( ! in_array( $totype, array( 'plugins', 'themes', 'core' ), true ) ) {
throw new InvalidArgumentException( 'Type must be "plugins", "themes", or "core"' );
}
@@ -536,9 +560,9 @@ public static function alias_textdomain( $from, $to, $totype, $ver ) {
if ( empty( self::$domain_map[ $from ] ) ) {
self::init_domain_map_hooks( $from, array() === self::$domain_map );
- self::$domain_map[ $from ] = array( $to, $totype, $ver );
+ self::$domain_map[ $from ] = array( $to, $totype, $ver, $path );
} elseif ( Semver::compare( $ver, self::$domain_map[ $from ][2] ) > 0 ) {
- self::$domain_map[ $from ] = array( $to, $totype, $ver );
+ self::$domain_map[ $from ] = array( $to, $totype, $ver, $path );
}
}
@@ -549,15 +573,21 @@ public static function alias_textdomain( $from, $to, $totype, $ver ) {
* with the following properties:
* - 'domain': String, `$to`
* - 'type': String, `$totype`
- * - 'packages': Array, mapping `$from` to `$ver`.
+ * - 'packages': Array, mapping `$from` to `array( 'path' => $path, 'ver' => $ver )` (or to the string `$ver` for back compat).
*
* @since 1.15.0
* @param string $file Mapping file.
*/
public static function alias_textdomains_from_file( $file ) {
$data = require $file;
- foreach ( $data['packages'] as $from => $ver ) {
- self::alias_textdomain( $from, $data['domain'], $data['type'], $ver );
+ foreach ( $data['packages'] as $from => $fromdata ) {
+ if ( ! is_array( $fromdata ) ) {
+ $fromdata = array(
+ 'path' => '',
+ 'ver' => $fromdata,
+ );
+ }
+ self::alias_textdomain( $from, $data['domain'], $data['type'], $fromdata['ver'], $fromdata['path'] );
}
}
diff --git a/projects/packages/assets/src/js/i18n-loader.js b/projects/packages/assets/src/js/i18n-loader.js
new file mode 100644
index 0000000000000..6887aa8539f59
--- /dev/null
+++ b/projects/packages/assets/src/js/i18n-loader.js
@@ -0,0 +1,76 @@
+const { default: md5 } = require( 'md5-es' );
+const i18n = require( '@wordpress/i18n' );
+
+const locationMap = {
+ plugin: 'plugins/',
+ theme: 'themes/',
+ core: '',
+};
+
+const hasOwn = ( obj, prop ) => Object.prototype.hasOwnProperty.call( obj, prop );
+
+module.exports = {
+ state: {
+ baseUrl: null,
+ locale: null,
+ domainMap: {},
+ domainPaths: {},
+ },
+
+ /**
+ * Download and register translations for a bundle.
+ *
+ * @param {string} path - Bundle path being fetched. May have a query part.
+ * @param {string} domain - Text domain to register into.
+ * @param {string} location - Location for the translation: 'plugin', 'theme', or 'core'.
+ * @returns {Promise} Resolved when the translations are registered, or rejected with an `Error`.
+ */
+ async downloadI18n( path, domain, location ) {
+ const state = this.state;
+ if ( ! state || typeof state.baseUrl !== 'string' ) {
+ throw new Error( 'wp.jpI18nLoader.state is not set' );
+ }
+
+ // "en_US" is the default, no translations are needed.
+ if ( state.locale === 'en_US' ) {
+ return;
+ }
+
+ // Check that fetch is available.
+ if ( typeof fetch === 'undefined' ) {
+ throw new Error( 'Fetch API is not available.' );
+ }
+
+ // Extract any query part and hash the script name like WordPress does.
+ const pathPrefix = hasOwn( state.domainPaths, domain ) ? state.domainPaths[ domain ] : '';
+ let hash, query;
+ const i = path.indexOf( '?' );
+ if ( i >= 0 ) {
+ hash = md5.hash( pathPrefix + path.substring( 0, i ) );
+ query = path.substring( i );
+ } else {
+ hash = md5.hash( pathPrefix + path );
+ query = '';
+ }
+
+ // Download.
+ const locationAndDomain = hasOwn( state.domainMap, domain )
+ ? state.domainMap[ domain ]
+ : locationMap[ location ] + domain;
+ const res = await fetch(
+ // prettier-ignore
+ `${ state.baseUrl }${ locationAndDomain }-${ state.locale }-${ hash }.json${ query }`
+ );
+ if ( ! res.ok ) {
+ throw new Error( `HTTP request failed: ${ res.status } ${ res.statusText }` );
+ }
+ const data = await res.json();
+
+ // Extract the messages from the file and register them.
+ const localeData = hasOwn( data.locale_data, domain )
+ ? data.locale_data[ domain ]
+ : data.locale_data.messages;
+ localeData[ '' ].domain = domain;
+ i18n.setLocaleData( localeData, domain );
+ },
+};
diff --git a/projects/packages/assets/tests/js/i18n-loader.test.js b/projects/packages/assets/tests/js/i18n-loader.test.js
new file mode 100644
index 0000000000000..903e65168e7e4
--- /dev/null
+++ b/projects/packages/assets/tests/js/i18n-loader.test.js
@@ -0,0 +1,215 @@
+global.fetch = jest.fn();
+fetch.mockFetchResponse = function ( body, init = {} ) {
+ const status = parseInt( init.status || 200 );
+ const statusText = init.statusText || 'OK';
+
+ let rbody = body;
+ if ( typeof rbody !== 'string' ) {
+ rbody = JSON.stringify( rbody );
+ }
+ if ( status < 200 || status > 599 ) {
+ throw new Error( `Invalid status: ${ init.status }` );
+ }
+
+ this.mockResolvedValueOnce( {
+ body: rbody,
+ ok: status < 300,
+ status: status,
+ statusText: statusText,
+ json: () => JSON.parse( rbody ),
+ } );
+};
+
+const mockSetLocaleData = jest.fn();
+jest.doMock( '@wordpress/i18n', () => ( {
+ setLocaleData: mockSetLocaleData,
+} ) );
+
+const translations = JSON.stringify( {
+ domain: 'messages',
+ locale_data: {
+ messages: {
+ '': {
+ domain: 'messages',
+ lang: 'en',
+ 'plural-forms': 'nplurals=2; plural=(n != 1);',
+ },
+ 'This is translated': [ 'is-Thay is-way anslated-tray' ],
+ },
+ },
+} );
+
+const loader = require( '../../src/js/i18n-loader.js' );
+
+beforeEach( () => {
+ global.fetch.mockReset();
+ mockSetLocaleData.mockReset();
+
+ loader.state = {
+ baseUrl: 'http://example.com/wp-content/languages/',
+ locale: 'en_piglatin',
+ domainMap: {},
+ domainPaths: {},
+ };
+} );
+
+test( 'No state', async () => {
+ loader.state = null;
+ await expect( loader.downloadI18n( 'foo', 'bar', 'baz' ) ).rejects.toThrow(
+ 'wp.jpI18nLoader.state is not set'
+ );
+ expect( global.fetch ).not.toHaveBeenCalled();
+ expect( mockSetLocaleData ).not.toHaveBeenCalled();
+} );
+
+test( 'No baseUrl in state', async () => {
+ loader.state.baseUrl = null;
+ await expect( loader.downloadI18n( 'foo', 'bar', 'baz' ) ).rejects.toThrow(
+ 'wp.jpI18nLoader.state is not set'
+ );
+ expect( global.fetch ).not.toHaveBeenCalled();
+ expect( mockSetLocaleData ).not.toHaveBeenCalled();
+} );
+
+test( 'Locale is en_US', async () => {
+ loader.state.locale = 'en_US';
+ await expect( loader.downloadI18n( 'foo', 'bar', 'baz' ) ).resolves.not.toThrow();
+ expect( global.fetch ).not.toHaveBeenCalled();
+ expect( mockSetLocaleData ).not.toHaveBeenCalled();
+} );
+
+test( 'No fetch', async () => {
+ const tmp = global.fetch;
+ try {
+ delete global.fetch;
+ await expect( loader.downloadI18n( 'foo', 'bar', 'baz' ) ).rejects.toThrow(
+ 'Fetch API is not available.'
+ );
+ } finally {
+ global.fetch = tmp;
+ }
+ expect( global.fetch ).not.toHaveBeenCalled();
+ expect( mockSetLocaleData ).not.toHaveBeenCalled();
+} );
+
+test.each( [
+ [ 'plugin', 'plugins/' ],
+ [ 'theme', 'themes/' ],
+ [ 'core', '' ],
+] )( 'Simple fetch (location=%s)', async ( location, urlpart ) => {
+ fetch.mockFetchResponse( translations );
+ await expect( loader.downloadI18n( 'foo.js', 'bar', location ) ).resolves.not.toThrow();
+ expect( global.fetch ).toHaveBeenCalledTimes( 1 );
+ expect( global.fetch ).toHaveBeenCalledWith(
+ `http://example.com/wp-content/languages/${ urlpart }bar-en_piglatin-c30102c2bcf57c992ba3491b22e1e4d0.json`
+ );
+ expect( mockSetLocaleData ).toHaveBeenCalledTimes( 1 );
+ expect( mockSetLocaleData ).toHaveBeenCalledWith(
+ {
+ '': {
+ domain: 'bar',
+ lang: 'en',
+ 'plural-forms': 'nplurals=2; plural=(n != 1);',
+ },
+ 'This is translated': [ 'is-Thay is-way anslated-tray' ],
+ },
+ 'bar'
+ );
+} );
+
+test( 'Failed fetch', async () => {
+ fetch.mockFetchResponse( 'The specified document was not found.', {
+ status: 404,
+ statusText: 'Not found',
+ } );
+ await expect( loader.downloadI18n( 'foo.js', 'bar', 'plugin' ) ).rejects.toThrow(
+ 'HTTP request failed: 404 Not found'
+ );
+ expect( global.fetch ).toHaveBeenCalledTimes( 1 );
+ expect( global.fetch ).toHaveBeenCalledWith(
+ 'http://example.com/wp-content/languages/plugins/bar-en_piglatin-c30102c2bcf57c992ba3491b22e1e4d0.json'
+ );
+ expect( mockSetLocaleData ).not.toHaveBeenCalled();
+} );
+
+test( 'Bad JSON', async () => {
+ fetch.mockFetchResponse( 'Whatever' );
+ await expect( loader.downloadI18n( 'foo.js', 'bar', 'plugin' ) ).rejects.toThrow(
+ 'Unexpected token < in JSON at position 0'
+ );
+ expect( global.fetch ).toHaveBeenCalledTimes( 1 );
+ expect( global.fetch ).toHaveBeenCalledWith(
+ 'http://example.com/wp-content/languages/plugins/bar-en_piglatin-c30102c2bcf57c992ba3491b22e1e4d0.json'
+ );
+ expect( mockSetLocaleData ).not.toHaveBeenCalled();
+} );
+
+test( 'Fetch with query part and domain map', async () => {
+ loader.state.domainMap.bar = 'themes/mytheme';
+ fetch.mockFetchResponse( translations );
+ await expect(
+ loader.downloadI18n( 'dist/foo.js?ver=12345?6789', 'bar', 'plugin' )
+ ).resolves.not.toThrow();
+ expect( global.fetch ).toHaveBeenCalledTimes( 1 );
+ expect( global.fetch ).toHaveBeenCalledWith(
+ 'http://example.com/wp-content/languages/themes/mytheme-en_piglatin-1648792a465228e857cd166c292e2e4a.json?ver=12345?6789'
+ );
+ expect( mockSetLocaleData ).toHaveBeenCalledTimes( 1 );
+ expect( mockSetLocaleData ).toHaveBeenCalledWith(
+ {
+ '': {
+ domain: 'bar',
+ lang: 'en',
+ 'plural-forms': 'nplurals=2; plural=(n != 1);',
+ },
+ 'This is translated': [ 'is-Thay is-way anslated-tray' ],
+ },
+ 'bar'
+ );
+} );
+
+test( 'Fetch with query part and domain map and path', async () => {
+ loader.state.domainMap.bar = 'themes/mytheme';
+ loader.state.domainPaths.bar = 'path/to/bar/';
+ fetch.mockFetchResponse( translations );
+ await expect(
+ loader.downloadI18n( 'dist/foo.js?ver=12345?6789', 'bar', 'plugin' )
+ ).resolves.not.toThrow();
+ expect( global.fetch ).toHaveBeenCalledTimes( 1 );
+ expect( global.fetch ).toHaveBeenCalledWith(
+ 'http://example.com/wp-content/languages/themes/mytheme-en_piglatin-fe1568906ebfe6b7e22d422cba956f83.json?ver=12345?6789'
+ );
+ expect( mockSetLocaleData ).toHaveBeenCalledTimes( 1 );
+ expect( mockSetLocaleData ).toHaveBeenCalledWith(
+ {
+ '': {
+ domain: 'bar',
+ lang: 'en',
+ 'plural-forms': 'nplurals=2; plural=(n != 1);',
+ },
+ 'This is translated': [ 'is-Thay is-way anslated-tray' ],
+ },
+ 'bar'
+ );
+} );
+
+test( 'Fetch with silly domain', async () => {
+ fetch.mockFetchResponse( translations );
+ await expect( loader.downloadI18n( 'foo.js', 'toString', 'plugin' ) ).resolves.not.toThrow();
+ expect( global.fetch ).toHaveBeenCalledTimes( 1 );
+ expect( global.fetch ).toHaveBeenCalledWith(
+ 'http://example.com/wp-content/languages/plugins/toString-en_piglatin-c30102c2bcf57c992ba3491b22e1e4d0.json'
+ );
+ expect( mockSetLocaleData ).toHaveBeenCalledTimes( 1 );
+ expect( mockSetLocaleData ).toHaveBeenCalledWith(
+ {
+ '': {
+ domain: 'toString',
+ lang: 'en',
+ 'plural-forms': 'nplurals=2; plural=(n != 1);',
+ },
+ 'This is translated': [ 'is-Thay is-way anslated-tray' ],
+ },
+ 'toString'
+ );
+} );
diff --git a/projects/packages/assets/tests/php/test-assets-files/i18n-map.php b/projects/packages/assets/tests/php/test-assets-files/i18n-map.php
index 522298c29c572..b9ee3fef2fbfa 100644
--- a/projects/packages/assets/tests/php/test-assets-files/i18n-map.php
+++ b/projects/packages/assets/tests/php/test-assets-files/i18n-map.php
@@ -3,7 +3,10 @@
'domain' => 'target',
'type' => 'plugins',
'packages' => array(
- 'foo' => '1.2.3',
+ 'foo' => array(
+ 'path' => 'path/to/foo',
+ 'ver' => '1.2.3',
+ ),
'bar' => '4.5.6',
),
);
diff --git a/projects/packages/assets/tests/php/test-assets.php b/projects/packages/assets/tests/php/test-assets.php
index 1195f69a40cef..a649f5336079a 100644
--- a/projects/packages/assets/tests/php/test-assets.php
+++ b/projects/packages/assets/tests/php/test-assets.php
@@ -672,10 +672,24 @@ function ( $v ) {
$mock = $this->getMockBuilder( stdClass::class )
->setMethods( array( 'add', 'add_inline_script' ) )
->getMock();
- $mock->expects( $this->once() )->method( 'add' )
- ->with( 'wp-jp-i18n-state', null, array( 'wp-i18n' ) );
- $mock->expects( $this->once() )->method( 'add_inline_script' )
- ->with( 'wp-jp-i18n-state', $expect_js, 'before' );
+ $mock->expects( $this->exactly( 2 ) )->method( 'add' )
+ ->withConsecutive(
+ array(
+ 'wp-jp-i18n-loader',
+ $this->logicalOr(
+ 'http://www.example.com/wp-content/plugins/jetpack/packages/assets/build/i18n-loader.js?minify=true',
+ 'http://www.example.com/wp-content/plugins/jetpack/packages/assets/src/js/i18n-loader.js?minify=true'
+ ),
+ array( 'wp-i18n' ),
+ ),
+ array( 'wp-jp-i18n-state', null, array( 'wp-deprecated', 'wp-jp-i18n-loader' ) )
+ );
+ $mock->expects( $this->exactly( 3 ) )->method( 'add_inline_script' )
+ ->withConsecutive(
+ array( 'wp-jp-i18n-loader', $expect_js ),
+ array( 'wp-jp-i18n-state', 'wp.deprecated( "wp-jp-i18n-state", { alternative: "wp-jp-i18n-loader" } );' ),
+ array( 'wp-jp-i18n-state', 'wp.jpI18nState = wp.jpI18nLoader.state;' )
+ );
Assets::wp_default_scripts_hook( $mock );
}
@@ -683,54 +697,58 @@ function ( $v ) {
/** Data provider for test_wp_default_scripts_hook. */
public function provide_wp_default_scripts_hook() {
$expect_filter = array(
- 'baseUrl' => 'http://example.com/wp-content/languages/',
- 'locale' => 'en_US',
- 'domainMap' => array(),
+ 'baseUrl' => 'http://example.com/wp-content/languages/',
+ 'locale' => 'en_US',
+ 'domainMap' => array(),
+ 'domainPaths' => array(),
);
return array(
- 'Basic test' => array(
+ 'Basic test' => array(
$expect_filter,
- 'wp.jpI18nState = {"baseUrl":"http://example.com/wp-content/languages/","locale":"en_US","domainMap":{}};',
+ 'wp.jpI18nLoader.state = {"baseUrl":"http://example.com/wp-content/languages/","locale":"en_US","domainMap":{},"domainPaths":{}};',
),
- 'Basic test (2)' => array(
+ 'Basic test (2)' => array(
array(
- 'baseUrl' => 'http://example.com/wp-includes/languages/',
- 'locale' => 'de_DE',
- 'domainMap' => array(
+ 'baseUrl' => 'http://example.com/wp-includes/languages/',
+ 'locale' => 'de_DE',
+ 'domainMap' => array(
'jetpack-foo' => 'plugins/jetpack',
'jetpack-bar' => 'themes/sometheme',
'core' => 'default',
),
+ 'domainPaths' => array(
+ 'jetpack-foo' => 'path/to/foo/',
+ ),
),
- 'wp.jpI18nState = {"baseUrl":"http://example.com/wp-includes/languages/","locale":"de_DE","domainMap":{"jetpack-foo":"plugins/jetpack","jetpack-bar":"themes/sometheme","core":"default"}};',
+ 'wp.jpI18nLoader.state = {"baseUrl":"http://example.com/wp-includes/languages/","locale":"de_DE","domainMap":{"jetpack-foo":"plugins/jetpack","jetpack-bar":"themes/sometheme","core":"default"},"domainPaths":{"jetpack-foo":"path/to/foo/"}};',
array(
'constants' => array( 'WP_LANG_DIR' => '/path/to/wordpress/wp-includes/languages' ),
'locale' => 'de_DE',
'domain_map' => array(
- 'jetpack-foo' => array( 'jetpack', 'plugins', '1.2.3' ),
- 'jetpack-bar' => array( 'sometheme', 'themes', '1.2.3' ),
- 'core' => array( 'default', 'core', '1.2.3' ),
+ 'jetpack-foo' => array( 'jetpack', 'plugins', '1.2.3', 'path/to/foo' ),
+ 'jetpack-bar' => array( 'sometheme', 'themes', '1.2.3', '' ),
+ 'core' => array( 'default', 'core', '1.2.3', '' ),
),
),
),
- 'Bad WP_LANG_DIR' => array(
+ 'Bad WP_LANG_DIR' => array(
array( 'baseUrl' => false ) + $expect_filter,
'console.warn( "Failed to determine languages base URL. Is WP_LANG_DIR in the WordPress root?" );',
array(
'constants' => array( 'WP_LANG_DIR' => '/not/path/to/wordpress/wp-content/languages' ),
),
),
- 'WP_LANG_DIR in wp-includes' => array(
+ 'WP_LANG_DIR in wp-includes' => array(
array( 'baseUrl' => 'http://example.com/wp-includes/languages/' ) + $expect_filter,
- 'wp.jpI18nState = {"baseUrl":"http://example.com/wp-includes/languages/","locale":"en_US","domainMap":{}};',
+ 'wp.jpI18nLoader.state = {"baseUrl":"http://example.com/wp-includes/languages/","locale":"en_US","domainMap":{},"domainPaths":{}};',
array(
'constants' => array( 'WP_LANG_DIR' => '/path/to/wordpress/wp-includes/languages' ),
),
),
- 'WP_CONTENT_DIR not in ABSPATH' => array(
+ 'WP_CONTENT_DIR not in ABSPATH' => array(
array( 'baseUrl' => 'http://example.com/wp-content/languages/' ) + $expect_filter,
- 'wp.jpI18nState = {"baseUrl":"http://example.com/wp-content/languages/","locale":"en_US","domainMap":{}};',
+ 'wp.jpI18nLoader.state = {"baseUrl":"http://example.com/wp-content/languages/","locale":"en_US","domainMap":{},"domainPaths":{}};',
array(
'constants' => array(
'ABSPATH' => '/srv/htdocs/__wp__/',
@@ -739,72 +757,101 @@ public function provide_wp_default_scripts_hook() {
),
),
),
- 'Filter' => array(
+ 'Filter' => array(
array( 'baseUrl' => false ) + $expect_filter,
- 'wp.jpI18nState = {"baseUrl":"http://example.org/languages/","locale":"klingon","domainMap":{"foo":"plugins/bar"}};',
+ 'wp.jpI18nLoader.state = {"baseUrl":"http://example.org/languages/","locale":"klingon","domainMap":{"foo":"plugins/bar"},"domainPaths":{"foo":"path/to/bar/"}};',
array(
'constants' => array( 'WP_LANG_DIR' => '/not/path/to/wordpress/wp-content/languages' ),
'filter' => array(
- 'baseUrl' => 'http://example.org/languages/',
- 'locale' => 'klingon',
- 'domainMap' => array( 'foo' => 'plugins/bar' ),
+ 'baseUrl' => 'http://example.org/languages/',
+ 'locale' => 'klingon',
+ 'domainMap' => array( 'foo' => 'plugins/bar' ),
+ 'domainPaths' => array( 'foo' => 'path/to/bar/' ),
),
),
),
- 'Bad filter: not array' => array(
+ 'Bad filter: not array' => array(
$expect_filter,
'console.warn( "I18n state deleted by jetpack_i18n_state hook" );',
array( 'filter' => null ),
),
- 'Bad filter: baseUrl is not set' => array(
+ 'Bad filter: baseUrl is not set' => array(
$expect_filter,
'console.warn( "I18n state deleted by jetpack_i18n_state hook" );',
array(
'filter' => array(
- 'locale' => 'en_US',
- 'domainMap' => array(),
+ 'locale' => 'en_US',
+ 'domainMap' => array(),
+ 'domainPaths' => array(),
),
),
),
- 'Bad filter: locale is not set' => array(
+ 'Bad filter: locale is not set' => array(
$expect_filter,
'console.warn( "I18n state deleted by jetpack_i18n_state hook" );',
array(
'filter' => array(
- 'baseUrl' => 'http://example.com/wp-content/languages/',
- 'domainMap' => array(),
+ 'baseUrl' => 'http://example.com/wp-content/languages/',
+ 'domainMap' => array(),
+ 'domainPaths' => array(),
),
),
),
- 'Bad filter: locale is bad' => array(
+ 'Bad filter: locale is bad' => array(
$expect_filter,
'console.warn( "I18n state deleted by jetpack_i18n_state hook" );',
array(
'filter' => array(
- 'baseUrl' => 'http://example.com/wp-content/languages/',
- 'locale' => false,
- 'domainMap' => array(),
+ 'baseUrl' => 'http://example.com/wp-content/languages/',
+ 'locale' => false,
+ 'domainMap' => array(),
+ 'domainPaths' => array(),
),
),
),
- 'Bad filter: domainMap is not set' => array(
+ 'Bad filter: domainMap is not set' => array(
$expect_filter,
'console.warn( "I18n state deleted by jetpack_i18n_state hook" );',
array(
'filter' => array(
- 'baseUrl' => 'http://example.com/wp-content/languages/',
- 'locale' => 'en_US',
+ 'baseUrl' => 'http://example.com/wp-content/languages/',
+ 'locale' => 'en_US',
+ 'domainPaths' => array(),
),
),
),
- 'Bad filter: domainMap is bad' => array(
+ 'Bad filter: domainMap is bad' => array(
+ $expect_filter,
+ 'console.warn( "I18n state deleted by jetpack_i18n_state hook" );',
+ array(
+ 'filter' => array(
+ 'baseUrl' => 'http://example.com/wp-content/languages/',
+ 'locale' => 'en_US',
+ 'domainMap' => (object) array(),
+ 'domainPaths' => array(),
+ ),
+ ),
+ ),
+ 'Bad filter: domainPaths is not set' => array(
$expect_filter,
'console.warn( "I18n state deleted by jetpack_i18n_state hook" );',
array(
'filter' => array(
'baseUrl' => 'http://example.com/wp-content/languages/',
'locale' => 'en_US',
- 'domainMap' => (object) array(),
+ 'domainMap' => array(),
+ ),
+ ),
+ ),
+ 'Bad filter: domainPaths is bad' => array(
+ $expect_filter,
+ 'console.warn( "I18n state deleted by jetpack_i18n_state hook" );',
+ array(
+ 'filter' => array(
+ 'baseUrl' => 'http://example.com/wp-content/languages/',
+ 'locale' => 'en_US',
+ 'domainMap' => array(),
+ 'domainPaths' => (object) array(),
),
),
),
@@ -823,15 +870,15 @@ public function test_alias_textdomain() {
Filters\expectAdded( 'ngettext_with_context_bar' )->once()->with( array( Assets::class, 'filter_ngettext_with_context' ), 10, 6 );
Filters\expectAdded( 'load_script_translation_file' )->once()->with( array( Assets::class, 'filter_load_script_translation_file' ), 10, 3 );
- Assets::alias_textdomain( 'foo', 'one', 'plugins', '1.2.3' );
- Assets::alias_textdomain( 'foo', 'two', 'plugins', '1.2.4' );
- Assets::alias_textdomain( 'bar', 'one', 'themes', '1.2.3' );
- Assets::alias_textdomain( 'bar', 'two', 'themes', '1.2.2' );
+ Assets::alias_textdomain( 'foo', 'one', 'plugins', '1.2.3', 'path/to/foo/1.2.3' );
+ Assets::alias_textdomain( 'foo', 'two', 'plugins', '1.2.4', 'path/to/foo/1.2.4' );
+ Assets::alias_textdomain( 'bar', 'one', 'themes', '1.2.3', 'path/to/bar/1.2.3' );
+ Assets::alias_textdomain( 'bar', 'two', 'themes', '1.2.2', 'path/to/bar/1.2.2' );
$this->assertEquals(
array(
- 'foo' => array( 'two', 'plugins', '1.2.4' ),
- 'bar' => array( 'one', 'themes', '1.2.3' ),
+ 'foo' => array( 'two', 'plugins', '1.2.4', 'path/to/foo/1.2.4' ),
+ 'bar' => array( 'one', 'themes', '1.2.3', 'path/to/bar/1.2.3' ),
),
TestingAccessWrapper::newFromClass( Assets::class )->domain_map
);
@@ -849,8 +896,8 @@ public function test_alias_textdomains_from_file() {
Assets::alias_textdomains_from_file( __DIR__ . '/test-assets-files/i18n-map.php' );
$this->assertEquals(
array(
- 'foo' => array( 'target', 'plugins', '1.2.3' ),
- 'bar' => array( 'target', 'plugins', '4.5.6' ),
+ 'foo' => array( 'target', 'plugins', '1.2.3', 'path/to/foo' ),
+ 'bar' => array( 'target', 'plugins', '4.5.6', '' ),
),
TestingAccessWrapper::newFromClass( Assets::class )->domain_map
);
@@ -869,7 +916,7 @@ public function test_alias_textdomain__after_wp_default_scripts() {
/** Test filter_gettext. */
public function test_filter_gettext() {
- TestingAccessWrapper::newFromClass( Assets::class )->domain_map = array( 'olddomain' => array( 'newdomain', 'plugins', '1.2.3' ) );
+ TestingAccessWrapper::newFromClass( Assets::class )->domain_map = array( 'olddomain' => array( 'newdomain', 'plugins', '1.2.3', '' ) );
Functions\expect( '__' )->once()->with( 'foo', 'newdomain' )->andReturn( 'oo-fay' );
Functions\expect( '__' )->never()->with( 'bar', 'newdomain' );
@@ -881,7 +928,7 @@ public function test_filter_gettext() {
/** Test filter_ngettext. */
public function test_filter_ngettext() {
- TestingAccessWrapper::newFromClass( Assets::class )->domain_map = array( 'olddomain' => array( 'newdomain', 'plugins', '1.2.3' ) );
+ TestingAccessWrapper::newFromClass( Assets::class )->domain_map = array( 'olddomain' => array( 'newdomain', 'plugins', '1.2.3', '' ) );
Functions\expect( '_n' )->once()->with( 'foo', 'foos', 10, 'newdomain' )->andReturn( 'oos-fay' );
Functions\expect( '_n' )->never()->with( 'bar', 'bars', 42, 'newdomain' );
@@ -893,7 +940,7 @@ public function test_filter_ngettext() {
/** Test filter_gettext_with_context. */
public function test_filter_gettext_with_context() {
- TestingAccessWrapper::newFromClass( Assets::class )->domain_map = array( 'olddomain' => array( 'newdomain', 'plugins', '1.2.3' ) );
+ TestingAccessWrapper::newFromClass( Assets::class )->domain_map = array( 'olddomain' => array( 'newdomain', 'plugins', '1.2.3', '' ) );
Functions\expect( '_x' )->once()->with( 'foo', 'context', 'newdomain' )->andReturn( 'oo-fay' );
Functions\expect( '_x' )->never()->with( 'bar', 'context', 'newdomain' );
@@ -905,7 +952,7 @@ public function test_filter_gettext_with_context() {
/** Test filter_ngettext_with_context. */
public function test_filter_ngettext_with_context() {
- TestingAccessWrapper::newFromClass( Assets::class )->domain_map = array( 'olddomain' => array( 'newdomain', 'plugins', '1.2.3' ) );
+ TestingAccessWrapper::newFromClass( Assets::class )->domain_map = array( 'olddomain' => array( 'newdomain', 'plugins', '1.2.3', '' ) );
Functions\expect( '_nx' )->once()->with( 'foo', 'foos', 10, 'context', 'newdomain' )->andReturn( 'oos-fay' );
Functions\expect( '_nx' )->never()->with( 'bar', 'bars', 42, 'context', 'newdomain' );
@@ -926,9 +973,9 @@ public function test_filter_ngettext_with_context() {
public function test_filter_load_script_translation_file( $args, $is_readable, $expect ) {
Jetpack_Constants::set_constant( 'WP_LANG_DIR', '/path/to/wordpress/wp-content/languages' );
TestingAccessWrapper::newFromClass( Assets::class )->domain_map = array(
- 'one' => array( 'new1', 'plugins', '1.2.3' ),
- 'two' => array( 'new2', 'themes', '1.2.3' ),
- 'three' => array( 'new3', 'core', '1.2.3' ),
+ 'one' => array( 'new1', 'plugins', '1.2.3', 'path/to/one' ),
+ 'two' => array( 'new2', 'themes', '1.2.3', '' ),
+ 'three' => array( 'new3', 'core', '1.2.3', '' ),
);
Functions\when( 'is_readable' )->alias(
function ( $file ) use ( $is_readable ) {
diff --git a/projects/packages/assets/webpack.config.js b/projects/packages/assets/webpack.config.js
new file mode 100644
index 0000000000000..31af843abff68
--- /dev/null
+++ b/projects/packages/assets/webpack.config.js
@@ -0,0 +1,37 @@
+const jetpackWebpackConfig = require( '@automattic/jetpack-webpack-config/webpack' );
+const path = require( 'path' );
+
+module.exports = [
+ {
+ entry: {
+ 'i18n-loader': {
+ import: './src/js/i18n-loader.js',
+ library: {
+ name: [ 'wp', 'jpI18nLoader' ],
+ type: 'window',
+ },
+ },
+ },
+ mode: jetpackWebpackConfig.mode,
+ devtool: jetpackWebpackConfig.isProduction ? false : 'source-map',
+ output: {
+ ...jetpackWebpackConfig.output,
+ path: path.resolve( './build' ),
+ },
+ optimization: {
+ ...jetpackWebpackConfig.optimization,
+ },
+ resolve: {
+ ...jetpackWebpackConfig.resolve,
+ },
+ node: false,
+ plugins: [ ...jetpackWebpackConfig.StandardPlugins() ],
+ module: {
+ strictExportPresence: true,
+ rules: [
+ // Transpile JavaScript, including node_modules.
+ jetpackWebpackConfig.TranspileRule(),
+ ],
+ },
+ },
+];
diff --git a/projects/packages/autoloader/CHANGELOG.md b/projects/packages/autoloader/CHANGELOG.md
index 14167ae2a0274..a895b18c8d900 100644
--- a/projects/packages/autoloader/CHANGELOG.md
+++ b/projects/packages/autoloader/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).
+## [2.10.12] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [2.10.11] - 2022-01-04
### Changed
- Switch to pcov for code coverage.
@@ -221,6 +225,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add Custom Autoloader
+[2.10.12]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.11...v2.10.12
[2.10.11]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.10...v2.10.11
[2.10.10]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.9...v2.10.10
[2.10.9]: https://github.com/Automattic/jetpack-autoloader/compare/v2.10.8...v2.10.9
diff --git a/projects/packages/autoloader/changelog/update-project-scripts-no-install b/projects/packages/autoloader/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/autoloader/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/backup/CHANGELOG.md b/projects/packages/backup/CHANGELOG.md
index cd5b3bb331ac4..8d63dd84d7083 100644
--- a/projects/packages/backup/CHANGELOG.md
+++ b/projects/packages/backup/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).
+## [1.2.3] - 2022-01-25
+### Changed
+- Dependency Update - Sync from 1.29 to 1.29
+
## [1.2.2] - 2022-01-18
### Changed
- Updated package dependencies.
@@ -102,6 +106,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add API endpoints and Jetpack Backup package for managing Help…
+[1.2.3]: https://github.com/Automattic/jetpack-backup/compare/v1.2.2...v1.2.3
[1.2.2]: https://github.com/Automattic/jetpack-backup/compare/v1.2.1...v1.2.2
[1.2.1]: https://github.com/Automattic/jetpack-backup/compare/v1.2.0...v1.2.1
[1.2.0]: https://github.com/Automattic/jetpack-backup/compare/v1.1.11...v1.2.0
diff --git a/projects/packages/backup/changelog/add-jp-search-sync2 b/projects/packages/backup/changelog/add-jp-search-sync2
deleted file mode 100644
index 042129798fa81..0000000000000
--- a/projects/packages/backup/changelog/add-jp-search-sync2
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Dependency Update - Sync from 1.29 to 1.29
diff --git a/projects/packages/backup/changelog/update-project-scripts-no-install b/projects/packages/backup/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/backup/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/backup/src/class-package-version.php b/projects/packages/backup/src/class-package-version.php
index e87ace5d402db..33ec82e922dc1 100644
--- a/projects/packages/backup/src/class-package-version.php
+++ b/projects/packages/backup/src/class-package-version.php
@@ -12,7 +12,7 @@
*/
class Package_Version {
- const PACKAGE_VERSION = '1.2.3-alpha';
+ const PACKAGE_VERSION = '1.2.3';
const PACKAGE_SLUG = 'backup';
diff --git a/projects/packages/blocks/CHANGELOG.md b/projects/packages/blocks/CHANGELOG.md
index de4cec7982fbb..48afbf696c19f 100644
--- a/projects/packages/blocks/CHANGELOG.md
+++ b/projects/packages/blocks/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).
+## [1.4.11] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [1.4.10] - 2022-01-18
### Changed
- Updated package dependencies.
@@ -96,6 +100,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Blocks: introduce new package for block management
+[1.4.11]: https://github.com/Automattic/jetpack-blocks/compare/v1.4.10...v1.4.11
[1.4.10]: https://github.com/Automattic/jetpack-blocks/compare/v1.4.9...v1.4.10
[1.4.9]: https://github.com/Automattic/jetpack-blocks/compare/v1.4.8...v1.4.9
[1.4.8]: https://github.com/Automattic/jetpack-blocks/compare/v1.4.7...v1.4.8
diff --git a/projects/packages/blocks/changelog/update-project-scripts-no-install b/projects/packages/blocks/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/blocks/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/changelogger/CHANGELOG.md b/projects/packages/changelogger/CHANGELOG.md
index 9a3c117416daa..e3b00628fac8f 100644
--- a/projects/packages/changelogger/CHANGELOG.md
+++ b/projects/packages/changelogger/CHANGELOG.md
@@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [3.0.6] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [3.0.5] - 2022-01-04
### Changed
- Switch to pcov for code coverage.
@@ -86,6 +90,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added
- Initial version.
+[3.0.6]: https://github.com/Automattic/jetpack-changelogger/compare/3.0.5...3.0.6
[3.0.5]: https://github.com/Automattic/jetpack-changelogger/compare/3.0.4...3.0.5
[3.0.4]: https://github.com/Automattic/jetpack-changelogger/compare/3.0.3...3.0.4
[3.0.3]: https://github.com/Automattic/jetpack-changelogger/compare/3.0.2...3.0.3
diff --git a/projects/packages/changelogger/changelog/update-project-scripts-no-install b/projects/packages/changelogger/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/changelogger/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/changelogger/changelog/update-wpcs-to-dev-branch b/projects/packages/changelogger/changelog/update-wpcs-to-dev-branch
new file mode 100644
index 0000000000000..09ac220faa608
--- /dev/null
+++ b/projects/packages/changelogger/changelog/update-wpcs-to-dev-branch
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Fixed some new PHPCS warnings.
diff --git a/projects/packages/changelogger/src/Application.php b/projects/packages/changelogger/src/Application.php
index cd218a98bbd08..2290ad95fe480 100644
--- a/projects/packages/changelogger/src/Application.php
+++ b/projects/packages/changelogger/src/Application.php
@@ -18,7 +18,7 @@
*/
class Application extends SymfonyApplication {
- const VERSION = '3.0.6-alpha';
+ const VERSION = '3.0.7-alpha';
/**
* Constructor.
diff --git a/projects/packages/changelogger/tests/php/includes/lib/ParserTestCase.php b/projects/packages/changelogger/tests/php/includes/lib/ParserTestCase.php
index ffd6ce788e9ad..13103a1c0a2b7 100644
--- a/projects/packages/changelogger/tests/php/includes/lib/ParserTestCase.php
+++ b/projects/packages/changelogger/tests/php/includes/lib/ParserTestCase.php
@@ -132,7 +132,7 @@ protected function writeFixture( $filename, array $data ) {
$contents .= ' ' . get_class( $data['parse-exception'] ) . "\n";
$contents .= $this->indent( $data['parse-exception']->getMessage() ) . "\n";
$contents .= " ~~~~~~~~\n";
- } elseif ( isset( $data['parse-output'] ) && ! ( isset( $data['object'] ) && $data['object'] == $data['parse-output'] ) ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
+ } elseif ( isset( $data['parse-output'] ) && ! ( isset( $data['object'] ) && $data['object'] == $data['parse-output'] ) ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
$this->assertInstanceOf( Changelog::class, $data['parse-output'] );
$contents .= "\n## Expected output from `parse()`\n";
$contents .= " ~~~~~~~~json parse-output\n";
@@ -150,7 +150,7 @@ protected function writeFixture( $filename, array $data ) {
$contents .= ' ' . get_class( $data['format-exception'] ) . "\n";
$contents .= $this->indent( $data['format-exception']->getMessage() ) . "\n";
$contents .= " ~~~~~~~~\n";
- } elseif ( isset( $data['format-output'] ) && ! ( isset( $data['changelog'] ) && $data['changelog'] == $data['format-output'] ) ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
+ } elseif ( isset( $data['format-output'] ) && ! ( isset( $data['changelog'] ) && $data['changelog'] == $data['format-output'] ) ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
$this->assertIsString( $data['format-output'] );
$contents .= "\n## Expected output from `format()`\n";
$contents .= " ~~~~~~~~markdown format-output\n";
diff --git a/projects/packages/codesniffer/CHANGELOG.md b/projects/packages/codesniffer/CHANGELOG.md
index 6e6abec854130..4eacfa4e388b2 100644
--- a/projects/packages/codesniffer/CHANGELOG.md
+++ b/projects/packages/codesniffer/CHANGELOG.md
@@ -5,6 +5,16 @@ 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).
+## [2.4.0] - 2022-02-01
+### Added
+- Add a sniff to check the textdomain passed to `Assets::register_script()`.
+
+### Changed
+- Disable CI tests on 8.1, PHPCompatibility raises deprecation warnings.
+- Reconfigure phpcs so we don't need so many `phpcs:ignore` comments.
+- Switch to pcov for code coverage.
+- Updated package dependencies
+
## [2.3.0] - 2021-11-02
### Changed
- Set `convertDeprecationsToExceptions` true in PHPUnit config.
@@ -63,6 +73,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Codesniffer: Add a package to hold our coding standard
+[2.4.0]: https://github.com/Automattic/jetpack-codesniffer/compare/v2.3.0...v2.4.0
[2.3.0]: https://github.com/Automattic/jetpack-codesniffer/compare/v2.2.1...v2.3.0
[2.2.1]: https://github.com/Automattic/jetpack-codesniffer/compare/v2.2.0...v2.2.1
[2.2.0]: https://github.com/Automattic/jetpack-codesniffer/compare/v2.1.1...v2.2.0
diff --git a/projects/packages/codesniffer/Jetpack/ruleset.xml b/projects/packages/codesniffer/Jetpack/ruleset.xml
index 22aea15b3c373..fa02f39e9cd10 100644
--- a/projects/packages/codesniffer/Jetpack/ruleset.xml
+++ b/projects/packages/codesniffer/Jetpack/ruleset.xml
@@ -42,6 +42,9 @@
+
+
+
error
diff --git a/projects/packages/codesniffer/changelog/add-disallow-relative-includes b/projects/packages/codesniffer/changelog/add-disallow-relative-includes
new file mode 100644
index 0000000000000..8ea39d66578b5
--- /dev/null
+++ b/projects/packages/codesniffer/changelog/add-disallow-relative-includes
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Add sniff to disallow relative file includes.
diff --git a/projects/packages/codesniffer/changelog/add-disallow-relative-includes-2 b/projects/packages/codesniffer/changelog/add-disallow-relative-includes-2
new file mode 100644
index 0000000000000..d455739c9d693
--- /dev/null
+++ b/projects/packages/codesniffer/changelog/add-disallow-relative-includes-2
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Fixed minor coding standard violation.
diff --git a/projects/packages/codesniffer/changelog/add-phpcs-per-director-config b/projects/packages/codesniffer/changelog/add-phpcs-per-director-config
deleted file mode 100644
index 196bbd6894eb7..0000000000000
--- a/projects/packages/codesniffer/changelog/add-phpcs-per-director-config
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Reconfigure phpcs so we don't need so many `phpcs:ignore` comments.
diff --git a/projects/packages/codesniffer/changelog/add-phpcs-sniff-for-assets-register_script-textdomain b/projects/packages/codesniffer/changelog/add-phpcs-sniff-for-assets-register_script-textdomain
deleted file mode 100644
index 42c68f42f1d8a..0000000000000
--- a/projects/packages/codesniffer/changelog/add-phpcs-sniff-for-assets-register_script-textdomain
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: added
-
-Add a sniff to check the textdomain passed to `Assets::register_script()`.
diff --git a/projects/packages/codesniffer/changelog/fix-disable-codesniffer-tests-on-8.1 b/projects/packages/codesniffer/changelog/fix-disable-codesniffer-tests-on-8.1
deleted file mode 100644
index e07dec6413dbb..0000000000000
--- a/projects/packages/codesniffer/changelog/fix-disable-codesniffer-tests-on-8.1
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Disable CI tests on 8.1, PHPCompatibility raises deprecation warnings.
diff --git a/projects/packages/codesniffer/changelog/renovate-yoast-phpunit-polyfills-1.x b/projects/packages/codesniffer/changelog/renovate-yoast-phpunit-polyfills-1.x
deleted file mode 100644
index 302aa8cf46b5c..0000000000000
--- a/projects/packages/codesniffer/changelog/renovate-yoast-phpunit-polyfills-1.x
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies
diff --git a/projects/packages/codesniffer/changelog/try-pcov b/projects/packages/codesniffer/changelog/try-pcov
deleted file mode 100644
index bbcd26b6ce57e..0000000000000
--- a/projects/packages/codesniffer/changelog/try-pcov
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Switch to pcov for code coverage.
diff --git a/projects/packages/codesniffer/changelog/update-composer b/projects/packages/codesniffer/changelog/update-composer
deleted file mode 100644
index 9d476b2308bac..0000000000000
--- a/projects/packages/codesniffer/changelog/update-composer
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Tooling update: Use composer 2.2.
-
-
diff --git a/projects/packages/codesniffer/changelog/update-project-scripts-no-install b/projects/packages/codesniffer/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/codesniffer/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/codesniffer/changelog/update-wpcs-to-dev-branch b/projects/packages/codesniffer/changelog/update-wpcs-to-dev-branch
new file mode 100644
index 0000000000000..9834031ba0637
--- /dev/null
+++ b/projects/packages/codesniffer/changelog/update-wpcs-to-dev-branch
@@ -0,0 +1,4 @@
+Significance: minor
+Type: fixed
+
+Update `wp-coding-standards/wpcs` to `dev-develop`. They haven't done a release in over a year, and we need fixes for errors in PHP 8.0 and 8.1.
diff --git a/projects/packages/codesniffer/composer.json b/projects/packages/codesniffer/composer.json
index eeada7d163c10..b451c637bcc11 100644
--- a/projects/packages/codesniffer/composer.json
+++ b/projects/packages/codesniffer/composer.json
@@ -8,7 +8,8 @@
"mediawiki/mediawiki-codesniffer": "^38.0",
"phpcompatibility/phpcompatibility-wp": "^2.1",
"sirbrillig/phpcs-variable-analysis": "^2.10",
- "wp-coding-standards/wpcs": "^2.3"
+ "wp-coding-standards/wpcs": "dev-develop as 2.3.1",
+ "automattic/vipwpcs": "^2.3"
},
"require-dev": {
"yoast/phpunit-polyfills": "1.0.3",
@@ -54,7 +55,7 @@
"link-template": "https://github.com/Automattic/jetpack-codesniffer/compare/v${old}...v${new}"
},
"branch-alias": {
- "dev-master": "2.4.x-dev"
+ "dev-master": "2.5.x-dev"
}
},
"config": {
diff --git a/projects/packages/codesniffer/tests/php/tests/files/i18n.php.fixed b/projects/packages/codesniffer/tests/php/tests/files/i18n.php.fixed
index fd40a059572d9..4840f30e46c3e 100644
--- a/projects/packages/codesniffer/tests/php/tests/files/i18n.php.fixed
+++ b/projects/packages/codesniffer/tests/php/tests/files/i18n.php.fixed
@@ -41,8 +41,8 @@ Automattic\Jetpack\Assets::register_script( 'handle', 'path', __FILE__, array( '
\Automattic\Jetpack\Assets::register_script( 'handle', 'path', __FILE__, array( 'textdomain' => 'domain' ) );
Assets::register_script( 'handle', 'path', __FILE__, array( 'textdomain', 'foo' => 'bar' ) );
Assets::register_script( 'handle', 'path', __FILE__, array('textdomain'=>'domain') );
-Assets::register_script( 'handle', 'path', __FILE__, [ 'textdomain' => 'domain' ] );
-Assets::register_script( 'handle', 'path', __FILE__, ['textdomain'=>'domain'] );
+Assets::register_script( 'handle', 'path', __FILE__, array( 'textdomain' => 'domain' ) );
+Assets::register_script( 'handle', 'path', __FILE__, array('textdomain'=>'domain') );
Assets::register_script( 'handle', 'path', __FILE__, array( "textdomain" => "domain" ) );
Assets::register_script(
'handle',
@@ -50,7 +50,7 @@ Assets::register_script(
__FILE__,
array(
'foo' => array( 'bar', 'textdomain' => 'wrongdomain' ),
- 'bar' => [ 'baz', 'textdomain' => 'wrongdomain' ],
+ 'bar' => array( 'baz', 'textdomain' => 'wrongdomain' ),
'textdomain' => 'domain',
)
);
@@ -60,8 +60,8 @@ Assets::register_script( 'handle', 'path', __FILE__, array( 'textdomain' => 'dom
Automattic\Jetpack\Assets::register_script( 'handle', 'path', __FILE__, array( 'textdomain' => 'domain' ) );
\Automattic\Jetpack\Assets::register_script( 'handle', 'path', __FILE__, array( 'textdomain' => 'domain' ) );
Assets::register_script( 'handle', 'path', __FILE__, array('textdomain'=>'domain') );
-Assets::register_script( 'handle', 'path', __FILE__, [ 'textdomain' => 'domain' ] );
-Assets::register_script( 'handle', 'path', __FILE__, ['textdomain'=>'domain'] );
+Assets::register_script( 'handle', 'path', __FILE__, array( 'textdomain' => 'domain' ) );
+Assets::register_script( 'handle', 'path', __FILE__, array('textdomain'=>'domain') );
Assets::register_script( 'handle', 'path', __FILE__, array( "textdomain" => 'domain' ) );
Assets::register_script(
'handle',
diff --git a/projects/packages/codesniffer/tests/php/tests/files/i18n.php.report b/projects/packages/codesniffer/tests/php/tests/files/i18n.php.report
index d5c6e54cd60fe..db390251a1fa5 100644
--- a/projects/packages/codesniffer/tests/php/tests/files/i18n.php.report
+++ b/projects/packages/codesniffer/tests/php/tests/files/i18n.php.report
@@ -9,11 +9,16 @@
30 | ERROR | [ ] The domain must not contain interpolated variables. Found $domain.
| | (Jetpack.Functions.I18n.InterpolatedVariable)
33 | ERROR | [x] Mismatched text domain. Expected 'domain' but got 'a $domain'. (Jetpack.Functions.I18n.TextDomainMismatch)
+ 44 | ERROR | [x] Short array syntax is not allowed (Universal.Arrays.DisallowShortArraySyntax.Found)
+ 45 | ERROR | [x] Short array syntax is not allowed (Universal.Arrays.DisallowShortArraySyntax.Found)
+ 53 | ERROR | [x] Short array syntax is not allowed (Universal.Arrays.DisallowShortArraySyntax.Found)
59 | ERROR | [x] Mismatched text domain. Expected 'domain' but got 'wrongdomain'. (Jetpack.Functions.I18n.TextDomainMismatch)
60 | ERROR | [x] Mismatched text domain. Expected 'domain' but got 'wrongdomain'. (Jetpack.Functions.I18n.TextDomainMismatch)
61 | ERROR | [x] Mismatched text domain. Expected 'domain' but got 'wrongdomain'. (Jetpack.Functions.I18n.TextDomainMismatch)
62 | ERROR | [x] Mismatched text domain. Expected 'domain' but got 'wrongdomain'. (Jetpack.Functions.I18n.TextDomainMismatch)
+ 63 | ERROR | [x] Short array syntax is not allowed (Universal.Arrays.DisallowShortArraySyntax.Found)
63 | ERROR | [x] Mismatched text domain. Expected 'domain' but got 'wrongdomain'. (Jetpack.Functions.I18n.TextDomainMismatch)
+ 64 | ERROR | [x] Short array syntax is not allowed (Universal.Arrays.DisallowShortArraySyntax.Found)
64 | ERROR | [x] Mismatched text domain. Expected 'domain' but got 'wrongdomain'. (Jetpack.Functions.I18n.TextDomainMismatch)
65 | ERROR | [x] Mismatched text domain. Expected 'domain' but got 'wrongdomain'. (Jetpack.Functions.I18n.TextDomainMismatch)
72 | ERROR | [x] Mismatched text domain. Expected 'domain' but got 'wrongdomain'. (Jetpack.Functions.I18n.TextDomainMismatch)
diff --git a/projects/packages/codesniffer/tests/php/tests/files/mediawiki-imports.php.report b/projects/packages/codesniffer/tests/php/tests/files/mediawiki-imports.php.report
index 498aba1bc0562..6962a61af7546 100644
--- a/projects/packages/codesniffer/tests/php/tests/files/mediawiki-imports.php.report
+++ b/projects/packages/codesniffer/tests/php/tests/files/mediawiki-imports.php.report
@@ -3,6 +3,8 @@
16 | WARNING | [x] Unused use statement "Unused" (MediaWiki.Classes.UnusedUseStatement.UnusedUse)
37 | WARNING | [x] Floats should have a leading 0 (MediaWiki.AlternativeSyntax.LeadingZeroInFloat.Found)
40 | WARNING | [x] require_once keyword must not be used as a function. (MediaWiki.ExtraCharacters.ParenthesesAroundKeyword.ParenthesesAroundKeywords)
+ 40 | ERROR | [ ] Absolute include path must be used. Use `get_template_directory()`, `get_stylesheet_directory()` or `plugin_dir_path()`.
+ | | (WordPressVIPMinimum.Files.IncludingFile.NotAbsolutePath)
41 | WARNING | [x] clone keyword must not be used as a function. (MediaWiki.ExtraCharacters.ParenthesesAroundKeyword.ParenthesesAroundKeywords)
44 | ERROR | [x] Use __DIR__ constant instead of calling dirname(__FILE__) (MediaWiki.Usage.DirUsage.FunctionFound)
47 | WARNING | [x] Use (bool) instead of !! (MediaWiki.Usage.DoubleNotOperator.DoubleNotOperator)
@@ -13,8 +15,7 @@
65 | ERROR | [ ] Function nested is nested inside of another function or closure (MediaWiki.Usage.NestedFunctions.NestedFunction)
69 | ERROR | [ ] Use "." for string concat (MediaWiki.Usage.PlusStringConcat.Found)
70 | ERROR | [ ] Use ".=" for string concat (MediaWiki.Usage.PlusStringConcat.Found)
- 73 | ERROR | [ ] The ampersand in "&$this" must be removed. If you plan to get back another instance of this class, assign $this to a temporary variable.
- | | (MediaWiki.Usage.ReferenceThis.Found)
+ 73 | ERROR | [ ] The ampersand in "&$this" must be removed. If you plan to get back another instance of this class, assign $this to a temporary variable. (MediaWiki.Usage.ReferenceThis.Found)
76 | ERROR | [x] Multiple empty lines should not exist in a row; found 2 consecutive empty lines (MediaWiki.WhiteSpace.MultipleEmptyLines.MultipleEmptyLines)
79 | ERROR | [x] A single space should be after the function keyword in closures (MediaWiki.WhiteSpace.SpaceAfterClosure.NoWhitespaceAfterClosure)
86 | ERROR | [x] Multiple empty lines should not exist in a row; found 2 consecutive empty lines (MediaWiki.WhiteSpace.MultipleEmptyLines.MultipleEmptyLines)
diff --git a/projects/packages/codesniffer/tests/php/tests/test-jetpackstandard.php b/projects/packages/codesniffer/tests/php/tests/test-jetpackstandard.php
index b6b3b8099d656..6d4a22f813f5d 100644
--- a/projects/packages/codesniffer/tests/php/tests/test-jetpackstandard.php
+++ b/projects/packages/codesniffer/tests/php/tests/test-jetpackstandard.php
@@ -87,10 +87,16 @@ private function run_phpcs( $file, $fix ) {
* @param bool $fix Run as phpcbf rather than phpcs.
*/
public function test_phpcs( $file, $fix ) {
- $expect = file_get_contents( $fix ? "$file.fixed" : "$file.report" );
+ $snapfile = $fix ? "$file.fixed" : "$file.report";
+ $expect = file_get_contents( $snapfile );
$this->assertIsString( $expect );
- file_put_contents( $fix ? "$file.fixed" : "$file.report", $expect = $this->run_phpcs( $file, $fix ) );
- $this->assertEquals( $expect, $this->run_phpcs( $file, $fix ) );
+ $actual = $this->run_phpcs( $file, $fix );
+ if ( getenv( 'UPDATE_SNAPSHOTS' ) && $expect !== $actual ) {
+ file_put_contents( $snapfile, $actual );
+ $this->addWarning( "Updated snapshot in $snapfile" );
+ } else {
+ $this->assertEquals( $expect, $actual, '(Run with UPDATE_SNAPSHOTS=1 to update snapshots)' );
+ }
}
/**
diff --git a/projects/packages/composer-plugin/CHANGELOG.md b/projects/packages/composer-plugin/CHANGELOG.md
index 9b6ea6609edc9..0368a922e8938 100644
--- a/projects/packages/composer-plugin/CHANGELOG.md
+++ b/projects/packages/composer-plugin/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).
+## [1.1.0] - 2022-01-25
+### Added
+- Include package path prefixes in `i18n-map.php` so Assets can map them when lazy-loading.
+
## [1.0.2] - 2022-01-13
### Fixed
- Composer's `getVersion()` likes to return 4-component versions, while semver wants only 3 components. Strip any extra components instead of considering that invalid.
@@ -30,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added the Jetpack Installer package.
+[1.1.0]: https://github.com/Automattic/jetpack-composer-plugin/compare/v1.0.2...v1.1.0
[1.0.2]: https://github.com/Automattic/jetpack-composer-plugin/compare/v1.0.1...v1.0.2
[1.0.1]: https://github.com/Automattic/jetpack-composer-plugin/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/Automattic/jetpack-composer-plugin/compare/v0.2.0...v1.0.0
diff --git a/projects/packages/composer-plugin/changelog/update-project-scripts-no-install b/projects/packages/composer-plugin/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/composer-plugin/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/composer-plugin/composer.json b/projects/packages/composer-plugin/composer.json
index 7609c6604bb88..f421251325324 100644
--- a/projects/packages/composer-plugin/composer.json
+++ b/projects/packages/composer-plugin/composer.json
@@ -46,7 +46,7 @@
},
"autotagger": true,
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.1.x-dev"
}
}
}
diff --git a/projects/packages/composer-plugin/src/class-plugin.php b/projects/packages/composer-plugin/src/class-plugin.php
index 7f932fd3503bb..b176905b2d50a 100644
--- a/projects/packages/composer-plugin/src/class-plugin.php
+++ b/projects/packages/composer-plugin/src/class-plugin.php
@@ -123,8 +123,11 @@ public function generateManifest( Event $event ) {
if ( empty( $extra['textdomain'] ) ) {
$io->info( " {$package->getName()} ($ver): no textdomain set" );
} else {
- $data['packages'][ $extra['textdomain'] ] = $ver;
- $io->info( " {$package->getName()} ($ver): textdomain is {$extra['textdomain']}" );
+ $data['packages'][ $extra['textdomain'] ] = array(
+ 'path' => 'jetpack_vendor/' . $package->getPrettyName(),
+ 'ver' => $ver,
+ );
+ $io->info( " {$package->getName()} ($ver): textdomain is {$extra['textdomain']}, path is jetpack_vendor/{$package->getPrettyName()}" );
}
}
diff --git a/projects/packages/connection-ui/CHANGELOG.md b/projects/packages/connection-ui/CHANGELOG.md
index 13783f661d629..95250a036adcd 100644
--- a/projects/packages/connection-ui/CHANGELOG.md
+++ b/projects/packages/connection-ui/CHANGELOG.md
@@ -5,6 +5,17 @@ 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).
+## [2.3.4] - 2022-02-02
+### Changed
+- Updated package dependencies.
+
+## [2.3.3] - 2022-01-25
+### Added
+- connection initial state
+
+### Changed
+- Updated package dependencies.
+
## [2.3.2] - 2022-01-18
### Changed
- General: update required node version to v16.13.2
@@ -170,7 +181,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Connection UI: Building the Framework
-[2.3.2]: https://github.com/Automattic/jetpack-connection-ui/compare/v2.3.1...v2.3.2
+[2.3.4]: https://github.com/Automattic/jetpack-connection-ui/compare/v2.3.3...v2.3.4
+[2.3.3]: https://github.com/Automattic/jetpack-connection-ui/compare/v2.3.2...v2.3.3
[2.3.2]: https://github.com/Automattic/jetpack-connection-ui/compare/v2.3.1...v2.3.2
[2.3.1]: https://github.com/Automattic/jetpack-connection-ui/compare/v2.3.0...v2.3.1
[2.3.0]: https://github.com/Automattic/jetpack-connection-ui/compare/v2.2.0...v2.3.0
diff --git a/projects/packages/connection-ui/changelog/add-fancy-eslint-ignore b/projects/packages/connection-ui/changelog/add-fancy-eslint-ignore
deleted file mode 100644
index afe95e481cf32..0000000000000
--- a/projects/packages/connection-ui/changelog/add-fancy-eslint-ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Hack eslint config to use `.gitignore` and per-dir `.eslintignore`.
-
-
diff --git a/projects/js-packages/storybook/changelog/renovate-storybook-addon-knobs-6.x b/projects/packages/connection-ui/changelog/renovate-wordpress-monorepo
similarity index 100%
rename from projects/js-packages/storybook/changelog/renovate-storybook-addon-knobs-6.x
rename to projects/packages/connection-ui/changelog/renovate-wordpress-monorepo
diff --git a/projects/js-packages/storybook/changelog/2021-11-23-12-49-57-152426 b/projects/packages/connection-ui/changelog/renovate-wordpress-monorepo#2
similarity index 100%
rename from projects/js-packages/storybook/changelog/2021-11-23-12-49-57-152426
rename to projects/packages/connection-ui/changelog/renovate-wordpress-monorepo#2
diff --git a/projects/packages/connection-ui/changelog/update-connection-enforce-initial-state b/projects/packages/connection-ui/changelog/update-connection-enforce-initial-state
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/packages/connection-ui/changelog/update-connection-enforce-initial-state
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/packages/connection-ui/changelog/update-project-scripts-no-install b/projects/packages/connection-ui/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/connection-ui/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/connection-ui/composer.json b/projects/packages/connection-ui/composer.json
index 55227dc5d0e0c..98d2f23f43aeb 100644
--- a/projects/packages/connection-ui/composer.json
+++ b/projects/packages/connection-ui/composer.json
@@ -4,11 +4,11 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
- "automattic/jetpack-assets": "^1.16",
+ "automattic/jetpack-assets": "^1.17",
"automattic/jetpack-connection": "^1.36",
"automattic/jetpack-constants": "^1.6",
"automattic/jetpack-device-detection": "^1.4",
- "automattic/jetpack-identity-crisis": "^0.6"
+ "automattic/jetpack-identity-crisis": "^0.7"
},
"require-dev": {
"automattic/jetpack-changelogger": "^3.0"
diff --git a/projects/packages/connection-ui/package.json b/projects/packages/connection-ui/package.json
index e31a41e852317..6b126b7461c92 100644
--- a/projects/packages/connection-ui/package.json
+++ b/projects/packages/connection-ui/package.json
@@ -1,6 +1,6 @@
{
"name": "jetpack-connection-manager-ui",
- "version": "2.3.3-alpha",
+ "version": "2.3.5-alpha",
"description": "Jetpack Connection Manager UI",
"main": "_inc/admin.jsx",
"repository": "https://github.com/Automattic/jetpack-connection-ui",
@@ -15,19 +15,19 @@
},
"browserslist": "extends @wordpress/browserslist-config",
"dependencies": {
- "@automattic/jetpack-api": "workspace:^0.8.3-alpha",
- "@automattic/jetpack-connection": "workspace:^0.14.0-alpha",
- "@wordpress/data": "6.1.5"
+ "@automattic/jetpack-api": "workspace:^0.8.4-alpha",
+ "@automattic/jetpack-connection": "workspace:^0.15.1-alpha",
+ "@wordpress/data": "6.2.0"
},
"devDependencies": {
- "@automattic/jetpack-webpack-config": "workspace:^1.0.3-alpha",
+ "@automattic/jetpack-webpack-config": "workspace:^1.1.2-alpha",
"@babel/core": "7.16.0",
"@babel/preset-env": "7.16.4",
"@babel/register": "7.16.0",
"@babel/runtime": "7.16.3",
"@wordpress/browserslist-config": "4.1.0",
- "@wordpress/data": "6.1.5",
- "@wordpress/i18n": "4.2.4",
+ "@wordpress/data": "6.2.0",
+ "@wordpress/i18n": "4.3.0",
"enzyme": "3.11.0",
"jest": "27.3.1",
"react": "17.0.2",
diff --git a/projects/packages/connection/CHANGELOG.md b/projects/packages/connection/CHANGELOG.md
index 4868b0ca44205..3c3365967e4f9 100644
--- a/projects/packages/connection/CHANGELOG.md
+++ b/projects/packages/connection/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).
+## [1.36.1] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [1.36.0] - 2022-01-18
### Added
- Debugging: Add a filter to add XDEBUG_PROFILE to requests made to the sandbox.
@@ -499,6 +503,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Separate the connection library into its own package.
+[1.36.1]: https://github.com/Automattic/jetpack-connection/compare/v1.36.0...v1.36.1
[1.36.0]: https://github.com/Automattic/jetpack-connection/compare/v1.35.0...v1.36.0
[1.35.0]: https://github.com/Automattic/jetpack-connection/compare/v1.34.0...v1.35.0
[1.34.0]: https://github.com/Automattic/jetpack-connection/compare/v1.33.0...v1.34.0
diff --git a/projects/packages/connection/changelog/add-visitor-status b/projects/packages/connection/changelog/add-visitor-status
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/packages/connection/changelog/add-visitor-status
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/packages/connection/changelog/update-project-scripts-no-install b/projects/packages/connection/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/connection/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/connection/changelog/update-wpcs-to-dev-branch b/projects/packages/connection/changelog/update-wpcs-to-dev-branch
new file mode 100644
index 0000000000000..09ac220faa608
--- /dev/null
+++ b/projects/packages/connection/changelog/update-wpcs-to-dev-branch
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Fixed some new PHPCS warnings.
diff --git a/projects/packages/connection/src/class-client.php b/projects/packages/connection/src/class-client.php
index fea9e9c7fcbd5..69e5198579d26 100644
--- a/projects/packages/connection/src/class-client.php
+++ b/projects/packages/connection/src/class-client.php
@@ -310,7 +310,7 @@ public static function set_time_diff( &$response, $force_set = false ) {
$code = wp_remote_retrieve_response_code( $response );
// Only trust the Date header on some responses.
- if ( 200 != $code && 304 != $code && 400 != $code && 401 != $code ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
+ if ( 200 != $code && 304 != $code && 400 != $code && 401 != $code ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
return;
}
diff --git a/projects/packages/connection/src/class-package-version.php b/projects/packages/connection/src/class-package-version.php
index 1761a8eac1625..70a72bc95e530 100644
--- a/projects/packages/connection/src/class-package-version.php
+++ b/projects/packages/connection/src/class-package-version.php
@@ -12,7 +12,7 @@
*/
class Package_Version {
- const PACKAGE_VERSION = '1.36.1-alpha';
+ const PACKAGE_VERSION = '1.36.2-alpha';
const PACKAGE_SLUG = 'connection';
diff --git a/projects/packages/connection/src/class-rest-connector.php b/projects/packages/connection/src/class-rest-connector.php
index 1f4dfb5e21dc6..077dadfdd7cba 100644
--- a/projects/packages/connection/src/class-rest-connector.php
+++ b/projects/packages/connection/src/class-rest-connector.php
@@ -323,7 +323,7 @@ public static function connection_status( $rest_response = true ) {
'filter' => ( apply_filters( 'jetpack_development_mode', false ) || apply_filters( 'jetpack_offline_mode', false ) ), // jetpack_development_mode is deprecated.
'wpLocalConstant' => defined( 'WP_LOCAL_DEV' ) && WP_LOCAL_DEV,
),
- 'isPublic' => '1' == get_option( 'blog_public' ), // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
+ 'isPublic' => '1' == get_option( 'blog_public' ), // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
);
/**
diff --git a/projects/packages/connection/src/class-tokens.php b/projects/packages/connection/src/class-tokens.php
index 38fb58d4124b9..7522f40e9a590 100644
--- a/projects/packages/connection/src/class-tokens.php
+++ b/projects/packages/connection/src/class-tokens.php
@@ -58,7 +58,7 @@ public function validate( $user_id = null ) {
// Cannot validate non-existent tokens.
if ( false === $user_token || false === $blog_token ) {
return false;
- };
+ }
$method = 'POST';
$body = array(
diff --git a/projects/packages/constants/CHANGELOG.md b/projects/packages/constants/CHANGELOG.md
index c4b42f4562d55..d8b6f4ba293a9 100644
--- a/projects/packages/constants/CHANGELOG.md
+++ b/projects/packages/constants/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).
+## [1.6.15] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [1.6.14] - 2022-01-04
### Changed
- Switch to pcov for code coverage.
@@ -118,6 +122,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Packages: Finish the constants package
+[1.6.15]: https://github.com/Automattic/jetpack-constants/compare/v1.6.14...v1.6.15
[1.6.14]: https://github.com/Automattic/jetpack-constants/compare/v1.6.13...v1.6.14
[1.6.13]: https://github.com/Automattic/jetpack-constants/compare/v1.6.12...v1.6.13
[1.6.12]: https://github.com/Automattic/jetpack-constants/compare/v1.6.11...v1.6.12
diff --git a/projects/packages/constants/changelog/update-project-scripts-no-install b/projects/packages/constants/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/constants/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/device-detection/CHANGELOG.md b/projects/packages/device-detection/CHANGELOG.md
index 7330dceda6648..6d65c5e5a1e45 100644
--- a/projects/packages/device-detection/CHANGELOG.md
+++ b/projects/packages/device-detection/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).
+## [1.4.12] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [1.4.11] - 2022-01-04
### Changed
- Switch to pcov for code coverage.
@@ -96,6 +100,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Moving jetpack_is_mobile into a package
+[1.4.12]: https://github.com/Automattic/jetpack-device-detection/compare/v1.4.11...v1.4.12
[1.4.11]: https://github.com/Automattic/jetpack-device-detection/compare/v1.4.10...v1.4.11
[1.4.10]: https://github.com/Automattic/jetpack-device-detection/compare/v1.4.9...v1.4.10
[1.4.9]: https://github.com/Automattic/jetpack-device-detection/compare/v1.4.8...v1.4.9
diff --git a/projects/packages/device-detection/changelog/update-project-scripts-no-install b/projects/packages/device-detection/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/device-detection/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/device-detection/changelog/update-wpcs-to-dev-branch b/projects/packages/device-detection/changelog/update-wpcs-to-dev-branch
new file mode 100644
index 0000000000000..09ac220faa608
--- /dev/null
+++ b/projects/packages/device-detection/changelog/update-wpcs-to-dev-branch
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Fixed some new PHPCS warnings.
diff --git a/projects/packages/device-detection/src/class-user-agent-info.php b/projects/packages/device-detection/src/class-user-agent-info.php
index e8d2f20acea14..20852e7c3c064 100644
--- a/projects/packages/device-detection/src/class-user-agent-info.php
+++ b/projects/packages/device-detection/src/class-user-agent-info.php
@@ -1445,7 +1445,7 @@ public static function is_mobile_app() {
// the mobile reader on iOS has an incorrect UA when loading the reader
// currently it is the default one provided by the iOS framework which
// causes problems with 2-step-auth
- // User-Agent WordPress/3.1.4 CFNetwork/609 Darwin/13.0.0.
+ // User-Agent WordPress/3.1.4 CFNetwork/609 Darwin/13.0.0.
$app_agents[] = 'wordpress/3.1';
foreach ( $app_agents as $app_agent ) {
diff --git a/projects/packages/error/CHANGELOG.md b/projects/packages/error/CHANGELOG.md
index ea0c6df4e8741..fe268048e6dc2 100644
--- a/projects/packages/error/CHANGELOG.md
+++ b/projects/packages/error/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).
+## [1.3.13] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [1.3.12] - 2022-01-04
### Changed
- Switch to pcov for code coverage.
@@ -92,6 +96,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Packages: Introduce a jetpack-error package
+[1.3.13]: https://github.com/Automattic/jetpack-error/compare/v1.3.12...v1.3.13
[1.3.12]: https://github.com/Automattic/jetpack-error/compare/v1.3.11...v1.3.12
[1.3.11]: https://github.com/Automattic/jetpack-error/compare/v1.3.10...v1.3.11
[1.3.10]: https://github.com/Automattic/jetpack-error/compare/v1.3.9...v1.3.10
diff --git a/projects/packages/error/changelog/update-project-scripts-no-install b/projects/packages/error/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/error/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/identity-crisis/CHANGELOG.md b/projects/packages/identity-crisis/CHANGELOG.md
index 8ca4619c1f170..77506aba0b285 100644
--- a/projects/packages/identity-crisis/CHANGELOG.md
+++ b/projects/packages/identity-crisis/CHANGELOG.md
@@ -5,6 +5,20 @@ 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.7.1] - 2022-02-02
+### Changed
+- Updated package dependencies.
+
+## [0.7.0] - 2022-01-27
+### Changed
+- IDC "Safe Mode" admin bar button redesign.
+
+## [0.6.4] - 2022-01-25
+### Changed
+- Add class notice to the IDC container div.
+- Add missing JS peer dependency.
+- Updated package dependencies.
+
## [0.6.3] - 2022-01-18
### Changed
- General: update required node version to v16.13.2
@@ -131,7 +145,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Updated package dependencies.
- Use Connection/Urls for home_url and site_url functions migrated from Sync.
-[0.6.3]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.6.2...v0.6.3
+[0.7.1]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.7.0...v0.7.1
+[0.7.0]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.6.4...v0.7.0
+[0.6.4]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.6.3...v0.6.4
[0.6.3]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.6.2...v0.6.3
[0.6.2]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.6.1...v0.6.2
[0.6.1]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.6.0...v0.6.1
diff --git a/projects/packages/identity-crisis/changelog/add-fancy-eslint-ignore b/projects/packages/identity-crisis/changelog/add-fancy-eslint-ignore
deleted file mode 100644
index eed3c13831009..0000000000000
--- a/projects/packages/identity-crisis/changelog/add-fancy-eslint-ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Hack eslint config to use `.gitignore` and per-dir `.eslintignore`.
-
-Add missing JS peer dependency.
diff --git a/projects/packages/identity-crisis/changelog/add-idc-customizate-labels b/projects/packages/identity-crisis/changelog/add-idc-customizate-labels
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/packages/identity-crisis/changelog/add-idc-customizate-labels
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/packages/identity-crisis/changelog/add-idc-customizer-tags b/projects/packages/identity-crisis/changelog/add-idc-customizer-tags
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/packages/identity-crisis/changelog/add-idc-customizer-tags
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/packages/identity-crisis/changelog/add-visitor-status b/projects/packages/identity-crisis/changelog/add-visitor-status
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/packages/identity-crisis/changelog/add-visitor-status
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/renovate-storybook-monorepo b/projects/packages/identity-crisis/changelog/renovate-wordpress-monorepo
similarity index 100%
rename from projects/js-packages/storybook/changelog/renovate-storybook-monorepo
rename to projects/packages/identity-crisis/changelog/renovate-wordpress-monorepo
diff --git a/projects/js-packages/storybook/changelog/2021-11-30-15-13-01-232780 b/projects/packages/identity-crisis/changelog/renovate-wordpress-monorepo#2
similarity index 100%
rename from projects/js-packages/storybook/changelog/2021-11-30-15-13-01-232780
rename to projects/packages/identity-crisis/changelog/renovate-wordpress-monorepo#2
diff --git a/projects/packages/identity-crisis/changelog/update-jetpack-idc-notice b/projects/packages/identity-crisis/changelog/update-jetpack-idc-notice
deleted file mode 100644
index daf7b7b240747..0000000000000
--- a/projects/packages/identity-crisis/changelog/update-jetpack-idc-notice
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Add class notice to the IDC container div.
diff --git a/projects/packages/identity-crisis/changelog/update-project-scripts-no-install b/projects/packages/identity-crisis/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/identity-crisis/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/identity-crisis/composer.json b/projects/packages/identity-crisis/composer.json
index 11eb1a9cf216d..f029c7dd0ad2c 100644
--- a/projects/packages/identity-crisis/composer.json
+++ b/projects/packages/identity-crisis/composer.json
@@ -10,7 +10,7 @@
"automattic/jetpack-status": "^1.10",
"automattic/jetpack-tracking": "^1.14",
"automattic/jetpack-logo": "^1.5",
- "automattic/jetpack-assets": "^1.16"
+ "automattic/jetpack-assets": "^1.17"
},
"require-dev": {
"automattic/jetpack-changelogger": "^3.0",
@@ -66,7 +66,7 @@
"link-template": "https://github.com/Automattic/jetpack-identity-crisis/compare/v${old}...v${new}"
},
"branch-alias": {
- "dev-master": "0.6.x-dev"
+ "dev-master": "0.7.x-dev"
}
},
"config": {
diff --git a/projects/packages/identity-crisis/package.json b/projects/packages/identity-crisis/package.json
index 515f519db39c7..6aabcbafd05ea 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.6.4-alpha",
+ "version": "0.7.2-alpha",
"description": "Jetpack Identity Crisis",
"main": "_inc/admin.jsx",
"repository": "https://github.com/Automattic/jetpack-identity-crisis",
@@ -15,11 +15,11 @@
},
"browserslist": "extends @wordpress/browserslist-config",
"dependencies": {
- "@automattic/jetpack-idc": "workspace:^0.9.0-alpha",
- "@wordpress/data": "6.1.5"
+ "@automattic/jetpack-idc": "workspace:^0.9.2-alpha",
+ "@wordpress/data": "6.2.0"
},
"devDependencies": {
- "@automattic/jetpack-webpack-config": "workspace:^1.0.3-alpha",
+ "@automattic/jetpack-webpack-config": "workspace:^1.1.2-alpha",
"@babel/core": "7.16.0",
"@babel/preset-env": "7.16.4",
"@babel/register": "7.16.0",
diff --git a/projects/packages/identity-crisis/src/_inc/admin-bar.scss b/projects/packages/identity-crisis/src/_inc/admin-bar.scss
index cd2008cf1d109..262b0819da211 100644
--- a/projects/packages/identity-crisis/src/_inc/admin-bar.scss
+++ b/projects/packages/identity-crisis/src/_inc/admin-bar.scss
@@ -1,28 +1,30 @@
-#wp-admin-bar-jetpack-idc.hide {
- display: none;
-}
+#wpadminbar #wp-admin-bar-jetpack-idc {
+ margin-right: 5px;
-#wp-admin-bar-jetpack-idc .jp-idc-admin-bar {
- background: #fff;
- border-radius: 2px;
- color: #1d2327;
- padding: 4px 8px;
- font-size: 12px;
-}
+ .jp-idc-admin-bar {
+ border-radius: 2px;
+ font-weight: 500;
+ font-size: 14px;
+ line-height: 20px;
+ color: #EFEFF0;
+ padding: 6px 8px;
+ }
-#wpadminbar #wp-admin-bar-jetpack-idc .dashicons {
- color: #1d2327;
- font-family: 'dashicons';
-}
+ &.hide {
+ display: none;
+ }
-#wpadminbar #wp-admin-bar-jetpack-idc .dashicons:before {
- font-size: 16px;
-}
+ .dashicons {
+ font-family: 'dashicons';
+ margin-top: -6px;
-#wpadminbar #wp-admin-bar-jetpack-idc:hover .ab-item {
- background: inherit;
-}
+ &:before {
+ font-size: 18px;
+ }
+ }
-#wpadminbar #wp-admin-bar-jetpack-idc:hover .jp-idc-admin-bar {
- background: #f0f0f1;
+ .ab-item {
+ padding: 0;
+ background: #E68B28;
+ }
}
diff --git a/projects/packages/identity-crisis/src/class-identity-crisis.php b/projects/packages/identity-crisis/src/class-identity-crisis.php
index 2fb43e29f54e7..8c84da9858d3b 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.6.4-alpha';
+ const PACKAGE_VERSION = '0.7.2-alpha';
/**
* Instance of the object.
@@ -247,10 +247,15 @@ public function display_admin_bar_button() {
$href = wp_nonce_url( $href, 'jetpack_idc_clear_confirmation' );
+ $consumer_data = UI::get_consumer_data();
+ $label = isset( $consumer_data['customContent']['adminBarSafeModeLabel'] )
+ ? esc_html( $consumer_data['customContent']['adminBarSafeModeLabel'] )
+ : esc_html__( 'Jetpack Safe Mode', 'jetpack-idc' );
+
$title = sprintf(
'%s %s ',
- ' ',
- esc_html__( 'Jetpack Safe Mode', 'jetpack-idc' )
+ ' ',
+ $label
);
$menu = array(
diff --git a/projects/packages/identity-crisis/src/class-ui.php b/projects/packages/identity-crisis/src/class-ui.php
index b33c573e94538..becc04c830c58 100644
--- a/projects/packages/identity-crisis/src/class-ui.php
+++ b/projects/packages/identity-crisis/src/class-ui.php
@@ -17,6 +17,13 @@
*/
class UI {
+ /**
+ * Temporary storage for consumer data.
+ *
+ * @var array
+ */
+ private static $consumers;
+
/**
* Initialization.
*/
@@ -114,7 +121,11 @@ private static function get_initial_state_data() {
*
* @return array
*/
- private static function get_consumer_data() {
+ public static function get_consumer_data() {
+ if ( null !== static::$consumers ) {
+ return static::$consumers;
+ }
+
$consumers = apply_filters( 'jetpack_idc_consumers', array() );
if ( ! $consumers ) {
@@ -145,7 +156,9 @@ function ( $c1, $c2 ) {
}
}
- return $consumer_chosen ? $consumer_chosen : array_shift( $consumers );
+ static::$consumers = $consumer_chosen ? $consumer_chosen : array_shift( $consumers );
+
+ return static::$consumers;
}
}
diff --git a/projects/packages/jitm/.gitattributes b/projects/packages/jitm/.gitattributes
index b25e37c5f25fc..cc749a05d3703 100644
--- a/projects/packages/jitm/.gitattributes
+++ b/projects/packages/jitm/.gitattributes
@@ -12,4 +12,5 @@ webpack.config.js export-ignore
.gitignore production-exclude
.phpcs.dir.xml production-exclude
phpunit.xml.dist production-exclude
+.eslintignore production-exclude
tests/** production-exclude
diff --git a/projects/packages/jitm/CHANGELOG.md b/projects/packages/jitm/CHANGELOG.md
index 3735344b8caf2..b1ddb362b2772 100644
--- a/projects/packages/jitm/CHANGELOG.md
+++ b/projects/packages/jitm/CHANGELOG.md
@@ -5,6 +5,15 @@ 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).
+## [2.2.4] - 2022-02-02
+### Changed
+- Build: remove unneeded files from production build.
+- Update use of old jQuery interfaces
+
+## [2.2.3] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [2.2.2] - 2022-01-18
### Changed
- General: update required node version to v16.13.2
@@ -369,6 +378,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update Jetpack to use new JITM package
+[2.2.4]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.3...v2.2.4
+[2.2.3]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.2...v2.2.3
[2.2.2]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.1...v2.2.2
[2.2.1]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.0...v2.2.1
[2.2.0]: https://github.com/Automattic/jetpack-jitm/compare/v2.1.1...v2.2.0
diff --git a/projects/packages/jitm/changelog/add-fancy-eslint-ignore b/projects/packages/jitm/changelog/add-fancy-eslint-ignore
deleted file mode 100644
index afe95e481cf32..0000000000000
--- a/projects/packages/jitm/changelog/add-fancy-eslint-ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Hack eslint config to use `.gitignore` and per-dir `.eslintignore`.
-
-
diff --git a/projects/packages/jitm/changelog/add-visitor-status b/projects/packages/jitm/changelog/add-visitor-status
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/packages/jitm/changelog/add-visitor-status
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/js-packages/storybook/changelog/2021-12-07-15-51-02-511843 b/projects/packages/jitm/changelog/renovate-wordpress-monorepo
similarity index 100%
rename from projects/js-packages/storybook/changelog/2021-12-07-15-51-02-511843
rename to projects/packages/jitm/changelog/renovate-wordpress-monorepo
diff --git a/projects/packages/jitm/changelog/update-project-scripts-no-install b/projects/packages/jitm/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/jitm/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/jitm/composer.json b/projects/packages/jitm/composer.json
index 52ec7017538c4..13566c2fe2592 100644
--- a/projects/packages/jitm/composer.json
+++ b/projects/packages/jitm/composer.json
@@ -5,7 +5,7 @@
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-a8c-mc-stats": "^1.4",
- "automattic/jetpack-assets": "^1.16",
+ "automattic/jetpack-assets": "^1.17",
"automattic/jetpack-connection": "^1.36",
"automattic/jetpack-device-detection": "^1.4",
"automattic/jetpack-logo": "^1.5",
diff --git a/projects/packages/jitm/package.json b/projects/packages/jitm/package.json
index dfda33bce08a2..a4cbb96c680c3 100644
--- a/projects/packages/jitm/package.json
+++ b/projects/packages/jitm/package.json
@@ -22,7 +22,7 @@
},
"browserslist": "extends @wordpress/browserslist-config",
"devDependencies": {
- "@automattic/jetpack-webpack-config": "workspace:^1.0.3-alpha",
+ "@automattic/jetpack-webpack-config": "workspace:^1.1.2-alpha",
"@wordpress/browserslist-config": "4.1.0",
"sass": "1.43.3",
"sass-loader": "12.4.0",
diff --git a/projects/packages/jitm/src/class-jitm.php b/projects/packages/jitm/src/class-jitm.php
index 786843b77e657..6150e400cc665 100644
--- a/projects/packages/jitm/src/class-jitm.php
+++ b/projects/packages/jitm/src/class-jitm.php
@@ -20,7 +20,7 @@
*/
class JITM {
- const PACKAGE_VERSION = '2.2.3-alpha';
+ const PACKAGE_VERSION = '2.2.5-alpha';
/**
* The configuration method that is called from the jetpack-config package.
diff --git a/projects/packages/jitm/src/js/jetpack-jitm.js b/projects/packages/jitm/src/js/jetpack-jitm.js
index 785a38a73cd1f..61c5837aea54e 100644
--- a/projects/packages/jitm/src/js/jetpack-jitm.js
+++ b/projects/packages/jitm/src/js/jetpack-jitm.js
@@ -73,7 +73,9 @@ jQuery( document ).ready( function ( $ ) {
html += '';
html +=
'
+
+
+
+
+ ) : null;
+
+ return (
+
+
{ headerNav }
+
+
{ children }
+
+
+
+
+
+ );
+}
+
/**
* The initial renderer function.
*/
function render() {
const container = document.getElementById( 'my-jetpack-container' );
-
if ( null === container ) {
return;
}
- ReactDOM.render( , container );
+ ReactDOM.render(
+
+
+ } />
+ } /> }
+ />
+ } /> }
+ />
+ } /> }
+ />
+
+ ,
+ container
+ );
}
render();
diff --git a/projects/packages/my-jetpack/_inc/components/connection-screen/connect.png b/projects/packages/my-jetpack/_inc/components/connection-screen/connect.png
new file mode 100644
index 0000000000000..66f8710ac0634
Binary files /dev/null and b/projects/packages/my-jetpack/_inc/components/connection-screen/connect.png differ
diff --git a/projects/packages/my-jetpack/_inc/components/connection-screen/index.jsx b/projects/packages/my-jetpack/_inc/components/connection-screen/index.jsx
new file mode 100644
index 0000000000000..1976d0cf4235e
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/components/connection-screen/index.jsx
@@ -0,0 +1,49 @@
+/**
+ * External dependencies
+ */
+import React from 'react';
+import { __ } from '@wordpress/i18n';
+import { ConnectScreen } from '@automattic/jetpack-connection';
+import { Container, Col } from '@automattic/jetpack-components';
+
+/**
+ * Internal dependencies
+ */
+import useMyJetpackConnection from '../../hooks/use-my-jetpack-connection';
+import connectImage from './connect.png';
+
+const ConnectionScreen = () => {
+ const { apiRoot, apiNonce } = useMyJetpackConnection();
+ return (
+
+
+
+
+ { __( 'Receive instant downtime alerts', 'jetpack-my-jetpack' ) }
+
+ { __( 'Automatically share your content on social media', 'jetpack-my-jetpack' ) }
+
+ { __( 'Let your subscribers know when you post', 'jetpack-my-jetpack' ) }
+
+ { __( 'Receive notifications about new likes and comments', 'jetpack-my-jetpack' ) }
+
+
+ { __( 'Let visitors share your content on social media', 'jetpack-my-jetpack' ) }
+
+
+
+
+
+ );
+};
+
+export default ConnectionScreen;
diff --git a/projects/packages/my-jetpack/_inc/components/connection-screen/styles.module.scss b/projects/packages/my-jetpack/_inc/components/connection-screen/styles.module.scss
new file mode 100644
index 0000000000000..0a2c9c9ef8b6c
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/components/connection-screen/styles.module.scss
@@ -0,0 +1,14 @@
+.icon {
+ width: 16px;
+ margin-right: 4px;
+}
+
+.link {
+ --gray-70: #3C434A;
+
+ font-size: 14px;
+ color: var(--gray-70);
+ display: flex;
+ text-decoration: none;
+ align-items: center;
+}
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 c4c82e6d6c3f4..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,9 +1,9 @@
-/* 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.
@@ -11,15 +11,8 @@ import { ConnectionStatusCard } from '@automattic/jetpack-connection';
* @returns {object} ConnectionsSection React component.
*/
export default function ConnectionsSection() {
- const redirectAfterDisconnect = useCallback( () => {
- window.location = myJetpackInitialState.topJetpackMenuItemUrl;
- }, [] );
+ const { apiRoot, apiNonce, redirectUrl } = useMyJetpackConnection();
return (
-
+
);
}
diff --git a/projects/packages/my-jetpack/_inc/components/go-back-link/index.js b/projects/packages/my-jetpack/_inc/components/go-back-link/index.js
new file mode 100644
index 0000000000000..596375dd7fcf1
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/components/go-back-link/index.js
@@ -0,0 +1,26 @@
+/**
+ * External dependencies
+ */
+import React from 'react';
+import { Link } from 'react-router-dom';
+import { Icon, arrowLeft } from '@wordpress/icons';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import styles from './styles.module.scss';
+
+/**
+ * Simple component that renders a go back link
+ *
+ * @returns {object} GoBackLink component.
+ */
+export default function GoBackLink() {
+ return (
+
+
+ { __( 'Go back', 'jetpack-my-jetpack' ) }
+
+ );
+}
diff --git a/projects/packages/my-jetpack/_inc/components/go-back-link/stories/index.jsx b/projects/packages/my-jetpack/_inc/components/go-back-link/stories/index.jsx
new file mode 100644
index 0000000000000..50a9885c8619d
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/components/go-back-link/stories/index.jsx
@@ -0,0 +1,29 @@
+/**
+ * External dependencies
+ */
+import React from 'react';
+import { HashRouter, Routes, Route } from 'react-router-dom';
+
+/**
+ * Internal dependencies
+ */
+import GoBackLink from '..';
+
+export default {
+ title: 'Packages/My Jetpack/GoBackLink',
+ component: GoBackLink,
+};
+
+const DefaultArgs = {};
+
+const Template = args => (
+
+
+ } />
+
+
+);
+
+export const _default = Template.bind( {} );
+_default.parameters = {};
+_default.args = DefaultArgs;
diff --git a/projects/packages/my-jetpack/_inc/components/go-back-link/styles.module.scss b/projects/packages/my-jetpack/_inc/components/go-back-link/styles.module.scss
new file mode 100644
index 0000000000000..0a2c9c9ef8b6c
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/components/go-back-link/styles.module.scss
@@ -0,0 +1,14 @@
+.icon {
+ width: 16px;
+ margin-right: 4px;
+}
+
+.link {
+ --gray-70: #3C434A;
+
+ font-size: 14px;
+ color: var(--gray-70);
+ display: flex;
+ text-decoration: none;
+ align-items: center;
+}
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 9de47a85bb346..0e4c3577d96d9 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
@@ -3,22 +3,44 @@
*/
import React, { useEffect } from 'react';
import { __ } from '@wordpress/i18n';
+import { Notice } from '@wordpress/components';
+import { Icon, warning, info } from '@wordpress/icons';
import {
AdminSection,
AdminSectionHero,
AdminPage,
- Row,
+ Container,
Col,
} from '@automattic/jetpack-components';
/**
* Internal dependencies
*/
-import './style.scss';
import ConnectionsSection from '../connections-section';
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 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`
+ */
+ const iconMap = {
+ error: warning,
+ info,
+ };
+
+ return (
+
+ { iconMap?.[ options.status ] && }
+ { message }
+
+ );
+};
/**
* The My Jetpack App Main Screen.
@@ -26,40 +48,57 @@ import useAnalytics from '../../hooks/use-analytics';
* @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 f5b473ebb435f..0000000000000
--- a/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/style.scss
+++ /dev/null
@@ -1 +0,0 @@
-@import '@automattic/jetpack-base-styles/style';
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..26e050d25f7cc
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/styles.module.scss
@@ -0,0 +1,34 @@
+@import '@automattic/jetpack-base-styles/style';
+
+.heading {
+ font-size: 36px;
+ line-height: 40px;
+ margin: 0;
+}
+
+.notice {
+ margin: 0;
+ font-size: 16px;
+
+ &:global(.is-error) {
+ background-color: var(--jp-red-0);
+ }
+
+ & > *:first-child {
+ display: flex;
+ align-items: center;
+ margin: 0;
+ padding: 12px 4px;
+ }
+
+ & :global(.is-link) {
+ color: var( --jp-black );
+ font-size: 16px;
+ font-weight: 600;
+ }
+}
+
+.message {
+ margin-left: 8px;
+}
+
diff --git a/projects/packages/my-jetpack/_inc/components/plans-section/stories/index.jsx b/projects/packages/my-jetpack/_inc/components/plans-section/stories/index.jsx
index 9f59635adbc3f..15ea531f27c79 100644
--- a/projects/packages/my-jetpack/_inc/components/plans-section/stories/index.jsx
+++ b/projects/packages/my-jetpack/_inc/components/plans-section/stories/index.jsx
@@ -12,7 +12,7 @@ import { purchasesList, siteWithSecurityPlanResponseBody } from './mock-data';
import PlansSection from '../index.jsx';
export default {
- title: 'My Jetpack/Plans Section',
+ title: 'Packages/My Jetpack/Plans Section',
component: PlansSection,
decorators: [ withMock ],
argTypes: {
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 9be5bab09971a..dce96000c9805 100644
--- a/projects/packages/my-jetpack/_inc/components/product-card/index.jsx
+++ b/projects/packages/my-jetpack/_inc/components/product-card/index.jsx
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import React from 'react';
+import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { __, sprintf } from '@wordpress/i18n';
@@ -11,17 +11,20 @@ import { ButtonGroup, Button, DropdownMenu } from '@wordpress/components';
* Internal dependencies
*/
import styles from './style.module.scss';
+import useAnalytics from '../../hooks/use-analytics';
export const PRODUCT_STATUSES = {
ACTIVE: 'active',
INACTIVE: 'inactive',
ERROR: 'error',
- ABSENT: 'absent',
+ ABSENT: 'plugin_absent',
+ NEEDS_PURCHASE: 'needs_purchase',
};
const PRODUCT_STATUSES_LABELS = {
[ PRODUCT_STATUSES.ACTIVE ]: __( 'Active', 'jetpack-my-jetpack' ),
[ PRODUCT_STATUSES.INACTIVE ]: __( 'Inactive', 'jetpack-my-jetpack' ),
+ [ PRODUCT_STATUSES.NEEDS_PURCHASE ]: __( 'Inactive', 'jetpack-my-jetpack' ),
[ PRODUCT_STATUSES.ERROR ]: __( 'Error', 'jetpack-my-jetpack' ),
};
@@ -41,10 +44,11 @@ const renderActionButton = ( {
admin,
name,
onLearn,
+ onActivate,
onAdd,
onManage,
onFixConnection,
- onActivate,
+ isFetching,
} ) => {
if ( ! admin ) {
return (
@@ -57,7 +61,13 @@ const renderActionButton = ( {
);
}
+ const buttonState = {
+ isPressed: ! isFetching,
+ disabled: isFetching,
+ };
+
switch ( status ) {
+ case PRODUCT_STATUSES.NEEDS_PURCHASE:
case PRODUCT_STATUSES.ABSENT:
return (
@@ -69,19 +79,19 @@ const renderActionButton = ( {
);
case PRODUCT_STATUSES.ACTIVE:
return (
-
+
{ __( 'Manage', 'jetpack-my-jetpack' ) }
);
case PRODUCT_STATUSES.ERROR:
return (
-
+
{ __( 'Fix connection', 'jetpack-my-jetpack' ) }
);
case PRODUCT_STATUSES.INACTIVE:
return (
-
+
{ __( 'Activate', 'jetpack-my-jetpack' ) }
);
@@ -89,24 +99,71 @@ const renderActionButton = ( {
};
const ProductCard = props => {
- const { name, admin, description, icon, status, onDeactivate } = props;
+ const {
+ name,
+ admin,
+ description,
+ icon,
+ status,
+ onActivate,
+ onAdd,
+ onDeactivate,
+ isFetching,
+ } = props;
const isActive = status === PRODUCT_STATUSES.ACTIVE;
const isError = status === PRODUCT_STATUSES.ERROR;
const isInactive = status === PRODUCT_STATUSES.INACTIVE;
- const isAbsent = status === PRODUCT_STATUSES.ABSENT;
+ const isAbsent = status === PRODUCT_STATUSES.ABSENT || status === PRODUCT_STATUSES.NEEDS_PURCHASE;
+ const isPurchaseRequired = status === PRODUCT_STATUSES.NEEDS_PURCHASE;
const flagLabel = PRODUCT_STATUSES_LABELS[ status ];
const canDeactivate = ( isActive || isError ) && admin;
const containerClassName = classNames( styles.container, {
- [ styles.absent ]: isAbsent,
+ [ styles.plugin_absent ]: isAbsent,
+ [ styles[ 'is-purchase-required' ] ]: isPurchaseRequired,
} );
const statusClassName = classNames( styles.status, {
[ styles.active ]: isActive,
[ styles.inactive ]: isInactive,
[ styles.error ]: isError,
+ [ styles[ 'is-fetching' ] ]: isFetching,
} );
+ const {
+ tracks: { recordEvent },
+ } = useAnalytics();
+
+ /**
+ * Calls the passed function onDeactivate after firing Tracks event
+ */
+ const deactivateHandler = useCallback( () => {
+ recordEvent( 'jetpack_myjetpack_product_card_deactivate_click', {
+ product: name,
+ } );
+ onDeactivate();
+ }, [ name, onDeactivate, recordEvent ] );
+
+ /**
+ * Calls the passed function onActivate after firing Tracks event
+ */
+ const activateHandler = useCallback( () => {
+ recordEvent( 'jetpack_myjetpack_product_card_activate_click', {
+ product: name,
+ } );
+ onActivate();
+ }, [ name, onActivate, recordEvent ] );
+
+ /**
+ * Calls the passed function onAdd after firing Tracks event
+ */
+ const addHandler = useCallback( () => {
+ recordEvent( 'jetpack_myjetpack_product_card_add_click', {
+ product: name,
+ } );
+ onAdd();
+ }, [ name, onAdd, recordEvent ] );
+
return (
@@ -116,24 +173,25 @@ const ProductCard = props => {
{ description }
{ canDeactivate ? (
-
- { renderActionButton( props ) }
+
+ { renderActionButton( { ...props, onActivate: activateHandler } ) }
) : (
- renderActionButton( props )
+ renderActionButton( { ...props, onActivate: activateHandler, onAdd: addHandler } )
) }
{ ! isAbsent && { flagLabel }
}
@@ -146,6 +204,7 @@ ProductCard.propTypes = {
description: PropTypes.string.isRequired,
icon: PropTypes.element,
admin: PropTypes.bool.isRequired,
+ isFetching: PropTypes.bool,
onDeactivate: PropTypes.func,
onManage: PropTypes.func,
onFixConnection: PropTypes.func,
@@ -157,11 +216,13 @@ ProductCard.propTypes = {
PRODUCT_STATUSES.INACTIVE,
PRODUCT_STATUSES.ERROR,
PRODUCT_STATUSES.ABSENT,
+ PRODUCT_STATUSES.NEEDS_PURCHASE,
] ).isRequired,
};
ProductCard.defaultProps = {
icon: null,
+ isFetching: false,
onDeactivate: () => {},
onManage: () => {},
onFixConnection: () => {},
diff --git a/projects/packages/my-jetpack/_inc/components/product-card/stories/index.jsx b/projects/packages/my-jetpack/_inc/components/product-card/stories/index.jsx
index b840c1689db30..2b8d3ab5a8031 100644
--- a/projects/packages/my-jetpack/_inc/components/product-card/stories/index.jsx
+++ b/projects/packages/my-jetpack/_inc/components/product-card/stories/index.jsx
@@ -1,4 +1,5 @@
/* eslint-disable react/react-in-jsx-scope */
+
/**
* External dependencies
*/
@@ -8,9 +9,15 @@ import React from 'react';
* Internal dependencies
*/
import ProductCard, { PRODUCT_STATUSES } from '../index.jsx';
+import { initStore } from '../../../state/store';
+
+// Set myJetpackRest global var.
+window.myJetpackRest = {};
+
+initStore();
export default {
- title: 'My Jetpack/Product Card',
+ title: 'Packages/My Jetpack/Product Card',
component: ProductCard,
parameters: {
actions: { argTypesRegex: '^on.*' },
diff --git a/projects/packages/my-jetpack/_inc/components/product-card/style.module.scss b/projects/packages/my-jetpack/_inc/components/product-card/style.module.scss
index b70e31b52b9d1..a590af4ab23c2 100644
--- a/projects/packages/my-jetpack/_inc/components/product-card/style.module.scss
+++ b/projects/packages/my-jetpack/_inc/components/product-card/style.module.scss
@@ -9,11 +9,12 @@
--product-card-description-font-size: 14px;
--product-card-actions-font-size: 12px;
--product-card-margin-base: 4px;
+ --product-card-actions-size: 36px;
--status-size: 8px;
--status-active: #008710;
--status-inactive: #646970;
--status-error: #B32D2E;
- --status-absent: #C3C4C7;
+ --status-plugin_absent: #C3C4C7;
padding: 24px;
background: var( --jp-white );
@@ -24,10 +25,11 @@
flex-direction: column;
justify-content: space-between;
- &.absent {
+ &.is-purchase-required,
+ &.plugin_absent {
background: none;
box-shadow: none;
- box-shadow: 0 0 0 1px var( --status-absent ) inset;
+ box-shadow: 0 0 0 1px var( --status-plugin_absent ) inset;
}
}
@@ -51,15 +53,23 @@
align-items: center;
justify-content: space-between;
width: 100%;
- height: 36px;
+ min-height: var(--product-card-actions-size);
margin-top: calc(var(--product-card-margin-base) * 4); // 16px
font-size: var(--product-card-actions-font-size);
font-weight: 600;
+ flex-wrap: wrap;
+}
+
+.group {
+ display: flex;
+ height: var(--product-card-actions-size);
}
.status {
margin-left: var(--product-card-margin-base);
white-space: nowrap;
+ height: var(--product-card-actions-size);
+ line-height: var(--product-card-actions-size);
&:before {
content: "";
@@ -70,7 +80,7 @@
border-radius: 50%;
}
- // in absent case, there's not status flag
+ // in plugin absent case, there's not status flag
$statuses: active, inactive, error;
@each $status in $statuses {
@@ -83,5 +93,23 @@
}
}
}
+
+ &.is-fetching {
+ &:before {
+ animation: blink-animation 0.5s linear infinite;
+ }
+ }
+}
+
+@keyframes blink-animation{
+ 0%{
+ opacity: 0;
+ }
+ 50%{
+ opacity: 0.5;
+ }
+ 100%{
+ opacity: 0;
+ }
}
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/anti-spam-card.jsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/anti-spam-card.jsx
index 5384dd4398f1f..8da3fcbc77783 100644
--- a/projects/packages/my-jetpack/_inc/components/product-cards-section/anti-spam-card.jsx
+++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/anti-spam-card.jsx
@@ -7,9 +7,10 @@ import PropTypes from 'prop-types';
/**
* Internal dependencies
*/
-import ProductCard, { PRODUCT_STATUSES } from '../product-card';
+import ProductCard from '../product-card';
+import { useProduct } from '../../hooks/use-product';
-const AntiSpamIcon = () => (
+export const AntiSpamIcon = () => (
(
);
const AntiSpamCard = ( { admin } ) => {
- // @todo: implement action handlers
+ const { status, activate, deactivate, detail, isFetching } = useProduct( 'anti-spam' );
+ const { name, description } = detail;
+
return (
}
admin={ admin }
+ isFetching={ isFetching }
+ onDeactivate={ deactivate }
+ onActivate={ activate }
/>
);
};
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/backup-card.jsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/backup-card.jsx
index e2e110085d450..2f78bdbb726ed 100644
--- a/projects/packages/my-jetpack/_inc/components/product-cards-section/backup-card.jsx
+++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/backup-card.jsx
@@ -10,7 +10,7 @@ import PropTypes from 'prop-types';
import ProductCard from '../product-card';
import { useProduct } from '../../hooks/use-product';
-const BackupIcon = () => (
+export const BackupIcon = () => (
(
);
const BackupCard = ( { admin } ) => {
- const { status, activate, deactivate, detail } = useProduct( 'backup' );
+ const { status, activate, deactivate, detail, isFetching } = useProduct( 'backup' );
const { name, description } = detail;
return (
@@ -31,6 +31,7 @@ const BackupCard = ( { admin } ) => {
description={ description }
status={ status }
icon={ }
+ isFetching={ isFetching }
admin={ admin }
onDeactivate={ deactivate }
onActivate={ activate }
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/boost-card.jsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/boost-card.jsx
index 149b5ac4bf97c..ae84a80f474e3 100644
--- a/projects/packages/my-jetpack/_inc/components/product-cards-section/boost-card.jsx
+++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/boost-card.jsx
@@ -1,29 +1,42 @@
/**
* External dependencies
*/
-import React from 'react';
+import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
+import { useNavigate } from 'react-router-dom';
/**
* Internal dependencies
*/
-import ProductCard, { PRODUCT_STATUSES } from '../product-card';
+import ProductCard from '../product-card';
+import { useProduct } from '../../hooks/use-product';
-const BoostIcon = () => (
+export const BoostIcon = () => (
);
const BoostCard = ( { admin } ) => {
- // @todo: implement action handlers
+ const { status, activate, deactivate, detail, isFetching } = useProduct( 'boost' );
+ const { name, description } = detail;
+ const navigate = useNavigate();
+
+ const onAddHandler = useCallback( () => {
+ navigate( '/add-boost' );
+ }, [ navigate ] );
+
return (
}
admin={ admin }
+ isFetching={ isFetching }
+ onDeactivate={ deactivate }
+ onActivate={ activate }
+ onAdd={ onAddHandler }
/>
);
};
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/crm-card.jsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/crm-card.jsx
index 84011e5362ad3..7bb1055a0ce8a 100644
--- a/projects/packages/my-jetpack/_inc/components/product-cards-section/crm-card.jsx
+++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/crm-card.jsx
@@ -7,9 +7,10 @@ import PropTypes from 'prop-types';
/**
* Internal dependencies
*/
-import ProductCard, { PRODUCT_STATUSES } from '../product-card';
+import ProductCard from '../product-card';
+import { useProduct } from '../../hooks/use-product';
-const CrmIcon = () => (
+export const CrmIcon = () => (
(
);
const CrmCard = ( { admin } ) => {
- // @todo: implement action handlers
+ const { status, activate, deactivate, detail, isFetching } = useProduct( 'crm' );
+ const { name, description } = detail;
+
return (
}
+ isFetching={ isFetching }
admin={ admin }
+ onDeactivate={ deactivate }
+ onActivate={ activate }
/>
);
};
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/extras-card.jsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/extras-card.jsx
index ea738502bb7de..fc499d46a8732 100644
--- a/projects/packages/my-jetpack/_inc/components/product-cards-section/extras-card.jsx
+++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/extras-card.jsx
@@ -7,9 +7,10 @@ import PropTypes from 'prop-types';
/**
* Internal dependencies
*/
-import ProductCard, { PRODUCT_STATUSES } from '../product-card';
+import ProductCard from '../product-card';
+import { useProduct } from '../../hooks/use-product';
-const ExtrasIcon = () => (
+export const ExtrasIcon = () => (
(
);
const ExtrasCard = ( { admin } ) => {
- // @todo: implement action handlers
+ const { status, activate, deactivate, detail, isFetching } = useProduct( 'extras' );
+ const { name, description } = detail;
+
return (
}
admin={ admin }
+ isFetching={ isFetching }
+ onDeactivate={ deactivate }
+ onActivate={ activate }
/>
);
};
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/index.jsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/index.jsx
index e94f4b3f51749..2f70eed8d4645 100644
--- a/projects/packages/my-jetpack/_inc/components/product-cards-section/index.jsx
+++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/index.jsx
@@ -2,7 +2,7 @@
* External dependencies
*/
import React from 'react';
-import { Container, Row, Col } from '@automattic/jetpack-components';
+import { Container, Col } from '@automattic/jetpack-components';
/**
* Internal dependencies
@@ -23,33 +23,31 @@ import ExtrasCard from './extras-card';
*/
const ProductCardsSection = () => {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/scan-card.jsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/scan-card.jsx
index 6ddc3b7bd8ce6..e96f97fd41c43 100644
--- a/projects/packages/my-jetpack/_inc/components/product-cards-section/scan-card.jsx
+++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/scan-card.jsx
@@ -7,9 +7,10 @@ import PropTypes from 'prop-types';
/**
* Internal dependencies
*/
-import ProductCard, { PRODUCT_STATUSES } from '../product-card';
+import ProductCard from '../product-card';
+import { useProduct } from '../../hooks/use-product';
-const ScanIcon = () => (
+export const ScanIcon = () => (
(
);
const ScanCard = ( { admin } ) => {
- // @todo: implement action handlers
+ const { status, activate, deactivate, detail, isFetching } = useProduct( 'scan' );
+ const { name, description } = detail;
+
return (
}
admin={ admin }
+ isFetching={ isFetching }
+ onDeactivate={ deactivate }
+ onActivate={ activate }
/>
);
};
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/search-card.jsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/search-card.jsx
index afecdb5daceaa..ac424d1147cf4 100644
--- a/projects/packages/my-jetpack/_inc/components/product-cards-section/search-card.jsx
+++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/search-card.jsx
@@ -1,15 +1,17 @@
/**
* External dependencies
*/
-import React from 'react';
+import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
+import { useNavigate } from 'react-router-dom';
/**
* Internal dependencies
*/
-import ProductCard, { PRODUCT_STATUSES } from '../product-card';
+import ProductCard from '../product-card';
+import { useProduct } from '../../hooks/use-product';
-const SearchIcon = () => (
+export const SearchIcon = () => (
@@ -17,14 +19,23 @@ const SearchIcon = () => (
);
const SearchCard = ( { admin } ) => {
- // @todo: implement action handlers
+ const { status, activate, deactivate, detail, isFetching } = useProduct( 'search' );
+ const { name, description } = detail;
+
+ const navigate = useNavigate();
+ const onAddHandler = useCallback( () => navigate( '/add-search' ), [ navigate ] );
+
return (
}
admin={ admin }
+ isFetching={ isFetching }
+ onDeactivate={ deactivate }
+ onActivate={ activate }
+ onAdd={ onAddHandler }
/>
);
};
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/stories/index.jsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/stories/index.jsx
index 1fe6bd32bdaf3..d95bcf2b5fa2c 100644
--- a/projects/packages/my-jetpack/_inc/components/product-cards-section/stories/index.jsx
+++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/stories/index.jsx
@@ -9,7 +9,7 @@ import React from 'react';
import ProductCardsSection from '../index.jsx';
export default {
- title: 'My Jetpack/Product Cards Section',
+ title: 'Packages/My Jetpack/Product Cards Section',
component: ProductCardsSection,
parameters: {
actions: { argTypesRegex: '^on.*' },
diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/videopress-card.jsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/videopress-card.jsx
index 7e23886d86e2b..d125b7db73e25 100644
--- a/projects/packages/my-jetpack/_inc/components/product-cards-section/videopress-card.jsx
+++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/videopress-card.jsx
@@ -7,9 +7,10 @@ import PropTypes from 'prop-types';
/**
* Internal dependencies
*/
-import ProductCard, { PRODUCT_STATUSES } from '../product-card';
+import ProductCard from '../product-card';
+import { useProduct } from '../../hooks/use-product';
-const VideopressIcon = () => (
+export const VideopressIcon = () => (
(
);
const VideopressCard = ( { admin } ) => {
- // @todo: implement action handlers
+ const { status, activate, deactivate, detail, isFetching } = useProduct( 'videopress' );
+ const { name, description } = detail;
+
return (
}
admin={ admin }
+ isFetching={ isFetching }
+ onDeactivate={ deactivate }
+ onActivate={ activate }
/>
);
};
diff --git a/projects/packages/my-jetpack/_inc/components/product-detail-card/index.jsx b/projects/packages/my-jetpack/_inc/components/product-detail-card/index.jsx
new file mode 100644
index 0000000000000..994acfc46bdb6
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/components/product-detail-card/index.jsx
@@ -0,0 +1,140 @@
+/**
+ * External dependencies
+ */
+import React from 'react';
+import classnames from 'classnames';
+import { Button } from '@wordpress/components';
+import { Icon, check } from '@wordpress/icons';
+import { getCurrencyObject } from '@automattic/format-currency';
+import { __, sprintf } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import { useProduct } from '../../hooks/use-product';
+import { BackupIcon } from '../product-cards-section/backup-card';
+import styles from './style.module.scss';
+import { BoostIcon } from '../product-cards-section/boost-card';
+
+/**
+ * Simple react component to render the product icon,
+ * depending on the product slug.
+ *
+ * @param {string} slug - The product slug.
+ * @returns {object} ProductDetailCard react component.
+ */
+function ProductIcon( { slug } ) {
+ switch ( slug ) {
+ case 'backup':
+ return ;
+
+ case 'boost':
+ return ;
+ default:
+ return null;
+ }
+}
+
+/**
+ * React component to render the price.
+ *
+ * @param {object} props - Component props.
+ * @param {string} props.value - Product price
+ * @param {string} props.currency - Product current code
+ * @param {string} props.isOld - True when the product price is old
+ * @returns {object} Price react component.
+ */
+function Price( { value, currency, isOld } ) {
+ const priceObject = getCurrencyObject( value, currency );
+
+ const classNames = classnames( styles.price, {
+ [ styles[ 'is-old' ] ]: isOld,
+ } );
+
+ return (
+
+ { priceObject.symbol }
+ { priceObject.integer }
+ { priceObject.fraction }
+
+ );
+}
+
+/**
+ * Product Detail component.
+ *
+ * @param {object} props - Component props.
+ * @param {string} props.slug - Product slug
+ * @param {Function} props.trackButtonClick - Function to call for tracking clicks on Call To Action button
+ * @returns {object} ProductDetailCard react component.
+ */
+const ProductDetail = ( { slug, trackButtonClick } ) => {
+ const { detail } = useProduct( slug );
+ const { title, longDescription, features, pricingForUi = {} } = detail;
+ const { isFree, fullPrice, currencyCode } = pricingForUi;
+
+ return (
+
+
+
+
{ title }
+
{ longDescription }
+
+ { features.map( ( feature, id ) => (
+
+
+ { feature }
+
+ ) ) }
+
+
+ { ! isFree && (
+
+
+
+
+ { __( '/month, paid yearly', 'jetpack-my-jetpack' ) }
+
+
+ ) }
+
+ { isFree && (
+
{ __( 'Free', 'jetpack-my-jetpack' ) }
+ ) }
+
+
+ {
+ /* translators: placeholder is product name. */
+ sprintf( __( 'Add %s', 'jetpack-my-jetpack' ), title )
+ }
+
+
+ );
+};
+
+ProductDetail.defaultProps = {
+ trackButtonClick: () => {},
+};
+
+export { ProductDetail };
+
+/**
+ * ProductDetailCard component.
+ *
+ * @param {object} props - Component props.
+ * @param {string} props.slug - Product slug
+ * @returns {object} ProductDetailCard react component.
+ */
+export default function ProductDetailCard( { slug } ) {
+ return (
+
+ );
+}
diff --git a/projects/packages/my-jetpack/_inc/components/product-detail-card/stories/index.jsx b/projects/packages/my-jetpack/_inc/components/product-detail-card/stories/index.jsx
new file mode 100644
index 0000000000000..622933713c258
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/components/product-detail-card/stories/index.jsx
@@ -0,0 +1,74 @@
+/* eslint-disable react/react-in-jsx-scope */
+/**
+ * External dependencies
+ */
+import React from 'react';
+import withMock from 'storybook-addon-mock';
+
+/**
+ * Internal dependencies
+ */
+import ProductDetailCard, { ProductDetail } from '../index.jsx';
+import { backupProductData, boostProductData } from './mock-data.js';
+
+export default {
+ title: 'Packages/My Jetpack/Product Detail Card',
+ component: ProductDetailCard,
+ decorators: [ withMock ],
+};
+
+const DefaultArgs = {};
+
+const DefaultProductDetailCard = args => ;
+
+const mapResponse = {
+ backup: backupProductData,
+ boost: boostProductData,
+};
+
+/**
+ * Helper function that returns the story mock data.
+ *
+ * @param {string} product - Product slug
+ * @returns {Array} Story mock data
+ */
+function getMockData( product ) {
+ return [
+ {
+ url: `my-jetpack/v1/site/products/${ product }?_locale=user`,
+ method: 'GET',
+ status: 200,
+ response: mapResponse[ product ],
+ },
+ ];
+}
+
+export const _default = DefaultProductDetailCard.bind( {} );
+_default.parameters = {
+ mockData: getMockData( 'backup' ),
+};
+_default.args = DefaultArgs;
+
+const BackupTemplate = args => ;
+export const JetpackBackup = BackupTemplate.bind( {} );
+JetpackBackup.parameters = {
+ mockData: getMockData( 'backup' ),
+};
+
+const BoostTemplate = args => ;
+export const BoostBackup = BoostTemplate.bind( {} );
+BoostBackup.parameters = {
+ mockData: getMockData( 'boost' ),
+};
+
+const BackupCardTemplate = args => ;
+export const JetpackBackupCard = BackupCardTemplate.bind( {} );
+JetpackBackupCard.parameters = {
+ mockData: getMockData( 'backup' ),
+};
+
+const BoostCardTemplate = args => ;
+export const BoostBackupCard = BoostCardTemplate.bind( {} );
+BoostBackupCard.parameters = {
+ mockData: getMockData( 'boost' ),
+};
diff --git a/projects/packages/my-jetpack/_inc/components/product-detail-card/stories/mock-data.js b/projects/packages/my-jetpack/_inc/components/product-detail-card/stories/mock-data.js
new file mode 100644
index 0000000000000..906d34440a8d2
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/components/product-detail-card/stories/mock-data.js
@@ -0,0 +1,40 @@
+export const backupProductData = {
+ slug: 'backup',
+ name: 'Backup',
+ title: 'Jepack Backup',
+ description: 'Save every change',
+ long_description:
+ 'Real-time backups save every change and one-click restores get you back online quickly.',
+ status: 'active',
+ features: [
+ 'Real-time cloud backups',
+ '10GB of backup storage',
+ '30-day archive & activity log',
+ 'One-click restores',
+ ],
+ pricingForUi: {
+ available: true,
+ currencyCode: 'EUR',
+ fullPrice: '9',
+ promotionPercentage: '50',
+ },
+};
+
+export const boostProductData = {
+ slug: 'boost',
+ name: 'Boost',
+ title: 'Jepack Boost',
+ description: 'Instant speed and SEO',
+ long_description:
+ 'Jetpack Boost gives your site the same performance advantages as the world’s leading websites, no developer required.',
+ status: 'inactive',
+ features: [
+ 'Check your site performance',
+ 'Enable improvements in one click',
+ 'Standalone free plugin for those focused on speed',
+ ],
+ pricingForUi: {
+ available: true,
+ is_free: true,
+ },
+};
diff --git a/projects/packages/my-jetpack/_inc/components/product-detail-card/style.module.scss b/projects/packages/my-jetpack/_inc/components/product-detail-card/style.module.scss
new file mode 100644
index 0000000000000..b8bc78facfacb
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/components/product-detail-card/style.module.scss
@@ -0,0 +1,152 @@
+.card {
+ // Card main wrapper
+ --product-card-bg-color: white;
+ --product-card-border-color: #d5d5d5;
+ --product-card-shadow: rgb(0 0 0 / 3%);
+ --product-card-border-radius: 4px;
+
+ background-color: var(--product-card-bg-color);
+ border: 1px solid var(--product-card-border-color);
+ border-radius: var(--product-card-border-radius);
+ box-shadow: 0 2px 6px var(--product-card-shadow), 0 1px 2px var(--product-card-shadow);
+ max-width: 384px;
+}
+
+.container {
+ // @todo find a better way to change link color
+ // since this could affect more places
+ --wp-admin-theme-color: var(--jp-black);
+
+ // all css variables used down in product-detail-card
+
+ // Colors
+ --jp-green-primary: #069e08;
+ --jp-green-secondary: #2fb41f;
+ --jp-text-color: ###;
+
+ // Text
+ --product-card-description-line-height: 1.5;
+ --product-card-description-font-size: 16px;
+ --product-card-price-font-size: 4rem;
+
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ max-width: 600px;
+ padding: 60px 40px;
+
+ h3 {
+ font-size: var(--font-title-large);
+ font-weight: 700;
+ margin-top: 48px;
+ line-height: 1.1;
+ color: var( --jp-black );
+ margin: 10px 0;
+ }
+
+ .checkout-button {
+ margin-top: 20px;
+ white-space: pre-line;
+ height: auto;
+ line-height: 26px;
+ }
+
+ // Product Icon
+ & > svg {
+ height: 30px;
+ line-height: 30px;
+ }
+}
+
+.name {
+ line-height: var(--product-card-description-line-height);
+ font-size: var(--product-card-description-font-size);
+ margin: 0 0 22px;
+}
+
+.features {
+ margin: 0 0 30px;
+ padding: 0;
+ li {
+ list-style: none;
+ display: flex;
+ align-items: center;
+ margin-bottom: 8px;
+ font-size: 14px;
+ line-height: 1.4;
+ }
+
+ svg {
+ fill: var(--jp-green-primary);
+ }
+}
+
+.price-container {
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: row;
+ align-items: flex-end;
+ margin-top: auto;
+ color: var(--jp-text-color);
+}
+
+.price {
+ font-size: var(--product-card-price-font-size);
+ display: flex;
+ margin: 0;
+ position: relative;
+
+ &:first-child {
+ margin-right: 10px;
+ }
+
+ .price-symbol {
+ padding-right: 1px;
+ font-size: 0.5em;
+ line-height: 1.4;
+ }
+
+ .price-number {
+ font-size: inherit;
+ font-weight: 700;
+ line-height: 1;
+ }
+
+ .price-fraction {
+ padding-left: 1px;
+ font-size: 0.35em;
+ font-weight: 700;
+ line-height: 1.7;
+ }
+
+ &.is-old {
+ color: #a7aaad;
+ &:after {
+ content: " ";
+ display: block;
+ width: 100%;
+ height: 3px;
+ background: #c9356e;
+ border-radius: 5px;
+ position: absolute;
+ top: 50%;
+ margin-top: -2px;
+ pointer-events: none;
+ }
+ }
+}
+
+.price-description {
+ color: #787c82;
+ letter-spacing: 0.2px;
+ flex-grow: 2;
+}
+
+.checkout-button {
+ align-self: flex-start;
+ font-size: 16px;
+ padding: 8px 60px;
+ text-align: center;;
+ width: 100%;
+ display: inline-block;
+}
diff --git a/projects/packages/my-jetpack/_inc/components/product-interstitial/boost.png b/projects/packages/my-jetpack/_inc/components/product-interstitial/boost.png
new file mode 100644
index 0000000000000..65a8aedd98f67
Binary files /dev/null and b/projects/packages/my-jetpack/_inc/components/product-interstitial/boost.png differ
diff --git a/projects/packages/my-jetpack/_inc/components/product-interstitial/index.jsx b/projects/packages/my-jetpack/_inc/components/product-interstitial/index.jsx
new file mode 100644
index 0000000000000..ad06224fbaa57
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/components/product-interstitial/index.jsx
@@ -0,0 +1,88 @@
+/**
+ * External dependencies
+ */
+import React, { useCallback, useEffect } from 'react';
+import { Container, Col } from '@automattic/jetpack-components';
+
+/**
+ * Internal dependencies
+ */
+import { ProductDetail } from '../product-detail-card';
+import styles from './style.module.scss';
+import useAnalytics from '../../hooks/use-analytics';
+import boostImage from './boost.png';
+import searchImage from './search.png';
+
+/**
+ * Product Interstitial component.
+ *
+ * @param {object} props - Component props.
+ * @param {string} props.slug - Product slug
+ * @param {object} props.children - Product additional content
+ * @returns {object} ProductInterstitial react component.
+ */
+export default function ProductInterstitial( { slug, children = null } ) {
+ const {
+ tracks: { recordEvent },
+ } = useAnalytics();
+
+ useEffect( () => {
+ recordEvent( 'jetpack_myjetpack_product_interstitial_view', { product: slug } );
+ }, [ recordEvent, slug ] );
+
+ const trackProductClick = useCallback( () => {
+ recordEvent( 'jetpack_myjetpack_product_interstitial_add_link_click', { product: slug } );
+ }, [ recordEvent, slug ] );
+
+ return (
+
+
+
+
+
+
+ { children }
+
+
+
+ );
+}
+
+/**
+ * BackupInterstitial component
+ *
+ * @returns {object} BackupInterstitial react component.
+ */
+export function BackupInterstitial() {
+ return (
+
+ @todo Popular upgrade here
+
+ );
+}
+
+/**
+ * SearchInterstitial component
+ *
+ * @returns {object} BoostInterstitial react component.
+ */
+export function BoostInterstitial() {
+ return (
+
+
+
+ );
+}
+
+/**
+ * SearchInterstitial component
+ *
+ * @returns {object} SearchInterstitial react component.
+ */
+export function SearchInterstitial() {
+ return (
+
+
+
+ );
+}
diff --git a/projects/packages/my-jetpack/_inc/components/product-interstitial/search.png b/projects/packages/my-jetpack/_inc/components/product-interstitial/search.png
new file mode 100644
index 0000000000000..4ef9cf13a1091
Binary files /dev/null and b/projects/packages/my-jetpack/_inc/components/product-interstitial/search.png differ
diff --git a/projects/packages/my-jetpack/_inc/components/product-interstitial/stories/index.jsx b/projects/packages/my-jetpack/_inc/components/product-interstitial/stories/index.jsx
new file mode 100644
index 0000000000000..c3a5c7f9a707f
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/components/product-interstitial/stories/index.jsx
@@ -0,0 +1,41 @@
+/* eslint-disable react/react-in-jsx-scope */
+/**
+ * External dependencies
+ */
+import React from 'react';
+import withMock from 'storybook-addon-mock';
+
+/**
+ * Internal dependencies
+ */
+import ProductInterstitial, {
+ BackupInterstitial,
+ BoostInterstitial,
+ SearchInterstitial,
+} from '../index.jsx';
+
+export default {
+ title: 'Packages/My Jetpack/Product Interstitial',
+ component: ProductInterstitial,
+ decorators: [ withMock ],
+};
+
+const DefaultArgs = {};
+
+const DefaultBackupDetailCard = args => ;
+
+export const _default = DefaultBackupDetailCard.bind( {} );
+_default.parameters = {};
+_default.args = DefaultArgs;
+
+const BackupTemplate = args => ;
+export const JetpackBackup = BackupTemplate.bind( {} );
+JetpackBackup.parameters = {};
+
+const BoostTemplate = args => ;
+export const JetpackBoost = BoostTemplate.bind( {} );
+JetpackBoost.parameters = {};
+
+const SearchTemplate = args => ;
+export const JetpackSearch = SearchTemplate.bind( {} );
+JetpackSearch.parameters = {};
diff --git a/projects/packages/my-jetpack/_inc/components/product-interstitial/style.module.scss b/projects/packages/my-jetpack/_inc/components/product-interstitial/style.module.scss
new file mode 100644
index 0000000000000..6cd00d4ba1a03
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/components/product-interstitial/style.module.scss
@@ -0,0 +1,43 @@
+.container {
+ // Card main wrapper
+ --product-card-bg-color: white;
+ --product-card-border-color: #d5d5d5;
+ --product-card-shadow: rgb(0 0 0 / 3%);
+ --product-card-border-radius: 4px;
+
+ background-color: var(--product-card-bg-color);
+ border: 1px solid var(--product-card-border-color);
+ border-radius: var(--product-card-border-radius);
+ box-shadow: 0 2px 6px var(--product-card-shadow), 0 1px 2px var(--product-card-shadow);
+}
+
+.imageContainer {
+ display: flex;
+ align-items: center;
+
+ img {
+ object-fit: cover;
+ width: 100%;
+ }
+}
+
+// Wrapper
+
+@media (max-width: 768px) {
+ .wrapper {
+ margin-right: 12px;
+ }
+}
+
+@mixin container($width, $margin-right) {
+ @media ( min-width: #{$width} ) {
+ margin-right: $margin-right
+ }
+}
+
+.wrapper {
+ --margin-right-compensation-sm: 10px;
+ --margin-right-compensation-md: 20px;
+ @include container( 0px, var(--margin-right-compensation-sm) );
+ @include container( 782px, var(--margin-right-compensation-md) );
+}
\ No newline at end of file
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 7aa0dfec949d8..e5013017ef2ad 100644
--- a/projects/packages/my-jetpack/_inc/hooks/use-analytics/index.js
+++ b/projects/packages/my-jetpack/_inc/hooks/use-analytics/index.js
@@ -1,29 +1,24 @@
-/* global myJetpackInitialState */
/**
* 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 } = myJetpackInitialState;
+ const { isUserConnected, userConnectionData = {} } = useMyJetpackConnection();
+ const { login, ID } = userConnectionData.currentUser?.wpcomUser || {};
- const { isUserConnected, userConnectionData } = useConnection( {
- apiRoot,
- apiNonce,
- } );
-
- const { login, ID } = userConnectionData.currentUser.wpcomUser;
/**
* Initialize tracks with user data.
* Should run when we have a connected user.
*/
useEffect( () => {
- if ( isUserConnected ) {
+ if ( isUserConnected && ID && login ) {
jetpackAnalytics.initialize( ID, login );
}
- } );
+ }, [ ID, isUserConnected, login ] );
+
const {
clearedIdentity,
ga,
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/Readme.md b/projects/packages/my-jetpack/_inc/hooks/use-notice/Readme.md
new file mode 100644
index 0000000000000..57f1a7c9f6b4a
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/hooks/use-notice/Readme.md
@@ -0,0 +1,20 @@
+# useNotice custom hooks
+
+## useNoticeWatcher
+
+## useGlobalNotice
+
+```es6
+import { useGlobalNotice } from './hooks/use-notice';
+
+function PlansSection() {
+ const global = useGlobalNotice();
+ if ( ! global ) {
+ return null;
+ }
+
+ return (
+ { global.message } ;
+ )
+}
+```
diff --git a/projects/packages/my-jetpack/_inc/hooks/use-notice/index.js b/projects/packages/my-jetpack/_inc/hooks/use-notice/index.js
new file mode 100644
index 0000000000000..c6387b7ffdfd4
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/hooks/use-notice/index.js
@@ -0,0 +1,62 @@
+/**
+ * WordPress dependencies
+ */
+import { useEffect } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { useNavigate } from 'react-router-dom';
+
+/**
+ * Internal dependencies
+ */
+import { STORE_ID } from '../../state/store';
+import useMyJetpackConnection from '../use-my-jetpack-connection';
+
+/**
+ * React custom hook to get global notices.
+ *
+ * @returns {object} Global notices data
+ */
+export function useGlobalNotice() {
+ const dispatch = useDispatch();
+
+ const { message, options } = useSelect( select => select( STORE_ID ).getGlobalNotice() );
+ return {
+ message,
+ options: options || {},
+ clean: () => dispatch( STORE_ID ).cleanGlobalNotice(),
+ };
+}
+
+/**
+ * React custom hook to watch global events.
+ * For instance, when the user is not connected,
+ * the hook dispatches an action to populate the global notice.
+ */
+export default function useNoticeWatcher() {
+ const dispatch = useDispatch();
+ const { isUserConnected } = useMyJetpackConnection();
+ const navigate = useNavigate();
+
+ useEffect( () => {
+ if ( ! isUserConnected ) {
+ return dispatch( STORE_ID ).setGlobalNotice(
+ __(
+ 'Jetpack is currently not connected and some products might not work until the connection is reestablished.',
+ 'jetpack-my-jetpack'
+ ),
+ {
+ status: 'error',
+ actions: [
+ {
+ label: __( 'Connect Jetpack now.', 'jetpack-my-jetpack' ),
+ onClick: () => navigate( '/connection' ),
+ variant: 'link',
+ noDefaultClasses: true,
+ },
+ ],
+ }
+ );
+ }
+ }, [ isUserConnected, dispatch, navigate ] );
+}
diff --git a/projects/packages/my-jetpack/_inc/hooks/use-product/Readme.md b/projects/packages/my-jetpack/_inc/hooks/use-product/Readme.md
index e42b63a9ca089..b6b930b72d0c0 100644
--- a/projects/packages/my-jetpack/_inc/hooks/use-product/Readme.md
+++ b/projects/packages/my-jetpack/_inc/hooks/use-product/Readme.md
@@ -23,8 +23,6 @@ An object with details about the product.
Returns the current products list of My Jetpack.
-An array with all products list.
-
### activate( )
A helper function to activate a product.
diff --git a/projects/packages/my-jetpack/_inc/hooks/use-product/index.js b/projects/packages/my-jetpack/_inc/hooks/use-product/index.js
index 766e4491d148b..667604daf06a4 100644
--- a/projects/packages/my-jetpack/_inc/hooks/use-product/index.js
+++ b/projects/packages/my-jetpack/_inc/hooks/use-product/index.js
@@ -7,6 +7,7 @@ import { useSelect, useDispatch } from '@wordpress/data';
* Internal dependencies
*/
import { STORE_ID } from '../../state/store';
+import { mapObjectKeysToCamel } from '../../utils/to-camel';
/**
* React custom hook to deal with a My Jetpack product.
@@ -16,7 +17,14 @@ import { STORE_ID } from '../../state/store';
*/
export function useProduct( productId ) {
const { activateProduct, deactivateProduct } = useDispatch( STORE_ID );
- const detail = useSelect( select => select( STORE_ID ).getProduct( productId ) );
+
+ /*
+ * Re map object keys to camel case.
+ * Consider to improve this in the process.
+ */
+ let detail = useSelect( select => select( STORE_ID ).getProduct( productId ) );
+ detail = mapObjectKeysToCamel( detail, true );
+ detail.pricingForUi = mapObjectKeysToCamel( detail.pricingForUi, true );
return {
activate: () => activateProduct( productId ),
@@ -24,7 +32,7 @@ export function useProduct( productId ) {
productsList: useSelect( select => select( STORE_ID ).getProducts() ),
detail,
isActive: detail.status === 'active',
+ isFetching: useSelect( select => select( STORE_ID ).isFetching( productId ) ),
status: detail.status, // shorthand. Consider to remove.
- isFetching: !! detail.isFetching,
};
}
diff --git a/projects/packages/my-jetpack/_inc/hooks/use-route-blocker/index.js b/projects/packages/my-jetpack/_inc/hooks/use-route-blocker/index.js
new file mode 100644
index 0000000000000..b5e5a9b8deaf7
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/hooks/use-route-blocker/index.js
@@ -0,0 +1,16 @@
+/**
+ * External dependencies
+ */
+import { useEffect } from 'react';
+import { useNavigationType, useNavigate } from 'react-router-dom';
+
+export default () => {
+ const type = useNavigationType();
+ const navigate = useNavigate();
+
+ useEffect( () => {
+ if ( type === 'POP' ) {
+ navigate( '/', { replace: true } );
+ }
+ }, [ type, navigate ] );
+};
diff --git a/projects/packages/my-jetpack/_inc/state/actions.js b/projects/packages/my-jetpack/_inc/state/actions.js
index c01e0676fcb0b..7290195937f4d 100644
--- a/projects/packages/my-jetpack/_inc/state/actions.js
+++ b/projects/packages/my-jetpack/_inc/state/actions.js
@@ -15,12 +15,16 @@ import { REST_API_SITE_PRODUCTS_ENDPOINT } from './constants';
const SET_PURCHASES_IS_FETCHING = 'SET_PURCHASES_IS_FETCHING';
const FETCH_PURCHASES = 'FETCH_PURCHASES';
const SET_PURCHASES = 'SET_PURCHASES';
-const SET_PRODUCT_ACTION_ERROR = 'SET_PRODUCT_ACTION_ERROR';
+const SET_IS_FETCHING_PRODUCT = 'SET_IS_FETCHING_PRODUCT';
+const SET_PRODUCT = 'SET_PRODUCT';
+const SET_PRODUCT_REQUEST_ERROR = 'SET_PRODUCT_REQUEST_ERROR';
const ACTIVATE_PRODUCT = 'ACTIVATE_PRODUCT';
const DEACTIVATE_PRODUCT = 'DEACTIVATE_PRODUCT';
-const SET_FETCHING_PRODUCT_STATUS = 'SET_FETCHING_PRODUCT_STATUS';
const SET_PRODUCT_STATUS = 'SET_PRODUCT_STATUS';
+const SET_GLOBAL_NOTICE = 'SET_GLOBAL_NOTICE';
+const CLEAN_GLOBAL_NOTICE = 'CLEAN_GLOBAL_NOTICE';
+
const setPurchasesIsFetching = isFetching => {
return { type: SET_PURCHASES_IS_FETCHING, isFetching };
};
@@ -33,17 +37,33 @@ const setPurchases = purchases => {
return { type: SET_PURCHASES, purchases };
};
+const setProduct = product => ( { type: SET_PRODUCT, product } );
+
+const setRequestProductError = ( productId, error ) => ( {
+ type: SET_PRODUCT_REQUEST_ERROR,
+ productId,
+ error,
+} );
+
const setProductStatus = ( productId, status ) => {
return { type: SET_PRODUCT_STATUS, productId, status };
};
-const setIsFetchingProductStatus = ( productId, isFetching ) => {
- return { type: SET_FETCHING_PRODUCT_STATUS, productId, isFetching };
-};
-
-const setProductActionError = error => {
- return { type: SET_PRODUCT_ACTION_ERROR, error };
-};
+/**
+ * Action that set the `isFetching` state of the product,
+ * when the client hits the server.
+ *
+ * @param {string} productId - My Jetpack product ID.
+ * @param {boolean} isFetching - True if the product is being fetched. Otherwise, False.
+ * @returns {object} - Redux action.
+ */
+function setIsFetchingProduct( productId, isFetching ) {
+ return {
+ type: SET_IS_FETCHING_PRODUCT,
+ productId,
+ isFetching,
+ };
+}
/**
* Side effect action which will sync
@@ -61,31 +81,34 @@ function requestProductStatus( productId, data, { select, dispatch } ) {
// Check valid product.
const isValid = select.isValidProduct( productId );
if ( ! isValid ) {
+ dispatch( setProductStatus( productId, 'error' ) );
return dispatch(
- setProductActionError( {
+ setRequestProductError( productId, {
code: 'invalid_product',
message: __( 'Invalid product name', 'jetpack-my-jetpack' ),
} )
);
}
- dispatch( setIsFetchingProductStatus( productId, true ) );
+ const method = data.activate ? 'POST' : 'DELETE';
+ dispatch( setIsFetchingProduct( productId, true ) );
// Activate/deactivate product.
return apiFetch( {
path: `${ REST_API_SITE_PRODUCTS_ENDPOINT }/${ productId }`,
- method: 'POST',
+ method,
data,
} )
- .then( status => {
- dispatch( setIsFetchingProductStatus( productId, false ) );
- dispatch( setProductStatus( productId, status ) );
+ .then( freshProduct => {
+ dispatch( setIsFetchingProduct( productId, false ) );
+ dispatch( setProduct( freshProduct ) );
resolve( status );
} )
.catch( error => {
- dispatch( setProductActionError( error ) );
+ dispatch( setProductStatus( productId, 'error' ) );
+ dispatch( setRequestProductError( productId, error ) );
reject( error );
- dispatch( setIsFetchingProductStatus( productId, false ) );
+ dispatch( setIsFetchingProduct( productId, false ) );
} );
} );
}
@@ -113,14 +136,27 @@ const deactivateProduct = productId => async store => {
};
const productActions = {
+ setProduct,
activateProduct,
deactivateProduct,
+ setIsFetchingProduct,
+ setRequestProductError,
+};
+
+const noticeActions = {
+ setGlobalNotice: ( message, options ) => ( {
+ type: 'SET_GLOBAL_NOTICE',
+ message,
+ options,
+ } ),
+ cleanGlobalNotice: () => ( { type: 'CLEAN_GLOBAL_NOTICE' } ),
};
const actions = {
setPurchasesIsFetching,
fetchPurchases,
setPurchases,
+ ...noticeActions,
...productActions,
};
@@ -128,10 +164,13 @@ export {
SET_PURCHASES_IS_FETCHING,
FETCH_PURCHASES,
SET_PURCHASES,
- SET_PRODUCT_ACTION_ERROR,
+ SET_PRODUCT,
+ SET_PRODUCT_REQUEST_ERROR,
ACTIVATE_PRODUCT,
DEACTIVATE_PRODUCT,
- SET_FETCHING_PRODUCT_STATUS,
+ SET_IS_FETCHING_PRODUCT,
SET_PRODUCT_STATUS,
+ SET_GLOBAL_NOTICE,
+ CLEAN_GLOBAL_NOTICE,
actions as default,
};
diff --git a/projects/packages/my-jetpack/_inc/state/reducers.js b/projects/packages/my-jetpack/_inc/state/reducers.js
index 593ffb0fca3d8..0301619589276 100644
--- a/projects/packages/my-jetpack/_inc/state/reducers.js
+++ b/projects/packages/my-jetpack/_inc/state/reducers.js
@@ -9,13 +9,31 @@ import { combineReducers } from '@wordpress/data';
import {
SET_PURCHASES,
SET_PURCHASES_IS_FETCHING,
- SET_PRODUCT_ACTION_ERROR,
+ SET_PRODUCT,
SET_PRODUCT_STATUS,
- SET_FETCHING_PRODUCT_STATUS,
+ SET_IS_FETCHING_PRODUCT,
+ SET_PRODUCT_REQUEST_ERROR,
+ SET_GLOBAL_NOTICE,
+ CLEAN_GLOBAL_NOTICE,
} from './actions';
const products = ( state = {}, action ) => {
switch ( action.type ) {
+ case SET_IS_FETCHING_PRODUCT: {
+ const { productId, isFetching } = action;
+ return {
+ ...state,
+ isFetching: {
+ ...state.isFetching,
+ [ productId ]: isFetching,
+ },
+ errors: {
+ ...state.errors,
+ [ productId ]: isFetching ? undefined : state.errors[ productId ],
+ },
+ };
+ }
+
case SET_PRODUCT_STATUS: {
const { productId, status } = action;
return {
@@ -24,31 +42,32 @@ const products = ( state = {}, action ) => {
...state.items,
[ productId ]: {
...state.items[ productId ],
- ...status,
+ status,
},
},
- error: {},
};
}
- case SET_PRODUCT_ACTION_ERROR:
+ case SET_PRODUCT: {
+ const { product } = action;
+ const { slug: productId } = product;
return {
...state,
- error: action.error,
+ items: {
+ ...state.items,
+ [ productId ]: product,
+ },
};
+ }
- case SET_FETCHING_PRODUCT_STATUS: {
- const { productId, isFetching } = action;
+ case SET_PRODUCT_REQUEST_ERROR: {
+ const { productId, error } = action;
return {
...state,
- items: {
- ...state.items,
- [ productId ]: {
- ...state.items[ productId ],
- isFetching,
- },
+ errors: {
+ ...state.errors,
+ [ productId ]: error,
},
- error: isFetching ? {} : state.error,
};
}
@@ -76,9 +95,35 @@ const purchases = ( state = {}, action ) => {
}
};
+const notices = ( state = { global: {} }, action ) => {
+ switch ( action.type ) {
+ case SET_GLOBAL_NOTICE: {
+ const { message, options } = action;
+ return {
+ ...state,
+ global: {
+ message,
+ options,
+ },
+ };
+ }
+
+ case CLEAN_GLOBAL_NOTICE: {
+ return {
+ ...state,
+ global: {},
+ };
+ }
+
+ default:
+ return state;
+ }
+};
+
const reducers = combineReducers( {
products,
purchases,
+ notices,
} );
export default reducers;
diff --git a/projects/packages/my-jetpack/_inc/state/resolvers.js b/projects/packages/my-jetpack/_inc/state/resolvers.js
index 22c828d9cbe29..e80b717012458 100644
--- a/projects/packages/my-jetpack/_inc/state/resolvers.js
+++ b/projects/packages/my-jetpack/_inc/state/resolvers.js
@@ -6,9 +6,26 @@ import apiFetch from '@wordpress/api-fetch';
/**
* Internal dependencies
*/
-import { REST_API_SITE_PURCHASES_ENDPOINT } from './constants';
+import { REST_API_SITE_PURCHASES_ENDPOINT, REST_API_SITE_PRODUCTS_ENDPOINT } from './constants';
const myJetpackResolvers = {
+ getProduct: ( productId ) => async ( { dispatch } ) => {
+ try {
+ dispatch.setProduct(
+ await apiFetch( {
+ path: `${ REST_API_SITE_PRODUCTS_ENDPOINT }/${ productId }`,
+ } )
+ );
+ } catch ( error ) {
+ // Pick error from the response body.
+ if ( error?.code && error?.message ) {
+ dispatch.setRequestProductError( productId, error );
+ } else {
+ throw new Error( error );
+ }
+ }
+ },
+
getPurchases: () => async ( { dispatch } ) => {
dispatch.setPurchasesIsFetching( true );
diff --git a/projects/packages/my-jetpack/_inc/state/selectors.js b/projects/packages/my-jetpack/_inc/state/selectors.js
index 8caa5597946d3..6d35a4d3364e9 100644
--- a/projects/packages/my-jetpack/_inc/state/selectors.js
+++ b/projects/packages/my-jetpack/_inc/state/selectors.js
@@ -9,6 +9,7 @@ const productSelectors = {
getProductNames,
getProduct,
isValidProduct,
+ isFetching: ( state, productId ) => state.products?.isFetching?.[ productId ] || false,
};
const purchasesSelectors = {
@@ -16,9 +17,14 @@ const purchasesSelectors = {
isRequestingPurchases: state => state.isRequestingPurchases || false,
};
+const noticeSelectors = {
+ getGlobalNotice: state => state.notices?.global,
+};
+
const selectors = {
...productSelectors,
...purchasesSelectors,
+ ...noticeSelectors,
};
export default selectors;
diff --git a/projects/packages/my-jetpack/_inc/style.module.scss b/projects/packages/my-jetpack/_inc/style.module.scss
new file mode 100644
index 0000000000000..94bb3d8604c2e
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/style.module.scss
@@ -0,0 +1,14 @@
+.layout {
+ min-height: calc( 100vh - 100px ); // Thank you for creating with WordPress.Version 5.9
+ position: relative;
+ padding-bottom: 30px;
+}
+
+.footer {
+ position: absolute;
+ bottom: 0px;
+ width: 100%;
+ height: 30px;
+ line-height: 30px;
+ padding-right: 20px;
+}
\ No newline at end of file
diff --git a/projects/packages/my-jetpack/_inc/utils/get-manage-your-plan-url.js b/projects/packages/my-jetpack/_inc/utils/get-manage-your-plan-url.js
index ec5ab784ed8cd..bc1ea11af68be 100644
--- a/projects/packages/my-jetpack/_inc/utils/get-manage-your-plan-url.js
+++ b/projects/packages/my-jetpack/_inc/utils/get-manage-your-plan-url.js
@@ -17,6 +17,6 @@ import { MY_JETPACK_MY_PLANS_MANAGE_SOURCE } from '../constants';
* @returns {string} the redirect URL
*/
export default function () {
- const { siteSuffix: site } = window?.myJetpackInitialState;
+ const site = window?.myJetpackInitialState?.siteSuffix;
return getRedirectUrl( MY_JETPACK_MY_PLANS_MANAGE_SOURCE, { site } );
}
diff --git a/projects/packages/my-jetpack/_inc/utils/to-camel.js b/projects/packages/my-jetpack/_inc/utils/to-camel.js
new file mode 100644
index 0000000000000..986b8015a5149
--- /dev/null
+++ b/projects/packages/my-jetpack/_inc/utils/to-camel.js
@@ -0,0 +1,42 @@
+/**
+ * Convert snake case string to camel case string.
+ *
+ * @param {string} string - String to convert.
+ * @returns {string} - Converted string.
+ */
+export function snakeToCamel( string ) {
+ return string.replace( /([-_][a-z])/gi, $1 => {
+ return $1.toUpperCase().replace( '_', '' );
+ } );
+}
+
+/**
+ * Check is the string has snake shape.
+ *
+ * @param {string} string - String to check.
+ * @returns {boolean} - True if the string has snake shape.
+ */
+function isSnake( string ) {
+ return string.indexOf( '_' ) !== -1;
+}
+
+/**
+ * Map object keys to camel case, in case they have snake shape.
+ *
+ * @param {object} object - Object to be converted.
+ * @param {boolean} deleteOriginalProp - Whether to delete the original property. False by default.
+ * @returns {object} - Converted object.
+ */
+export function mapObjectKeysToCamel( object = {}, deleteOriginalProp = false ) {
+ for ( const key in object ) {
+ if ( object.hasOwnProperty( key ) && isSnake( key ) ) {
+ object[ snakeToCamel( key ) ] = object[ key ];
+
+ if ( deleteOriginalProp ) {
+ delete object[ key ];
+ }
+ }
+ }
+
+ return object;
+}
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-data-approach b/projects/packages/my-jetpack/changelog/add-anti-spam-my-jetpack
similarity index 57%
rename from projects/packages/my-jetpack/changelog/update-my-jetpack-data-approach
rename to projects/packages/my-jetpack/changelog/add-anti-spam-my-jetpack
index 9a5a3d6662d69..797fde0a6b358 100644
--- a/projects/packages/my-jetpack/changelog/update-my-jetpack-data-approach
+++ b/projects/packages/my-jetpack/changelog/add-anti-spam-my-jetpack
@@ -1,4 +1,4 @@
Significance: patch
Type: added
-Add first data approach
+anti spam product class
diff --git a/projects/packages/my-jetpack/changelog/add-event-interstitial-my-jetpack b/projects/packages/my-jetpack/changelog/add-event-interstitial-my-jetpack
new file mode 100644
index 0000000000000..059b466161234
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/add-event-interstitial-my-jetpack
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Fire Tracks event when showing the Interstitial page
diff --git a/projects/packages/my-jetpack/changelog/add-fancy-eslint-ignore b/projects/packages/my-jetpack/changelog/add-fancy-eslint-ignore
deleted file mode 100644
index afe95e481cf32..0000000000000
--- a/projects/packages/my-jetpack/changelog/add-fancy-eslint-ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Hack eslint config to use `.gitignore` and per-dir `.eslintignore`.
-
-
diff --git a/projects/packages/my-jetpack/changelog/add-my-jetpack-product-card b/projects/packages/my-jetpack/changelog/add-my-jetpack-product-card
deleted file mode 100644
index e07b1f0b309a2..0000000000000
--- a/projects/packages/my-jetpack/changelog/add-my-jetpack-product-card
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: added
-
-My Jetpack: Add Product Card component
diff --git a/projects/packages/my-jetpack/changelog/add-my-jetpack-tracks-event-interstitial-click b/projects/packages/my-jetpack/changelog/add-my-jetpack-tracks-event-interstitial-click
new file mode 100644
index 0000000000000..a13aedaf7087b
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/add-my-jetpack-tracks-event-interstitial-click
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Fire Tracks event when clicking CTA button on product Interstitial page
diff --git a/projects/packages/my-jetpack/changelog/add-my_jetpack_has_plan b/projects/packages/my-jetpack/changelog/add-my_jetpack_has_plan
new file mode 100644
index 0000000000000..14a8adfb86373
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/add-my_jetpack_has_plan
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Add has_required_plan to product info and implement method in Search
diff --git a/projects/js-packages/storybook/changelog/2022-01-04-19-50-37-326407 b/projects/packages/my-jetpack/changelog/add-my_jetpack_has_plan#2
similarity index 100%
rename from projects/js-packages/storybook/changelog/2022-01-04-19-50-37-326407
rename to projects/packages/my-jetpack/changelog/add-my_jetpack_has_plan#2
diff --git a/projects/packages/my-jetpack/changelog/add-products-section-my-jetpack b/projects/packages/my-jetpack/changelog/add-products-section-my-jetpack
deleted file mode 100644
index a286866458353..0000000000000
--- a/projects/packages/my-jetpack/changelog/add-products-section-my-jetpack
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: added
-
-Adds very basic product cards section component to my jetpack
diff --git a/projects/packages/my-jetpack/changelog/add-record-event-for-on-add b/projects/packages/my-jetpack/changelog/add-record-event-for-on-add
new file mode 100644
index 0000000000000..e3a1920771229
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/add-record-event-for-on-add
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Fire Tracks event when clickn Add link on My Jetpack product card
diff --git a/projects/packages/my-jetpack/changelog/add-scan-and-backup-purchase-check b/projects/packages/my-jetpack/changelog/add-scan-and-backup-purchase-check
new file mode 100644
index 0000000000000..bbe1f452756a9
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/add-scan-and-backup-purchase-check
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Plan verification for Backup and Scan
diff --git a/projects/packages/my-jetpack/changelog/add-visitor-status b/projects/packages/my-jetpack/changelog/add-visitor-status
deleted file mode 100644
index 0b395e9ceee8c..0000000000000
--- a/projects/packages/my-jetpack/changelog/add-visitor-status
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: added
-
-Added Visitor class for status regarding the site visitor.
diff --git a/projects/packages/my-jetpack/changelog/fix-set-product-status-reducer-my-jetpack b/projects/packages/my-jetpack/changelog/fix-set-product-status-reducer-my-jetpack
new file mode 100644
index 0000000000000..85b9b40141658
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/fix-set-product-status-reducer-my-jetpack
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Fix My Jetpack's reducer for SET_PRODUCT_STATUS
diff --git a/projects/packages/my-jetpack/changelog/fix-svg-attr-my-jetpack-product-card b/projects/packages/my-jetpack/changelog/fix-svg-attr-my-jetpack-product-card
deleted file mode 100644
index f877ecaaf06f0..0000000000000
--- a/projects/packages/my-jetpack/changelog/fix-svg-attr-my-jetpack-product-card
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fixed
-
-Fixed svg attribute strokeWidth for Boost Card
diff --git a/projects/packages/my-jetpack/changelog/poc-my-jetpac-product-class b/projects/packages/my-jetpack/changelog/poc-my-jetpac-product-class
deleted file mode 100644
index 94d53f63b453c..0000000000000
--- a/projects/packages/my-jetpack/changelog/poc-my-jetpac-product-class
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: added
-
-add API endpoints to manipulate products
diff --git a/projects/packages/my-jetpack/changelog/remove-plans-endpoints-my-jetpack b/projects/packages/my-jetpack/changelog/remove-plans-endpoints-my-jetpack
deleted file mode 100644
index 494f1463fb811..0000000000000
--- a/projects/packages/my-jetpack/changelog/remove-plans-endpoints-my-jetpack
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: added
-
-Removed endpoint plans superseded by purchases
diff --git a/projects/js-packages/storybook/changelog/renovate-storybook-monorepo#2 b/projects/packages/my-jetpack/changelog/renovate-wordpress-monorepo
similarity index 100%
rename from projects/js-packages/storybook/changelog/renovate-storybook-monorepo#2
rename to projects/packages/my-jetpack/changelog/renovate-wordpress-monorepo
diff --git a/projects/js-packages/storybook/changelog/2022-01-11-19-07-56-905220 b/projects/packages/my-jetpack/changelog/renovate-wordpress-monorepo#2
similarity index 100%
rename from projects/js-packages/storybook/changelog/2022-01-11-19-07-56-905220
rename to projects/packages/my-jetpack/changelog/renovate-wordpress-monorepo#2
diff --git a/projects/packages/my-jetpack/changelog/update-connection-enforce-initial-state b/projects/packages/my-jetpack/changelog/update-connection-enforce-initial-state
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/packages/my-jetpack/changelog/update-connection-enforce-initial-state
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/packages/my-jetpack/changelog/update-css-module-plans-section b/projects/packages/my-jetpack/changelog/update-css-module-plans-section
deleted file mode 100644
index 85861596aba92..0000000000000
--- a/projects/packages/my-jetpack/changelog/update-css-module-plans-section
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: added
-
-Added css module for My Jetpack Plans Section
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-add-pricing-harcoded-product-data b/projects/packages/my-jetpack/changelog/update-my-jetpack-add-pricing-harcoded-product-data
new file mode 100644
index 0000000000000..d6a890c2d7b86
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-add-pricing-harcoded-product-data
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Add Product princign harcoded data
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-add-product-boost-interstitial b/projects/packages/my-jetpack/changelog/update-my-jetpack-add-product-boost-interstitial
new file mode 100644
index 0000000000000..946482fd9acb9
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-add-product-boost-interstitial
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Add Boost interstitial cmp.
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-add-product-interstitial-cmp b/projects/packages/my-jetpack/changelog/update-my-jetpack-add-product-interstitial-cmp
new file mode 100644
index 0000000000000..4adad2e01ed71
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-add-product-interstitial-cmp
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Introduce ProductDetailCard component
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-add-products-endpoint b/projects/packages/my-jetpack/changelog/update-my-jetpack-add-products-endpoint
deleted file mode 100644
index c0ec3dccade6f..0000000000000
--- a/projects/packages/my-jetpack/changelog/update-my-jetpack-add-products-endpoint
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: added
-
-Add Products and REST_Products basic classes
diff --git a/projects/packages/connection-ui/changelog/update-enforce-connection-initial-state b/projects/packages/my-jetpack/changelog/update-my-jetpack-add-search-product-data
similarity index 56%
rename from projects/packages/connection-ui/changelog/update-enforce-connection-initial-state
rename to projects/packages/my-jetpack/changelog/update-my-jetpack-add-search-product-data
index c9a80df46cbc6..98561ef639652 100644
--- a/projects/packages/connection-ui/changelog/update-enforce-connection-initial-state
+++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-add-search-product-data
@@ -1,4 +1,4 @@
Significance: patch
Type: added
-connection initial state
+Add search product data
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-add-title-and-features-to-products-data b/projects/packages/my-jetpack/changelog/update-my-jetpack-add-title-and-features-to-products-data
new file mode 100644
index 0000000000000..15e7180c0d3e4
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-add-title-and-features-to-products-data
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Add title and features to products data
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-clean-unused-hook b/projects/packages/my-jetpack/changelog/update-my-jetpack-clean-unused-hook
deleted file mode 100644
index 3978d3f9d8702..0000000000000
--- a/projects/packages/my-jetpack/changelog/update-my-jetpack-clean-unused-hook
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: removed
-
-Remove unused usePlans() custom react hook
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-connection-redirect b/projects/packages/my-jetpack/changelog/update-my-jetpack-connection-redirect
new file mode 100644
index 0000000000000..26fdd23674bdf
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-connection-redirect
@@ -0,0 +1,4 @@
+Significance: patch
+Type: changed
+
+My Jetpack: Update notice style and implements redirect for connection route
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-connection-screen b/projects/packages/my-jetpack/changelog/update-my-jetpack-connection-screen
new file mode 100644
index 0000000000000..324f508c87934
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-connection-screen
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+My Jetpack: Add Connection screen
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-connections-section b/projects/packages/my-jetpack/changelog/update-my-jetpack-connections-section
deleted file mode 100644
index b522b73954f77..0000000000000
--- a/projects/packages/my-jetpack/changelog/update-my-jetpack-connections-section
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Add Connections Section wrapping the Connection Status Card to My Jetpack
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-do-not-ship-raw-files b/projects/packages/my-jetpack/changelog/update-my-jetpack-do-not-ship-raw-files
deleted file mode 100644
index 8afa612f711b0..0000000000000
--- a/projects/packages/my-jetpack/changelog/update-my-jetpack-do-not-ship-raw-files
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Build: do not ship raw files in production bundle.
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-fix-tracking-events-handler b/projects/packages/my-jetpack/changelog/update-my-jetpack-fix-tracking-events-handler
new file mode 100644
index 0000000000000..63463e190d3e5
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-fix-tracking-events-handler
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+useCallback for functions that are bound to onEvents
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-handle-search-product-from-ui b/projects/packages/my-jetpack/changelog/update-my-jetpack-handle-search-product-from-ui
new file mode 100644
index 0000000000000..801eded873ab6
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-handle-search-product-from-ui
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Implement Search product interstitial
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-open-boost-interstitial b/projects/packages/my-jetpack/changelog/update-my-jetpack-open-boost-interstitial
new file mode 100644
index 0000000000000..c1cea97a4d763
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-open-boost-interstitial
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Connect Boost card with the interstitial page via /add-boost root
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-re-organize-layout b/projects/packages/my-jetpack/changelog/update-my-jetpack-re-organize-layout
new file mode 100644
index 0000000000000..92983c3c0bae1
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-re-organize-layout
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Introduce basic Layout component. Add GoBackLink component
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-reduxify-products b/projects/packages/my-jetpack/changelog/update-my-jetpack-reduxify-products
deleted file mode 100644
index 1cc4b8bd62b93..0000000000000
--- a/projects/packages/my-jetpack/changelog/update-my-jetpack-reduxify-products
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: added
-
-My Jetpack: Implement data handling for enable/disable products
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-restore-get-product-resolver b/projects/packages/my-jetpack/changelog/update-my-jetpack-restore-get-product-resolver
new file mode 100644
index 0000000000000..36331f47cce81
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-restore-get-product-resolver
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Restore getProduct() resolver
diff --git a/projects/packages/my-jetpack/changelog/update-my-jetpack-use-pricing-data b/projects/packages/my-jetpack/changelog/update-my-jetpack-use-pricing-data
new file mode 100644
index 0000000000000..e129473a2969c
--- /dev/null
+++ b/projects/packages/my-jetpack/changelog/update-my-jetpack-use-pricing-data
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Implement Free price for Boost product
diff --git a/projects/packages/my-jetpack/changelog/update-project-scripts-no-install b/projects/packages/my-jetpack/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/my-jetpack/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/my-jetpack/composer.json b/projects/packages/my-jetpack/composer.json
index 3a485c4120fdf..3e7a28f67789f 100644
--- a/projects/packages/my-jetpack/composer.json
+++ b/projects/packages/my-jetpack/composer.json
@@ -5,14 +5,17 @@
"license": "GPL-2.0-or-later",
"require": {
"automattic/jetpack-admin-ui": "^0.2",
- "automattic/jetpack-assets": "^1.16",
+ "automattic/jetpack-assets": "^1.17",
"automattic/jetpack-connection": "^1.36",
+ "automattic/jetpack-plugins-installer": "^0.1",
"automattic/jetpack-terms-of-service": "^1.9",
"automattic/jetpack-tracking": "^1.14"
},
"require-dev": {
"yoast/phpunit-polyfills": "1.0.3",
"automattic/jetpack-changelogger": "^3.0",
+ "automattic/jetpack-options": "^1.14",
+ "automattic/jetpack-search": "^0.7",
"automattic/wordbless": "@dev"
},
"autoload": {
@@ -70,7 +73,7 @@
"link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}"
},
"branch-alias": {
- "dev-master": "0.4.x-dev"
+ "dev-master": "0.5.x-dev"
}
},
"config": {
diff --git a/projects/packages/my-jetpack/jest.setup.js b/projects/packages/my-jetpack/jest.setup.js
index 01caebccf3e3c..06b97b04e86b9 100644
--- a/projects/packages/my-jetpack/jest.setup.js
+++ b/projects/packages/my-jetpack/jest.setup.js
@@ -2,3 +2,12 @@
* External dependencies
*/
import '@testing-library/jest-dom';
+window.JP_CONNECTION_INITIAL_STATE = {
+ userConnectionData: {
+ currentUser: {
+ wpcomUser: { Id: 99999, login: 'bobsacramento', display_name: 'Bob Sacrmaneto' },
+ },
+ },
+};
+window.myJetpackInitialState = {};
+window.myJetpackRest = {};
diff --git a/projects/packages/my-jetpack/package.json b/projects/packages/my-jetpack/package.json
index b57cf8f5d5eed..d1b0019c61347 100644
--- a/projects/packages/my-jetpack/package.json
+++ b/projects/packages/my-jetpack/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@automattic/jetpack-my-jetpack",
- "version": "0.4.0-alpha",
+ "version": "0.5.1-alpha",
"description": "WP Admin page with information and configuration shared among all Jetpack stand-alone plugins",
"homepage": "https://jetpack.com",
"bugs": {
@@ -21,22 +21,26 @@
"test": "js-test-runner jest --passWithNoTests --setupFilesAfterEnv ./jest.setup.js"
},
"dependencies": {
- "@automattic/jetpack-analytics": "workspace:^0.1.7-alpha",
- "@automattic/jetpack-components": "workspace:^0.10.3-alpha",
- "@automattic/jetpack-connection": "workspace:^0.14.0-alpha",
- "@wordpress/components": "19.1.6",
- "@wordpress/data": "6.1.5",
- "@wordpress/i18n": "4.2.4",
+ "@automattic/format-currency": "1.0.0-alpha.0",
+ "@automattic/jetpack-analytics": "workspace:^0.1.7",
+ "@automattic/jetpack-components": "workspace:^0.10.5-alpha",
+ "@automattic/jetpack-connection": "workspace:^0.15.1-alpha",
+ "@wordpress/api-fetch": "6.0.0",
+ "@wordpress/components": "19.3.0",
+ "@wordpress/data": "6.2.0",
+ "@wordpress/i18n": "4.3.0",
+ "@wordpress/icons": "6.2.0",
"classnames": "2.3.1",
- "prop-types": "15.8.1"
+ "prop-types": "15.8.1",
+ "react-router-dom": "6"
},
"sideEffects": [
"*.css",
"*.scss"
],
"devDependencies": {
- "@automattic/jetpack-base-styles": "workspace:^0.1.7-alpha",
- "@automattic/jetpack-webpack-config": "workspace:^1.0.3-alpha",
+ "@automattic/jetpack-base-styles": "workspace:^0.1.8-alpha",
+ "@automattic/jetpack-webpack-config": "workspace:^1.1.2-alpha",
"@babel/core": "7.16.0",
"@babel/preset-env": "7.16.4",
"@babel/register": "7.16.0",
diff --git a/projects/packages/my-jetpack/src/class-initializer.php b/projects/packages/my-jetpack/src/class-initializer.php
index 0365057c71175..af0c83b6084f8 100644
--- a/projects/packages/my-jetpack/src/class-initializer.php
+++ b/projects/packages/my-jetpack/src/class-initializer.php
@@ -28,12 +28,7 @@ class Initializer {
* @return void
*/
public static function init() {
- if ( did_action( 'my_jetpack_init' ) ) {
- return;
- }
-
- // Feature flag while we are developing it.
- if ( ! defined( 'JETPACK_ENABLE_MY_JETPACK' ) || ! JETPACK_ENABLE_MY_JETPACK ) {
+ if ( ! self::should_initialize() ) {
return;
}
@@ -102,8 +97,6 @@ public static function enqueue_scripts() {
'my_jetpack_main_app',
'myJetpackInitialState',
array(
- 'apiRoot' => esc_url_raw( rest_url() ),
- 'apiNonce' => wp_create_nonce( 'wp_rest' ),
'products' => array(
'items' => Products::get_products(),
),
@@ -116,6 +109,15 @@ public static function enqueue_scripts() {
)
);
+ wp_localize_script(
+ 'my_jetpack_main_app',
+ 'myJetpackRest',
+ array(
+ 'apiRoot' => esc_url_raw( rest_url() ),
+ 'apiNonce' => wp_create_nonce( 'wp_rest' ),
+ )
+ );
+
// Connection Initial State.
wp_add_inline_script( 'my_jetpack_main_app', Connection_Initial_State::render(), 'before' );
@@ -166,6 +168,36 @@ public static function permissions_callback() {
return current_user_can( 'manage_options' );
}
+ /**
+ * Return true if we should initialize the My Jetpack
+ */
+ public static function should_initialize() {
+ if ( did_action( 'my_jetpack_init' ) ) {
+ return false;
+ }
+
+ /**
+ * Allows filtering whether My Jetpack should be initialized
+ *
+ * @since 0.5.0-alpha
+ *
+ * @param bool $shoud_initialize Should we initialize My Jetpack?
+ */
+ $should = apply_filters( 'jetpack_my_jetpack_should_initialize', true );
+
+ // Feature flag while we are developing it.
+ if ( ! defined( 'JETPACK_ENABLE_MY_JETPACK' ) || ! JETPACK_ENABLE_MY_JETPACK ) {
+ return false;
+ }
+
+ // Do not initialize My Jetpack if site is not connected.
+ if ( ! ( new Connection_Manager() )->is_connected() ) {
+ return false;
+ }
+
+ return $should;
+ }
+
/**
* Site full-data endpoint.
*
diff --git a/projects/packages/my-jetpack/src/class-products.php b/projects/packages/my-jetpack/src/class-products.php
index 2dfae5cfc0e42..36e8c75f74d0e 100644
--- a/projects/packages/my-jetpack/src/class-products.php
+++ b/projects/packages/my-jetpack/src/class-products.php
@@ -108,39 +108,7 @@ public static function get_product_data_schema() {
* @return array Object with infromation about the product.
*/
public static function get_anti_spam_data() {
- return array(
- 'slug' => 'anti-spam',
- 'description' => __( 'Stop comment and form spam', 'jetpack-my-jetpack' ),
- 'name' => __( 'Anti-spam', 'jetpack-my-jetpack' ),
- 'status' => 'inactive',
- );
- }
-
- /**
- * Activate Backup product
- */
- public static function activate_backup_product() {
- /*
- * @todo: implement
- * suggestion: when it enables, return an success array:
- * array( 'status' => 'activated' );
- * Otherwise, an WP_Error instance will be nice.
- */
- return array(
- 'status' => 'active',
- );
- }
-
- /**
- * Deactivate Backup product
- */
- public static function deactivate_backup_product() {
- /*
- * @todo: implement
- */
- return array(
- 'status' => 'inactive',
- );
+ return Products\Anti_Spam::get_info();
}
/**
@@ -149,12 +117,7 @@ public static function deactivate_backup_product() {
* @return array Object with infromation about the product.
*/
public static function get_backup_data() {
- return array(
- 'slug' => 'backup',
- 'description' => __( 'Save every change', 'jetpack-my-jetpack' ),
- 'name' => __( 'Backup', 'jetpack-my-jetpack' ),
- 'status' => 'active',
- );
+ return Products\Backup::get_info();
}
/**
@@ -172,12 +135,7 @@ public static function get_boost_data() {
* @return array Object with infromation about the product.
*/
public static function get_crm_data() {
- return array(
- 'slug' => 'crm',
- 'description' => __( 'Connect with your people', 'jetpack-my-jetpack' ),
- 'name' => __( 'CRM', 'jetpack-my-jetpack' ),
- 'status' => 'plugin_absent',
- );
+ return Products\Crm::get_info();
}
/**
@@ -200,12 +158,7 @@ public static function get_extras_data() {
* @return array Object with infromation about the product.
*/
public static function get_scan_data() {
- return array(
- 'slug' => 'scan',
- 'description' => __( 'Stay one step ahead of threats', 'jetpack-my-jetpack' ),
- 'name' => __( 'Scan', 'jetpack-my-jetpack' ),
- 'status' => 'plugin_absent',
- );
+ return Products\Scan::get_info();
}
/**
@@ -214,12 +167,7 @@ public static function get_scan_data() {
* @return array Object with infromation about the product.
*/
public static function get_search_data() {
- return array(
- 'slug' => 'search',
- 'description' => __( 'Help them find what they need', 'jetpack-my-jetpack' ),
- 'name' => __( 'Search', 'jetpack-my-jetpack' ),
- 'status' => 'plugin_absent',
- );
+ return Products\Search::get_info();
}
/**
@@ -228,11 +176,6 @@ public static function get_search_data() {
* @return array Object with infromation about the product.
*/
public static function get_videopress_data() {
- return array(
- 'slug' => 'videopress',
- 'description' => __( 'High quality, ad-free video', 'jetpack-my-jetpack' ),
- 'name' => __( 'VideoPress', 'jetpack-my-jetpack' ),
- 'status' => 'active',
- );
+ return Products\Videopress::get_info();
}
}
diff --git a/projects/packages/my-jetpack/src/class-rest-products.php b/projects/packages/my-jetpack/src/class-rest-products.php
index 3f638f993bc09..debd291ffa096 100644
--- a/projects/packages/my-jetpack/src/class-rest-products.php
+++ b/projects/packages/my-jetpack/src/class-rest-products.php
@@ -32,7 +32,7 @@ public function __construct() {
'description' => __( 'Product slug', 'jetpack-my-jetpack' ),
'type' => 'string',
'enum' => Products::get_product_names(),
- 'required' => false,
+ 'required' => true,
'validate_callback' => __CLASS__ . '::check_product_argument',
);
@@ -68,6 +68,20 @@ public function __construct() {
);
}
+ /**
+ * Get the schema for the products endpoint
+ *
+ * @return array
+ */
+ public function get_products_schema() {
+ return array(
+ '$schema' => 'http://json-schema.org/draft-04/schema#',
+ 'title' => 'products',
+ 'type' => 'object',
+ 'properties' => Products::get_product_data_schema(),
+ );
+ }
+
/**
* Check user capability to access the endpoint.
*
@@ -107,9 +121,7 @@ public static function check_product_argument( $value ) {
* @return array of site products list.
*/
public static function get_products() {
- $response = array(
- 'products' => Products::get_products(),
- );
+ $response = Products::get_products();
return rest_ensure_response( $response, 200 );
}
@@ -121,10 +133,7 @@ public static function get_products() {
*/
public static function get_product( $request ) {
$product_slug = $request->get_param( 'product' );
- $response = array(
- 'products' => array( Products::get_product( $product_slug ) ),
- );
- return rest_ensure_response( $response, 200 );
+ return rest_ensure_response( Products::get_product( $product_slug ), 200 );
}
/**
@@ -152,30 +161,20 @@ public static function activate_product( $request ) {
$product_slug = $request->get_param( 'product' );
$product = Products::get_product( $product_slug );
if ( ! isset( $product['class'] ) ) {
- return new \WP_REST_Response(
- array(
- 'error_message' => 'not_implemented',
- ),
- 400
+ return new \WP_Error(
+ 'not_implemented',
+ esc_html__( 'The product class handler is not implemented', 'jetpack-my-jetpack' ),
+ array( 'status' => 501 )
);
}
- $success = call_user_func( array( $product['class'], 'activate' ) );
- $error_code = '';
- $error_message = '';
- if ( is_wp_error( $success ) ) {
- $error_code = $success->get_error_code();
- $error_message = $success->get_error_message();
- $success = false;
+ $activate_product_result = call_user_func( array( $product['class'], 'activate' ) );
+ if ( is_wp_error( $activate_product_result ) ) {
+ $activate_product_result->add_data( array( 'status' => 400 ) );
+ return $activate_product_result;
}
- $response = array(
- 'success' => $success,
- 'products' => array( Products::get_product( $product_slug ) ),
- 'error_code' => $error_code,
- 'error_message' => $error_message,
- );
- return rest_ensure_response( $response, 200 );
+ return rest_ensure_response( Products::get_product( $product_slug ), 200 );
}
/**
@@ -188,37 +187,20 @@ public static function deactivate_product( $request ) {
$product_slug = $request->get_param( 'product' );
$product = Products::get_product( $product_slug );
if ( ! isset( $product['class'] ) ) {
- return new \WP_REST_Response(
- array(
- 'error_message' => 'not_implemented',
- ),
- 400
+ return new \WP_Error(
+ 'not_implemented',
+ esc_html__( 'The product class handler is not implemented', 'jetpack-my-jetpack' ),
+ array( 'status' => 501 )
);
}
- $success = call_user_func( array( $product['class'], 'deactivate' ) );
- $response = array(
- 'success' => $success,
- 'products' => array( Products::get_product( $product_slug ) ),
- );
- return rest_ensure_response( $response, 200 );
-
- }
-
- /**
- * Set site product state.
- *
- * @param \WP_REST_Request $request The request object.
- * @return array of site products list.
- */
- public static function set_product_state( $request ) {
- $product_slug = $request->get_param( 'product' );
- $activate = $request->get_param( 'activate' );
-
- if ( $activate ) {
- return Products::activate_backup_product( $product_slug );
+ $deactivate_product_result = call_user_func( array( $product['class'], 'deactivate' ) );
+ if ( is_wp_error( $deactivate_product_result ) ) {
+ $deactivate_product_result->add_data( array( 'status' => 400 ) );
+ return $deactivate_product_result;
}
- return Products::deactivate_backup_product( $product_slug );
+ return rest_ensure_response( Products::get_product( $product_slug ), 200 );
}
+
}
diff --git a/projects/packages/my-jetpack/src/class-rest-purchases.php b/projects/packages/my-jetpack/src/class-rest-purchases.php
index 3439c4f8da78d..e01089e45238e 100644
--- a/projects/packages/my-jetpack/src/class-rest-purchases.php
+++ b/projects/packages/my-jetpack/src/class-rest-purchases.php
@@ -8,6 +8,7 @@
namespace Automattic\Jetpack\My_Jetpack;
use Automattic\Jetpack\Connection\Client as Client;
+use Automattic\Jetpack\Connection\Manager as Connection_Manager;
/**
* Registers the REST routes for Purchases.
@@ -37,6 +38,19 @@ public function __construct() {
* @return true|WP_Error
*/
public static function permissions_callback() {
+ $connection = new Connection_Manager();
+ $is_user_connected = $connection->is_connected();
+
+ if ( ! $is_user_connected ) {
+ return new \WP_Error(
+ 'not_connected',
+ __( 'You are not connected to Jetpack.', 'jetpack-my-jetpack' ),
+ array(
+ 'status' => 400,
+ )
+ );
+ }
+
return current_user_can( 'manage_options' );
}
diff --git a/projects/packages/my-jetpack/src/products/class-anti-spam.php b/projects/packages/my-jetpack/src/products/class-anti-spam.php
new file mode 100644
index 0000000000000..e30d2b395b065
--- /dev/null
+++ b/projects/packages/my-jetpack/src/products/class-anti-spam.php
@@ -0,0 +1,103 @@
+ true,
+ 'currency_code' => 'EUR',
+ 'full_price' => '9',
+ 'promotion_percentage' => '50',
+ );
+ }
+}
diff --git a/projects/packages/my-jetpack/src/products/class-backup.php b/projects/packages/my-jetpack/src/products/class-backup.php
new file mode 100644
index 0000000000000..605e3addc3151
--- /dev/null
+++ b/projects/packages/my-jetpack/src/products/class-backup.php
@@ -0,0 +1,146 @@
+ true,
+ 'currency_code' => 'EUR',
+ 'full_price' => '9',
+ 'promotion_percentage' => '50',
+ );
+ }
+
+ /**
+ * Hits the wpcom api to check rewind status.
+ *
+ * @todo Maybe add caching.
+ *
+ * @return Object|WP_Error
+ */
+ private static function get_state_from_wpcom() {
+ static $status = null;
+
+ if ( ! is_null( $status ) ) {
+ return $status;
+ }
+
+ $site_id = Jetpack_Options::get_option( 'id' );
+
+ $response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/rewind', $site_id ) . '?force=wpcom', '2', array(), null, 'wpcom' );
+
+ if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
+ return new WP_Error( 'rewind_state_fetch_failed' );
+ }
+
+ $body = wp_remote_retrieve_body( $response );
+ $status = json_decode( $body );
+ return $status;
+ }
+
+ /**
+ * Checks whether the current plan (or purchases) of the site already supports the product
+ *
+ * @return boolean
+ */
+ public static function has_required_plan() {
+ $rewind_data = static::get_state_from_wpcom();
+ if ( is_wp_error( $rewind_data ) ) {
+ return false;
+ }
+ return is_object( $rewind_data ) && isset( $rewind_data->state ) && 'unavailable' !== $rewind_data->state;
+ }
+
+}
diff --git a/projects/packages/my-jetpack/src/products/class-boost.php b/projects/packages/my-jetpack/src/products/class-boost.php
index 5ce5a93f32c0a..633591392fac3 100644
--- a/projects/packages/my-jetpack/src/products/class-boost.php
+++ b/projects/packages/my-jetpack/src/products/class-boost.php
@@ -7,84 +7,99 @@
namespace Automattic\Jetpack\My_Jetpack\Products;
+use Automattic\Jetpack\My_Jetpack\Product;
+
/**
* Class responsible for handling the Boost product
*/
-class Boost {
+class Boost extends Product {
- const PLUGIN_FILENAME = 'boost/jetpack-boost.php';
+ /**
+ * The product slug
+ *
+ * @var string
+ */
+ public static $slug = 'boost';
/**
- * Get the Product info for the API
+ * The filename (id) of the plugin associated with this product. If not defined, it will default to the Jetpack plugin
*
- * @return array
+ * @var string
*/
- public static function get_info() {
- if ( self::is_active() ) {
- $status = 'active';
- } elseif ( ! self::is_plugin_installed() ) {
- $status = 'plugin_absent';
- } else {
- $status = 'inactive';
- }
- return array(
- 'slug' => 'boost',
- 'description' => __( 'Instant speed and SEO', 'jetpack-my-jetpack' ),
- 'name' => __( 'Boost', 'jetpack-my-jetpack' ),
- 'status' => $status,
- 'class' => __CLASS__,
- );
+ public static $plugin_filename = 'boost/jetpack-boost.php';
+
+ /**
+ * The slug of the plugin associated with this product. If not defined, it will default to the Jetpack plugin
+ *
+ * @var string
+ */
+ public static $plugin_slug = 'jetpack-boost';
+
+ /**
+ * Whether this product requires a user connection
+ *
+ * @var string
+ */
+ public static $requires_user_connection = false;
+
+ /**
+ * Get the internationalized product name
+ *
+ * @return string
+ */
+ public static function get_name() {
+ return __( 'Boost', 'jetpack-my-jetpack' );
}
/**
- * Checks whether the Product is active
+ * Get the internationalized product title
*
- * @return boolean
+ * @return string
*/
- public static function is_active() {
- return self::is_plugin_active();
+ public static function get_title() {
+ return __( 'Jetpack Boost', 'jetpack-my-jetpack' );
}
/**
- * Checks whether the plugin is installed
+ * Get the internationalized product description
*
- * @return boolean
+ * @return string
*/
- public static function is_plugin_installed() {
- $all_plugins = get_plugins();
- return array_key_exists( self::PLUGIN_FILENAME, $all_plugins );
+ public static function get_description() {
+ return __( 'Instant speed and SEO', 'jetpack-my-jetpack' );
}
/**
- * Checks whether the plugin is active
+ * Get the internationalized product long description
*
- * @return boolean
+ * @return string
*/
- public static function is_plugin_active() {
- return is_plugin_active( self::PLUGIN_FILENAME );
+ public static function get_long_description() {
+ return __( 'Jetpack Boost gives your site the same performance advantages as the world’s leading websites, no developer required.', 'jetpack-my-jetpack' );
}
/**
- * Activates the plugin (in the future also intall the plugin if needed)
+ * Get the internationalized features list
*
- * @return boolean|\WP_Error
+ * @return array Boost features list
*/
- public static function activate() {
- // TODO - extract lib/plugins.php from Jetpack in order to install. For now this will only activate the already installed plugin.
- $result = activate_plugin( self::PLUGIN_FILENAME );
- if ( is_wp_error( $result ) ) {
- return $result;
- }
- return is_null( $result );
+ public static function get_features() {
+ return array(
+ __( 'Check your site performance', 'jetpack-my-jetpack' ),
+ __( 'Enable improvements in one click', 'jetpack-my-jetpack' ),
+ __( 'Standalone free plugin for those focused on speed', 'jetpack-my-jetpack' ),
+ );
}
/**
- * Deactivate the plugin
+ * Get the product princing details
*
- * @return boolean
+ * @return array Pricing details
*/
- public static function deactivate() {
- deactivate_plugins( self::PLUGIN_FILENAME );
- return true;
+ public static function get_pricing_for_ui() {
+ return array(
+ 'available' => true,
+ 'is_free' => true,
+ );
}
}
diff --git a/projects/packages/my-jetpack/src/products/class-crm.php b/projects/packages/my-jetpack/src/products/class-crm.php
new file mode 100644
index 0000000000000..4535ebfd59804
--- /dev/null
+++ b/projects/packages/my-jetpack/src/products/class-crm.php
@@ -0,0 +1,101 @@
+ true,
+ 'is_free' => true,
+ );
+ }
+}
diff --git a/projects/packages/my-jetpack/src/products/class-hybrid-product.php b/projects/packages/my-jetpack/src/products/class-hybrid-product.php
new file mode 100644
index 0000000000000..2a1b55391cc88
--- /dev/null
+++ b/projects/packages/my-jetpack/src/products/class-hybrid-product.php
@@ -0,0 +1,30 @@
+ static::$slug,
+ 'name' => static::get_name(),
+ 'title' => static::get_title(),
+ 'description' => static::get_description(),
+ 'long_description' => static::get_long_description(),
+ 'features' => static::get_features(),
+ 'status' => static::get_status(),
+ 'pricing_for_ui' => static::get_pricing_for_ui(),
+ 'requires_user_connection' => static::$requires_user_connection,
+ 'has_required_plan' => static::has_required_plan(),
+ 'class' => get_called_class(),
+ );
+ }
+
+ /**
+ * Get the internationalized product name
+ *
+ * @return string
+ */
+ abstract public static function get_name();
+
+ /**
+ * Get the internationalized product title
+ *
+ * @return string
+ */
+ abstract public static function get_title();
+
+ /**
+ * Get the internationalized product description
+ *
+ * @return string
+ */
+ abstract public static function get_description();
+
+ /**
+ * Get the internationalized product long description
+ *
+ * @return string
+ */
+ abstract public static function get_long_description();
+
+ /**
+ * Get the internationalized features list
+ *
+ * @return array
+ */
+ abstract public static function get_features();
+
+ /**
+ * Get the product pricing
+ *
+ * @return array
+ */
+ abstract public static function get_pricing_for_ui();
+
+ /**
+ * Checks whether the current plan (or purchases) of the site already supports the product
+ *
+ * Returns true if it supports. Return false if a purchase is still required.
+ *
+ * Free products will always return true.
+ *
+ * @return boolean
+ */
+ public static function has_required_plan() {
+ return true;
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @return string
+ */
+ public static function get_status() {
+ if ( ! static::has_required_plan() ) {
+ $status = 'needs_purchase';
+ } elseif ( static::is_active() ) {
+ $status = 'active';
+ } elseif ( ! self::is_plugin_installed() ) {
+ $status = 'plugin_absent';
+ } else {
+ $status = 'inactive';
+ }
+ return $status;
+ }
+
+ /**
+ * Checks whether the Product is active
+ *
+ * @return boolean
+ */
+ public static function is_active() {
+ return static::is_plugin_active();
+ }
+
+ /**
+ * Checks whether the plugin is installed
+ *
+ * @return boolean
+ */
+ public static function is_plugin_installed() {
+ $all_plugins = Plugins_Installer::get_plugins();
+ return array_key_exists( static::get_plugin_filename(), $all_plugins );
+ }
+
+ /**
+ * Checks whether the plugin is active
+ *
+ * @return boolean
+ */
+ public static function is_plugin_active() {
+ return Plugins_Installer::is_plugin_active( static::get_plugin_filename() );
+ }
+
+ /**
+ * Checks whether the Jetpack plugin is installed
+ *
+ * @return boolean
+ */
+ public static function is_jetpack_plugin_installed() {
+ $all_plugins = Plugins_Installer::get_plugins();
+ return array_key_exists( self::JETPACK_PLUGIN_FILENAME, $all_plugins );
+ }
+
+ /**
+ * Checks whether the Jetpack plugin is active
+ *
+ * @return boolean
+ */
+ public static function is_jetpack_plugin_active() {
+ return Plugins_Installer::is_plugin_active( self::JETPACK_PLUGIN_FILENAME );
+ }
+
+ /**
+ * Activates the product by installing and activating its plugin
+ *
+ * @return boolean|WP_Error
+ */
+ public static function activate() {
+ if ( static::is_active() ) {
+ return true;
+ }
+
+ if ( ! static::is_plugin_installed() ) {
+ $installed = Plugins_Installer::install_plugin( static::get_plugin_slug() );
+ if ( is_wp_error( $installed ) ) {
+ return $installed;
+ }
+ }
+
+ if ( ! current_user_can( 'activate_plugins' ) ) {
+ return new WP_Error( 'not_allowed', __( 'You are not allowed to activate plugins on this site.', 'jetpack-my-jetpack' ) );
+ }
+
+ $result = activate_plugin( static::get_plugin_filename() );
+ if ( is_wp_error( $result ) ) {
+ return $result;
+ }
+ return is_null( $result );
+ }
+
+ /**
+ * Deactivate the product
+ *
+ * @return boolean
+ */
+ public static function deactivate() {
+ deactivate_plugins( static::get_plugin_filename() );
+ return true;
+ }
+}
diff --git a/projects/packages/my-jetpack/src/products/class-scan.php b/projects/packages/my-jetpack/src/products/class-scan.php
new file mode 100644
index 0000000000000..cc90e315fe992
--- /dev/null
+++ b/projects/packages/my-jetpack/src/products/class-scan.php
@@ -0,0 +1,131 @@
+ true,
+ 'is_free' => true,
+ );
+ }
+
+ /**
+ * Hits the wpcom api to check scan status.
+ *
+ * @todo Maybe add caching.
+ *
+ * @return Object|WP_Error
+ */
+ private static function get_state_from_wpcom() {
+ static $status = null;
+
+ if ( ! is_null( $status ) ) {
+ return $status;
+ }
+
+ $site_id = Jetpack_Options::get_option( 'id' );
+
+ $response = Client::wpcom_json_api_request_as_blog( sprintf( '/sites/%d/scan', $site_id ) . '?force=wpcom', '2', array(), null, 'wpcom' );
+
+ if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
+ return new WP_Error( 'scan_state_fetch_failed' );
+ }
+
+ $body = wp_remote_retrieve_body( $response );
+ $status = json_decode( $body );
+ return $status;
+ }
+
+ /**
+ * Checks whether the current plan (or purchases) of the site already supports the product
+ *
+ * @return boolean
+ */
+ public static function has_required_plan() {
+ $scan_data = static::get_state_from_wpcom();
+ if ( is_wp_error( $scan_data ) ) {
+ return false;
+ }
+ return is_object( $scan_data ) && isset( $scan_data->state ) && 'unavailable' !== $scan_data->state;
+ }
+
+}
diff --git a/projects/packages/my-jetpack/src/products/class-search.php b/projects/packages/my-jetpack/src/products/class-search.php
new file mode 100644
index 0000000000000..008290b2bf882
--- /dev/null
+++ b/projects/packages/my-jetpack/src/products/class-search.php
@@ -0,0 +1,108 @@
+ true,
+ 'currency_code' => 'EUR',
+ 'full_price' => '4.50',
+ 'promotion_percentage' => '50',
+ );
+ }
+
+ /**
+ * Checks whether the current plan of the site already supports the product
+ *
+ * Returns true if it supports. Return false if a purchase is still required.
+ *
+ * Free products will always return true.
+ *
+ * @return boolean
+ */
+ public static function has_required_plan() {
+ return ( new Search_PLan() )->supports_search();
+ }
+}
diff --git a/projects/packages/my-jetpack/src/products/class-videopress.php b/projects/packages/my-jetpack/src/products/class-videopress.php
new file mode 100644
index 0000000000000..abff0f858ade7
--- /dev/null
+++ b/projects/packages/my-jetpack/src/products/class-videopress.php
@@ -0,0 +1,89 @@
+ true,
+ 'currency_code' => 'EUR',
+ 'full_price' => '9',
+ 'promotion_percentage' => '50',
+ );
+ }
+}
diff --git a/projects/packages/my-jetpack/tests/php/assets/backup-mock-plugin.txt b/projects/packages/my-jetpack/tests/php/assets/backup-mock-plugin.txt
new file mode 100644
index 0000000000000..62371573b2ebe
--- /dev/null
+++ b/projects/packages/my-jetpack/tests/php/assets/backup-mock-plugin.txt
@@ -0,0 +1,11 @@
+markTestSkipped( 'avoid bug in PHP 5.6 that throws strict mode warnings for abstract static methods.' );
+ }
+
+ $this->install_mock_plugins();
+ wp_cache_delete( 'plugins', 'plugins' );
+
+ }
+
+ /**
+ * Installs the mock plugin present in the test assets folder as if it was the Boost plugin
+ *
+ * @return void
+ */
+ public function install_mock_plugins() {
+ $plugin_dir = WP_PLUGIN_DIR . '/' . Backup::$plugin_slug;
+ if ( ! file_exists( $plugin_dir ) ) {
+ mkdir( $plugin_dir, 0777, true );
+ }
+ if ( ! file_exists( WP_PLUGIN_DIR . '/jetpack' ) ) {
+ mkdir( WP_PLUGIN_DIR . '/jetpack', 0777, true );
+ }
+ copy( __DIR__ . '/assets/backup-mock-plugin.txt', WP_PLUGIN_DIR . '/' . Backup::$plugin_filename );
+ copy( __DIR__ . '/assets/jetpack-mock-plugin.txt', WP_PLUGIN_DIR . '/jetpack/jetpack.php' );
+ }
+
+ /**
+ * Returning the environment into its initial state.
+ *
+ * @after
+ */
+ public function tear_down() {
+
+ WorDBless_Options::init()->clear_options();
+
+ }
+
+ /**
+ * Tests with Jetpack active
+ */
+ public function test_if_jetpack_active_return_true() {
+ activate_plugin( 'jetpack/jetpack.php' );
+ $this->assertTrue( Backup::is_active() );
+ }
+
+ /**
+ * Tests with Backup active
+ */
+ public function test_if_jetpack_inactive_and_backup_active_return_true() {
+ deactivate_plugins( 'jetpack/jetpack.php' );
+ activate_plugins( Backup::$plugin_filename );
+ $this->assertTrue( Backup::is_active() );
+ }
+
+ /**
+ * Tests with both inactive
+ */
+ public function test_if_jetpack_inactive_and_backup_inactive_return_false() {
+ deactivate_plugins( 'jetpack/jetpack.php' );
+ deactivate_plugins( Backup::$plugin_filename );
+ $this->assertFalse( Backup::is_active() );
+ }
+
+}
diff --git a/projects/packages/my-jetpack/tests/php/test-module-product.php b/projects/packages/my-jetpack/tests/php/test-module-product.php
new file mode 100644
index 0000000000000..642925cfe3745
--- /dev/null
+++ b/projects/packages/my-jetpack/tests/php/test-module-product.php
@@ -0,0 +1,131 @@
+markTestSkipped( 'avoid bug in PHP 5.6 that throws strict mode warnings for abstract static methods.' );
+ }
+
+ $this->install_mock_plugins();
+ wp_cache_delete( 'plugins', 'plugins' );
+
+ // Mock site connection.
+ ( new Tokens() )->update_blog_token( 'test.test.1' );
+ Jetpack_Options::update_option( 'id', 123 );
+
+ Initializer::init();
+
+ self::$user_id = wp_insert_user(
+ array(
+ 'user_login' => 'test_admin',
+ 'user_pass' => '123',
+ 'role' => 'administrator',
+ )
+ );
+ wp_set_current_user( self::$user_id );
+
+ }
+
+ /**
+ * Installs the mock plugin present in the test assets folder as if it was the Boost plugin
+ *
+ * @return void
+ */
+ public function install_mock_plugins() {
+ if ( ! file_exists( WP_PLUGIN_DIR . '/jetpack' ) ) {
+ mkdir( WP_PLUGIN_DIR . '/jetpack', 0777, true );
+ }
+ copy( __DIR__ . '/assets/jetpack-mock-plugin.txt', WP_PLUGIN_DIR . '/jetpack/jetpack.php' );
+ }
+
+ /**
+ * Returning the environment into its initial state.
+ *
+ * @after
+ */
+ public function tear_down() {
+
+ WorDBless_Options::init()->clear_options();
+ WorDBless_Users::init()->clear_all_users();
+
+ }
+
+ /**
+ * Tests exception module name missing
+ */
+ public function test_throws_if_module_name_is_missing() {
+ $this->expectException( \Exception::class );
+ require_once __DIR__ . '/class-broken-product.php';
+ Broken_Product::is_module_active();
+ }
+
+ /**
+ * Test plugin slug and filename are overriden
+ */
+ public function test_plugin_slug_and_filename() {
+ $this->assertSame( Videopress::JETPACK_PLUGIN_SLUG, Videopress::get_plugin_slug() );
+ $this->assertSame( Videopress::JETPACK_PLUGIN_FILENAME, Videopress::get_plugin_filename() );
+ }
+
+ /**
+ * Tests activating/deactivating and checking active
+ */
+ public function test_activate_and_check() {
+ $this->assertFalse( Videopress::is_active() );
+ $this->assertTrue( Videopress::activate() );
+ $this->assertTrue( Videopress::is_active() );
+ $this->assertTrue( Videopress::deactivate() );
+ $this->assertFalse( Videopress::is_active() );
+ $this->assertFalse( Videopress::is_module_active() );
+ $this->assertTrue( Videopress::is_plugin_active() );
+ }
+
+ /**
+ * Assert WP Error is returned if Jetpack fails to activate the module
+ */
+ public function test_return_error_on_activation_failure() {
+ activate_plugins( 'jetpack/jetpack.php' );
+ \Jetpack::$return_false = true;
+ $this->assertTrue( is_wp_error( Videopress::activate() ) );
+
+ // also check deactivate returns false.
+ $this->assertFalse( Videopress::deactivate() );
+ }
+
+}
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 2a599d3c3a841..e3620db23695f 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;
@@ -58,7 +60,16 @@ class Test_Products_Rest extends TestCase {
*/
public function set_up() {
+ if ( version_compare( phpversion(), '5.7', '<=' ) ) {
+ $this->markTestSkipped( 'avoid bug in PHP 5.6 that throws strict mode warnings for abstract static methods.' );
+ }
+
$this->install_mock_plugin();
+ wp_cache_delete( 'plugins', 'plugins' );
+
+ // Mock site connection.
+ ( new Tokens() )->update_blog_token( 'test.test.1' );
+ Jetpack_Options::update_option( 'id', 123 );
global $wp_rest_server;
@@ -128,7 +139,7 @@ public function test_get_products() {
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
- $this->assertEquals( $products, $data['products'] );
+ $this->assertEquals( $products, $data );
}
/**
@@ -169,7 +180,7 @@ public function test_get_product() {
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
- $this->assertEquals( $product, $data['products'][0] );
+ $this->assertEquals( $product, $data );
}
/**
@@ -196,7 +207,8 @@ public function test_activate_boost() {
$response = $this->server->dispatch( $this->request );
$data = $response->get_data();
- $this->assertEquals( 'active', $data['products'][0]['status'] );
+ $this->assertEquals( 200, $response->get_status() );
+ $this->assertEquals( 'active', $data['status'] );
$this->assertTrue( is_plugin_active( $this->boost_mock_filename ) );
}
@@ -214,7 +226,8 @@ public function test_deactivate_boost() {
$response = $this->server->dispatch( $this->request );
$data = $response->get_data();
- $this->assertEquals( 'inactive', $data['products'][0]['status'] );
+ $this->assertEquals( 200, $response->get_status() );
+ $this->assertEquals( 'inactive', $data['status'] );
$this->assertFalse( is_plugin_active( $this->boost_mock_filename ) );
}
@@ -233,9 +246,8 @@ public function test_activate_uninstallable() {
$response = $this->server->dispatch( $this->request );
$data = $response->get_data();
- $this->assertEquals( 'inactive', $data['products'][0]['status'] );
- $this->assertEquals( 'plugin_php_incompatible', $data['error_code'] );
- $this->assertFalse( $data['success'] );
+ $this->assertEquals( 400, $response->get_status() );
+ $this->assertEquals( 'plugin_php_incompatible', $data['code'] );
$this->assertFalse( is_plugin_active( $this->boost_mock_filename ) );
}
diff --git a/projects/packages/partner/CHANGELOG.md b/projects/packages/partner/CHANGELOG.md
index 47591032d7dc0..7a9682bad3c90 100644
--- a/projects/packages/partner/CHANGELOG.md
+++ b/projects/packages/partner/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).
+## [1.6.4] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [1.6.3] - 2022-01-18
### Changed
- Updated package dependencies.
@@ -112,6 +116,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add partner subsidiary id to upgrade URLs.
+[1.6.4]: https://github.com/Automattic/jetpack-partner/compare/v1.6.3...v1.6.4
[1.6.3]: https://github.com/Automattic/jetpack-partner/compare/v1.6.2...v1.6.3
[1.6.2]: https://github.com/Automattic/jetpack-partner/compare/v1.6.1...v1.6.2
[1.6.1]: https://github.com/Automattic/jetpack-partner/compare/v1.6.0...v1.6.1
diff --git a/projects/packages/partner/changelog/update-project-scripts-no-install b/projects/packages/partner/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/partner/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/password-checker/CHANGELOG.md b/projects/packages/password-checker/CHANGELOG.md
index 8ecd21434f877..371ad3435909e 100644
--- a/projects/packages/password-checker/CHANGELOG.md
+++ b/projects/packages/password-checker/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.2.2] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [0.2.1] - 2022-01-18
### Changed
- Updated package dependencies.
@@ -56,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Use `composer update` rather than `install` in scripts, as composer.lock isn't checked in.
+[0.2.2]: https://github.com/Automattic/jetpack-password-checker/compare/v0.2.1...v0.2.2
[0.2.1]: https://github.com/Automattic/jetpack-password-checker/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/Automattic/jetpack-password-checker/compare/v0.1.8...v0.2.0
[0.1.8]: https://github.com/Automattic/jetpack-password-checker/compare/v0.1.7...v0.1.8
diff --git a/projects/packages/password-checker/changelog/update-project-scripts-no-install b/projects/packages/password-checker/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/password-checker/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/plans/.gitattributes b/projects/packages/plans/.gitattributes
new file mode 100644
index 0000000000000..dd08c5ac8f82f
--- /dev/null
+++ b/projects/packages/plans/.gitattributes
@@ -0,0 +1,15 @@
+# Files not needed to be distributed in the package.
+.gitattributes export-ignore
+.github/ export-ignore
+package.json export-ignore
+
+# Files to include in the mirror repo, but excluded via gitignore
+# Remember to end all directories with `/**` to properly tag every file.
+# /src/js/example.min.js production-include
+
+# Files to exclude from the mirror repo, but included in the monorepo.
+# Remember to end all directories with `/**` to properly tag every file.
+.gitignore production-exclude
+changelog/** production-exclude
+phpunit.xml.dist production-exclude
+tests/** production-exclude
diff --git a/projects/packages/plans/.gitignore b/projects/packages/plans/.gitignore
new file mode 100644
index 0000000000000..32d2a35ceea33
--- /dev/null
+++ b/projects/packages/plans/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+node_modules/
+wordpress
diff --git a/projects/packages/plans/CHANGELOG.md b/projects/packages/plans/CHANGELOG.md
new file mode 100644
index 0000000000000..721294abd00ad
--- /dev/null
+++ b/projects/packages/plans/CHANGELOG.md
@@ -0,0 +1,7 @@
+# Changelog
+
+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).
+
diff --git a/projects/packages/plans/README.md b/projects/packages/plans/README.md
new file mode 100644
index 0000000000000..61fc69d944839
--- /dev/null
+++ b/projects/packages/plans/README.md
@@ -0,0 +1,20 @@
+# plans
+
+Fetch information about Jetpack Plans from wpcom
+
+## How to install plans
+
+### Installation From Git Repo
+
+## Contribute
+
+## Get Help
+
+## Security
+
+Need to report a security vulnerability? Go to [https://automattic.com/security/](https://automattic.com/security/) or directly to our security bug bounty site [https://hackerone.com/automattic](https://hackerone.com/automattic).
+
+## License
+
+plans is licensed under [GNU General Public License v2 (or later)](./LICENSE.txt)
+
diff --git a/projects/packages/plans/changelog/.gitkeep b/projects/packages/plans/changelog/.gitkeep
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/projects/packages/plans/changelog/extract-jetpack-plans b/projects/packages/plans/changelog/extract-jetpack-plans
new file mode 100644
index 0000000000000..65c54db985465
--- /dev/null
+++ b/projects/packages/plans/changelog/extract-jetpack-plans
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Package created
diff --git a/projects/packages/plans/changelog/update-plans-mirror-repo b/projects/packages/plans/changelog/update-plans-mirror-repo
new file mode 100644
index 0000000000000..d7a7b73a40586
--- /dev/null
+++ b/projects/packages/plans/changelog/update-plans-mirror-repo
@@ -0,0 +1,4 @@
+Significance: patch
+Type: changed
+
+Core: update composer info to support mirror repo.
diff --git a/projects/packages/plans/composer.json b/projects/packages/plans/composer.json
new file mode 100644
index 0000000000000..74f0368b39d43
--- /dev/null
+++ b/projects/packages/plans/composer.json
@@ -0,0 +1,61 @@
+{
+ "name": "automattic/jetpack-plans",
+ "description": "Fetch information about Jetpack Plans from wpcom",
+ "type": "library",
+ "license": "GPL-2.0-or-later",
+ "require": {
+ "automattic/jetpack-connection": "^1.36"
+ },
+ "require-dev": {
+ "yoast/phpunit-polyfills": "1.0.3",
+ "automattic/jetpack-changelogger": "^3.0",
+ "automattic/jetpack-options": "^1.14",
+ "automattic/jetpack-status": "^1.10",
+ "automattic/wordbless": "@dev"
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "scripts": {
+ "phpunit": [
+ "./vendor/phpunit/phpunit/phpunit --colors=always"
+ ],
+ "test-coverage": [
+ "php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
+ ],
+ "test-php": [
+ "@composer phpunit"
+ ],
+ "post-update-cmd": "php -r \"copy('vendor/automattic/wordbless/src/dbless-wpdb.php', 'wordpress/wp-content/db.php');\"",
+ "build-production": "echo 'Add your build step to composer.json, please!'",
+ "build-development": "echo 'Add your build step to composer.json, please!'"
+ },
+ "repositories": [
+ {
+ "type": "path",
+ "url": "../../packages/*",
+ "options": {
+ "monorepo": true
+ }
+ }
+ ],
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "extra": {
+ "autotagger": true,
+ "mirror-repo": "Automattic/jetpack-plans",
+ "changelogger": {
+ "link-template": "https://github.com/Automattic/jetpack-plans/compare/v${old}...v${new}"
+ },
+ "branch-alias": {
+ "dev-master": "0.1.x-dev"
+ }
+ },
+ "config": {
+ "allow-plugins": {
+ "roots/wordpress-core-installer": true
+ }
+ }
+}
diff --git a/projects/packages/plans/package.json b/projects/packages/plans/package.json
new file mode 100644
index 0000000000000..f0aefec8c45cb
--- /dev/null
+++ b/projects/packages/plans/package.json
@@ -0,0 +1,29 @@
+{
+ "private": true,
+ "name": "@automattic/jetpack-plans",
+ "version": "0.1.0-alpha",
+ "description": "Fetch information about Jetpack Plans from wpcom",
+ "homepage": "https://jetpack.com",
+ "bugs": {
+ "url": "https://github.com/Automattic/jetpack/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/Automattic/jetpack.git"
+ },
+ "license": "GPL-2.0-or-later",
+ "author": "Automattic",
+ "scripts": {
+ "build": "echo 'Not implemented.",
+ "build-js": "echo 'Not implemented.",
+ "build-production": "echo 'Not implemented.",
+ "build-production-js": "echo 'Not implemented.",
+ "clean": "true"
+ },
+ "devDependencies": {},
+ "engines": {
+ "node": "^14.18.3 || ^16.13.2",
+ "pnpm": "^6.23.6",
+ "yarn": "use pnpm instead - see docs/yarn-upgrade.md"
+ }
+}
diff --git a/projects/packages/plans/phpunit.xml.dist b/projects/packages/plans/phpunit.xml.dist
new file mode 100644
index 0000000000000..3223c32458db2
--- /dev/null
+++ b/projects/packages/plans/phpunit.xml.dist
@@ -0,0 +1,14 @@
+
+
+
+ tests/php
+
+
+
+
+
+
+ src
+
+
+
diff --git a/projects/packages/plans/src/class-current-plan.php b/projects/packages/plans/src/class-current-plan.php
new file mode 100644
index 0000000000000..10f71cc109bd8
--- /dev/null
+++ b/projects/packages/plans/src/class-current-plan.php
@@ -0,0 +1,362 @@
+ array(
+ 'plans' => array(
+ 'jetpack_free',
+ ),
+ 'supports' => array(
+ 'opentable',
+ 'calendly',
+ 'send-a-message',
+ 'whatsapp-button',
+ 'social-previews',
+ 'videopress',
+
+ 'core/video',
+ 'core/cover',
+ 'core/audio',
+ ),
+ ),
+ 'personal' => array(
+ 'plans' => array(
+ 'jetpack_personal',
+ 'jetpack_personal_monthly',
+ 'personal-bundle',
+ 'personal-bundle-monthly',
+ 'personal-bundle-2y',
+ ),
+ 'supports' => array(
+ 'akismet',
+ 'recurring-payments',
+ 'premium-content/container',
+ 'videopress',
+ ),
+ ),
+ 'premium' => array(
+ 'plans' => array(
+ 'jetpack_premium',
+ 'jetpack_premium_monthly',
+ 'value_bundle',
+ 'value_bundle-monthly',
+ 'value_bundle-2y',
+ ),
+ 'supports' => array(
+ 'donations',
+ 'simple-payments',
+ 'vaultpress',
+ 'videopress',
+ 'republicize',
+ ),
+ ),
+ 'security' => array(
+ 'plans' => array(
+ 'jetpack_security_daily',
+ 'jetpack_security_daily_monthly',
+ 'jetpack_security_realtime',
+ 'jetpack_security_realtime_monthly',
+ 'jetpack_security_t1_yearly',
+ 'jetpack_security_t1_monthly',
+ 'jetpack_security_t2_yearly',
+ 'jetpack_security_t2_monthly',
+ ),
+ 'supports' => array(),
+ ),
+ 'business' => array(
+ 'plans' => array(
+ 'jetpack_business',
+ 'jetpack_business_monthly',
+ 'business-bundle',
+ 'business-bundle-monthly',
+ 'business-bundle-2y',
+ 'ecommerce-bundle',
+ 'ecommerce-bundle-monthly',
+ 'ecommerce-bundle-2y',
+ ),
+ 'supports' => array(),
+ ),
+
+ 'complete' => array(
+ 'plans' => array(
+ 'jetpack_complete',
+ 'jetpack_complete_monthly',
+ 'vip',
+ ),
+ 'supports' => array(),
+ ),
+ );
+
+ /**
+ * Given a response to the `/sites/%d` endpoint, will parse the response and attempt to set the
+ * site's plan and products from the response.
+ *
+ * @param array $response The response from `/sites/%d`.
+ * @return bool Was the plan successfully updated?
+ */
+ public static function update_from_sites_response( $response ) {
+ // Bail if there was an error or malformed response.
+ if ( is_wp_error( $response ) || ! is_array( $response ) || ! isset( $response['body'] ) ) {
+ return false;
+ }
+
+ $body = wp_remote_retrieve_body( $response );
+ if ( is_wp_error( $body ) ) {
+ return false;
+ }
+
+ // Decode the results.
+ $results = json_decode( $body, true );
+
+ if ( ! is_array( $results ) ) {
+ return false;
+ }
+
+ if ( isset( $results['products'] ) ) {
+ // Store the site's products in an option and return true if updated.
+ self::store_data_in_option( self::SITE_PRODUCTS_OPTION, $results['products'] );
+ }
+
+ if ( ! isset( $results['plan'] ) ) {
+ return false;
+ }
+
+ $current_plan = get_option( self::PLAN_OPTION, array() );
+
+ if ( ! empty( $current_plan ) && $current_plan === $results['plan'] ) {
+ // Bail if the plans array hasn't changed.
+ return false;
+ }
+
+ // Store the new plan in an option and return true if updated.
+ $result = self::store_data_in_option( self::PLAN_OPTION, $results['plan'] );
+
+ if ( $result ) {
+ // Reset the cache since we've just updated the plan.
+ self::$active_plan_cache = null;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Store data in an option.
+ *
+ * @param string $option The name of the option that will store the data.
+ * @param array $data Data to be store in an option.
+ * @return bool Were the subscriptions successfully updated?
+ */
+ private static function store_data_in_option( $option, $data ) {
+ $result = update_option( $option, $data, true );
+
+ // If something goes wrong with the update, so delete the current option and then update it.
+ if ( ! $result ) {
+ delete_option( $option );
+ $result = update_option( $option, $data, true );
+ }
+
+ return $result;
+ }
+
+ /**
+ * Make an API call to WordPress.com for plan status
+ *
+ * @uses Jetpack_Options::get_option()
+ * @uses Client::wpcom_json_api_request_as_blog()
+ * @uses update_option()
+ *
+ * @access public
+ * @static
+ *
+ * @return bool True if plan is updated, false if no update
+ */
+ public static function refresh_from_wpcom() {
+ // Make the API request.
+ $request = sprintf( '/sites/%d', Jetpack_Options::get_option( 'id' ) );
+ $response = Client::wpcom_json_api_request_as_blog( $request, '1.1' );
+
+ return self::update_from_sites_response( $response );
+ }
+
+ /**
+ * Get the plan that this Jetpack site is currently using.
+ *
+ * @uses get_option()
+ *
+ * @access public
+ * @static
+ *
+ * @return array Active Jetpack plan details
+ */
+ public static function get() {
+ // this can be expensive to compute so we cache for the duration of a request.
+ if ( is_array( self::$active_plan_cache ) && ! empty( self::$active_plan_cache ) ) {
+ return self::$active_plan_cache;
+ }
+
+ $plan = get_option( self::PLAN_OPTION, array() );
+
+ // Set the default options.
+ $plan = wp_parse_args(
+ $plan,
+ array(
+ 'product_slug' => 'jetpack_free',
+ 'class' => 'free',
+ 'features' => array(
+ 'active' => array(),
+ ),
+ )
+ );
+
+ list( $plan['class'], $supports ) = self::get_class_and_features( $plan['product_slug'] );
+
+ // get available features if Jetpack is active.
+ if ( class_exists( 'Jetpack' ) ) {
+ foreach ( Jetpack::get_available_modules() as $module_slug ) {
+ $module = Jetpack::get_module( $module_slug );
+ if ( ! isset( $module ) || ! is_array( $module ) ) {
+ continue;
+ }
+ if ( in_array( 'free', $module['plan_classes'], true ) || in_array( $plan['class'], $module['plan_classes'], true ) ) {
+ $supports[] = $module_slug;
+ }
+ }
+ }
+
+ $plan['supports'] = $supports;
+
+ self::$active_plan_cache = $plan;
+
+ return $plan;
+ }
+
+ /**
+ * Get the site's products.
+ *
+ * @uses get_option()
+ *
+ * @access public
+ * @static
+ *
+ * @return array Active Jetpack products
+ */
+ public static function get_products() {
+ return get_option( self::SITE_PRODUCTS_OPTION, array() );
+ }
+
+ /**
+ * Get the class of plan and a list of features it supports
+ *
+ * @param string $plan_slug The plan that we're interested in.
+ * @return array Two item array, the plan class and the an array of features.
+ */
+ private static function get_class_and_features( $plan_slug ) {
+ $features = array();
+ foreach ( self::PLAN_DATA as $class => $details ) {
+ $features = array_merge( $features, $details['supports'] );
+ if ( in_array( $plan_slug, $details['plans'], true ) ) {
+ return array( $class, $features );
+ }
+ }
+ return array( 'free', self::PLAN_DATA['free']['supports'] );
+ }
+
+ /**
+ * Gets the minimum plan slug that supports the given feature
+ *
+ * @param string $feature The name of the feature.
+ * @return string|bool The slug for the minimum plan that supports.
+ * the feature or false if not found
+ */
+ public static function get_minimum_plan_for_feature( $feature ) {
+ foreach ( self::PLAN_DATA as $details ) {
+ if ( in_array( $feature, $details['supports'], true ) ) {
+ return $details['plans'][0];
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determine whether the active plan supports a particular feature
+ *
+ * @uses Jetpack_Plan::get()
+ *
+ * @access public
+ * @static
+ *
+ * @param string $feature The module or feature to check.
+ *
+ * @return bool True if plan supports feature, false if not
+ */
+ public static function supports( $feature ) {
+ // Search product bypasses plan feature check.
+ if ( 'search' === $feature && (bool) get_option( 'has_jetpack_search_product' ) ) {
+ return true;
+ }
+
+ // As of Q3 2021 - a videopress free tier is available to all plans.
+ if ( 'videopress' === $feature ) {
+ return true;
+ }
+
+ $plan = self::get();
+
+ // Manually mapping WordPress.com features to Jetpack module slugs.
+ foreach ( $plan['features']['active'] as $wpcom_feature ) {
+ switch ( $wpcom_feature ) {
+ case 'wordads-jetpack':
+ // WordAds are supported for this site.
+ if ( 'wordads' === $feature ) {
+ return true;
+ }
+ break;
+ }
+ }
+
+ if (
+ in_array( $feature, $plan['supports'], true )
+ || in_array( $feature, $plan['features']['active'], true )
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/projects/packages/plans/src/class-plans.php b/projects/packages/plans/src/class-plans.php
new file mode 100644
index 0000000000000..b96ffdb4dcdbd
--- /dev/null
+++ b/projects/packages/plans/src/class-plans.php
@@ -0,0 +1,80 @@
+ 'GET',
+ 'headers' => array(
+ 'X-Forwarded-For' => ( new Automattic\Jetpack\Status\Visitor() )->get_ip( true ),
+ ),
+ ),
+ null,
+ 'rest'
+ );
+
+ $body = wp_remote_retrieve_body( $request );
+ if ( 200 === wp_remote_retrieve_response_code( $request ) ) {
+ return json_decode( $body );
+ } else {
+ return $body;
+ }
+ }
+
+ /**
+ * Get plan information for a plan given its slug
+ *
+ * @since-jetpack 7.7.0
+ *
+ * @param string $plan_slug Plan slug.
+ *
+ * @return object The plan object
+ */
+ public static function get_plan( $plan_slug ) {
+ $plans = self::get_plans();
+ if ( ! is_array( $plans ) ) {
+ return;
+ }
+
+ foreach ( $plans as $plan ) {
+ if ( $plan_slug === $plan->product_slug ) {
+ return $plan;
+ }
+ }
+ }
+}
diff --git a/projects/packages/plans/src/example.php b/projects/packages/plans/src/example.php
new file mode 100644
index 0000000000000..a788a63305dc4
--- /dev/null
+++ b/projects/packages/plans/src/example.php
@@ -0,0 +1,8 @@
+get_free_plan(), true );
+
+ $option = get_option( 'jetpack_active_plan' );
+ $this->assertSame( 'jetpack_free', $option['product_slug'] );
+
+ // Set up an issue where the value in cache does not match the DB, so the DB update fails.
+ Jetpack_Options::update_raw_option( 'jetpack_active_plan', $this->get_personal_plan(), true );
+
+ $this->assertTrue( Jetpack_Plan::update_from_sites_response( $this->get_response_personal_plan() ) );
+ }
+
+ /**
+ * @dataProvider get_update_from_sites_response_data
+ */
+ public function test_update_from_sites_response( $response, $expected_plan_slug_after, $expected_return, $initial_option = null ) {
+
+ if ( ! is_null( $initial_option ) ) {
+ update_option( 'jetpack_active_plan', $initial_option, true );
+ }
+
+ $this->assertSame( $expected_return, Jetpack_Plan::update_from_sites_response( $response ) );
+
+ $plan = Jetpack_Plan::get();
+ $this->assertSame( $expected_plan_slug_after, $plan['product_slug'] );
+ }
+
+ public function get_update_from_sites_response_data() {
+ return array(
+ 'is_errored_response' => array(
+ $this->get_errored_sites_response(),
+ 'jetpack_free',
+ false,
+ ),
+ 'response_is_empty' => array(
+ $this->get_mocked_response( 200, '' ),
+ 'jetpack_free',
+ false,
+ ),
+ 'response_does_not_have_body' => array(
+ array( 'code' => 400 ),
+ 'jetpack_free',
+ false,
+ ),
+ 'response_does_not_have_plan' => array(
+ array(
+ 'code' => 200,
+ array(),
+ ),
+ 'jetpack_free',
+ false,
+ ),
+ 'initially_empty_option_to_free' => array(
+ $this->get_response_free_plan(),
+ 'jetpack_free',
+ true,
+ ),
+ 'initially_empty_to_personal' => array(
+ $this->get_response_personal_plan(),
+ 'jetpack_personal',
+ true,
+ ),
+ 'initially_free_to_personal' => array(
+ $this->get_response_personal_plan(),
+ 'jetpack_personal',
+ true,
+ $this->get_free_plan(),
+ ),
+ 'initially_personal_to_free' => array(
+ $this->get_response_free_plan(),
+ 'jetpack_free',
+ true,
+ $this->get_personal_plan(),
+ ),
+ 'initially_free_no_change' => array(
+ $this->get_response_free_plan(),
+ 'jetpack_free',
+ false,
+ $this->get_free_plan(),
+ ),
+ 'initially_personal_to_changed_personal' => array(
+ $this->get_response_changed_personal_plan(),
+ 'jetpack_personal',
+ true,
+ $this->get_response_personal_plan(),
+ ),
+ );
+ }
+
+ private function get_response_free_plan() {
+ return $this->get_successful_plan_response( $this->get_free_plan() );
+ }
+
+ private function get_response_personal_plan() {
+ return $this->get_successful_plan_response( $this->get_personal_plan() );
+ }
+
+ private function get_response_changed_personal_plan() {
+ return $this->get_successful_plan_response( $this->get_changed_personal_plan() );
+ }
+
+ private function get_successful_plan_response( $plan_response ) {
+ $body = wp_json_encode(
+ array(
+ 'plan' => $plan_response,
+ )
+ );
+ return $this->get_mocked_response( 200, $body );
+ }
+
+ private function get_errored_sites_response() {
+ return $this->get_mocked_response( 400, new WP_Error() );
+ }
+
+ private function get_mocked_response( $code, $body ) {
+ return array(
+ 'code' => $code,
+ 'body' => $body,
+ );
+ }
+
+ private function get_free_plan() {
+ return array(
+ 'product_id' => 2002,
+ 'product_slug' => 'jetpack_free',
+ 'product_name_short' => 'Free',
+ 'expired' => false,
+ 'user_is_owner' => false,
+ 'is_free' => true,
+ 'features' => array(
+ 'active' => array(
+ 'akismet',
+ 'support',
+ ),
+ 'available' => array(
+ 'akismet' => array(
+ 'jetpack_premium',
+ 'jetpack_business',
+ 'jetpack_premium_monthly',
+ 'jetpack_business_monthly',
+ ),
+ 'vaultpress-backups' => array(
+ 'jetpack_premium',
+ 'jetpack_business',
+ 'jetpack_premium_monthly',
+ 'jetpack_business_monthly',
+ ),
+ 'vaultpress-backup-archive' => array(
+ 'jetpack_premium',
+ 'jetpack_business',
+ 'jetpack_premium_monthly',
+ 'jetpack_business_monthly',
+ ),
+ 'vaultpress-storage-space' => array(
+ 'jetpack_premium',
+ 'jetpack_business',
+ 'jetpack_premium_monthly',
+ 'jetpack_business_monthly',
+ ),
+ 'vaultpress-automated-restores' => array(
+ 'jetpack_premium',
+ 'jetpack_business',
+ 'jetpack_premium_monthly',
+ 'jetpack_business_monthly',
+ ),
+ 'simple-payments' => array(
+ 'jetpack_premium',
+ 'jetpack_business',
+ 'jetpack_premium_monthly',
+ 'jetpack_business_monthly',
+ ),
+ 'support' => array(
+ 'jetpack_premium',
+ 'jetpack_business',
+ 'jetpack_personal',
+ 'jetpack_premium_monthly',
+ 'jetpack_business_monthly',
+ 'jetpack_personal_monthly',
+ ),
+ 'premium-themes' => array(
+ 'jetpack_business',
+ 'jetpack_business_monthly',
+ ),
+ 'vaultpress-security-scanning' => array(
+ 'jetpack_business',
+ 'jetpack_business_monthly',
+ ),
+ 'polldaddy' => array(
+ 'jetpack_business',
+ 'jetpack_business_monthly',
+ ),
+ ),
+ ),
+ );
+ }
+
+ private function get_changed_personal_plan() {
+ $changed_personal_plan = $this->get_personal_plan();
+
+ $changed_personal_plan['features']['available']['test_feature'] = array( 'jetpack_free' );
+ return $changed_personal_plan;
+ }
+
+ private function get_personal_plan() {
+ return array(
+ 'product_id' => 2005,
+ 'product_slug' => 'jetpack_personal',
+ 'product_name_short' => 'Personal',
+ 'expired' => false,
+ 'user_is_owner' => false,
+ 'is_free' => false,
+ 'features' => array(
+ 'active' => array(
+ 'support',
+ ),
+ 'available' => array(
+ 'akismet' => array(
+ 'jetpack_free',
+ 'jetpack_premium',
+ 'jetpack_business',
+ 'jetpack_premium_monthly',
+ 'jetpack_business_monthly',
+ ),
+ 'support' => array(
+ 'jetpack_free',
+ 'jetpack_premium',
+ 'jetpack_business',
+ 'jetpack_premium_monthly',
+ 'jetpack_business_monthly',
+ 'jetpack_personal_monthly',
+ ),
+ 'vaultpress-backups' => array(
+ 'jetpack_premium',
+ 'jetpack_business',
+ 'jetpack_premium_monthly',
+ 'jetpack_business_monthly',
+ ),
+ 'vaultpress-backup-archive' => array(
+ 'jetpack_premium',
+ 'jetpack_business',
+ 'jetpack_premium_monthly',
+ 'jetpack_business_monthly',
+ ),
+ 'vaultpress-storage-space' => array(
+ 'jetpack_premium',
+ 'jetpack_business',
+ 'jetpack_premium_monthly',
+ 'jetpack_business_monthly',
+ ),
+ 'vaultpress-automated-restores' => array(
+ 'jetpack_premium',
+ 'jetpack_business',
+ 'jetpack_premium_monthly',
+ 'jetpack_business_monthly',
+ ),
+ 'simple-payments' => array(
+ 'jetpack_premium',
+ 'jetpack_business',
+ 'jetpack_premium_monthly',
+ 'jetpack_business_monthly',
+ ),
+ 'premium-themes' => array(
+ 'jetpack_business',
+ 'jetpack_business_monthly',
+ ),
+ 'vaultpress-security-scanning' => array(
+ 'jetpack_business',
+ 'jetpack_business_monthly',
+ ),
+ 'polldaddy' => array(
+ 'jetpack_business',
+ 'jetpack_business_monthly',
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+// phpcs:enable
diff --git a/projects/packages/plugins-installer/.gitattributes b/projects/packages/plugins-installer/.gitattributes
new file mode 100644
index 0000000000000..dd08c5ac8f82f
--- /dev/null
+++ b/projects/packages/plugins-installer/.gitattributes
@@ -0,0 +1,15 @@
+# Files not needed to be distributed in the package.
+.gitattributes export-ignore
+.github/ export-ignore
+package.json export-ignore
+
+# Files to include in the mirror repo, but excluded via gitignore
+# Remember to end all directories with `/**` to properly tag every file.
+# /src/js/example.min.js production-include
+
+# Files to exclude from the mirror repo, but included in the monorepo.
+# Remember to end all directories with `/**` to properly tag every file.
+.gitignore production-exclude
+changelog/** production-exclude
+phpunit.xml.dist production-exclude
+tests/** production-exclude
diff --git a/projects/packages/plugins-installer/.gitignore b/projects/packages/plugins-installer/.gitignore
new file mode 100644
index 0000000000000..140fd587d2d52
--- /dev/null
+++ b/projects/packages/plugins-installer/.gitignore
@@ -0,0 +1,2 @@
+vendor/
+node_modules/
diff --git a/projects/packages/plugins-installer/.phpcs.dir.xml b/projects/packages/plugins-installer/.phpcs.dir.xml
new file mode 100644
index 0000000000000..fd41c865e8f25
--- /dev/null
+++ b/projects/packages/plugins-installer/.phpcs.dir.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/projects/packages/plugins-installer/CHANGELOG.md b/projects/packages/plugins-installer/CHANGELOG.md
new file mode 100644
index 0000000000000..3f2733da86b48
--- /dev/null
+++ b/projects/packages/plugins-installer/CHANGELOG.md
@@ -0,0 +1,17 @@
+# Changelog
+
+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.1.0 - 2022-02-02
+### Added
+- First version
+- New functions to safely check plugin statuses
+
+### Changed
+- Build: add missing mirror repo details, so package can be deployed.
+
+### Fixed
+- Fix method logic
diff --git a/projects/packages/plugins-installer/README.md b/projects/packages/plugins-installer/README.md
new file mode 100644
index 0000000000000..b766b09457636
--- /dev/null
+++ b/projects/packages/plugins-installer/README.md
@@ -0,0 +1,20 @@
+# plugins-installer
+
+Handle installation of plugins from WP.org
+
+## How to install plugins-installer
+
+### Installation From Git Repo
+
+## Contribute
+
+## Get Help
+
+## Security
+
+Need to report a security vulnerability? Go to [https://automattic.com/security/](https://automattic.com/security/) or directly to our security bug bounty site [https://hackerone.com/automattic](https://hackerone.com/automattic).
+
+## License
+
+plugins-installer is licensed under [GNU General Public License v2 (or later)](./LICENSE.txt)
+
diff --git a/projects/packages/plugins-installer/changelog/.gitkeep b/projects/packages/plugins-installer/changelog/.gitkeep
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/projects/packages/plugins-installer/composer.json b/projects/packages/plugins-installer/composer.json
new file mode 100644
index 0000000000000..dd16fd5c3ae33
--- /dev/null
+++ b/projects/packages/plugins-installer/composer.json
@@ -0,0 +1,51 @@
+{
+ "name": "automattic/jetpack-plugins-installer",
+ "description": "Handle installation of plugins from WP.org",
+ "type": "library",
+ "license": "GPL-2.0-or-later",
+ "require": {
+ "automattic/jetpack-a8c-mc-stats": "^1.4"
+ },
+ "require-dev": {
+ "yoast/phpunit-polyfills": "1.0.3",
+ "automattic/jetpack-changelogger": "^3.0"
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "scripts": {
+ "phpunit": [
+ "./vendor/phpunit/phpunit/phpunit --colors=always"
+ ],
+ "test-coverage": [
+ "php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
+ ],
+ "test-php": [
+ "@composer phpunit"
+ ]
+ },
+ "repositories": [
+ {
+ "type": "path",
+ "url": "../../packages/*",
+ "options": {
+ "monorepo": true
+ }
+ }
+ ],
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.1.x-dev"
+ },
+ "mirror-repo": "Automattic/jetpack-plugins-installer",
+ "changelogger": {
+ "link-template": "https://github.com/Automattic/jetpack-plugins-installer/compare/v${old}...v${new}"
+ },
+ "autotagger": true,
+ "textdomain": "jetpack-plugins-installer"
+ }
+}
diff --git a/projects/packages/plugins-installer/phpunit.xml.dist b/projects/packages/plugins-installer/phpunit.xml.dist
new file mode 100644
index 0000000000000..3223c32458db2
--- /dev/null
+++ b/projects/packages/plugins-installer/phpunit.xml.dist
@@ -0,0 +1,14 @@
+
+
+
+ tests/php
+
+
+
+
+
+
+ src
+
+
+
diff --git a/projects/packages/plugins-installer/src/class-automatic-install-skin.php b/projects/packages/plugins-installer/src/class-automatic-install-skin.php
new file mode 100644
index 0000000000000..ae129c5975b32
--- /dev/null
+++ b/projects/packages/plugins-installer/src/class-automatic-install-skin.php
@@ -0,0 +1,155 @@
+fs_connect( array( WP_CONTENT_DIR, WP_PLUGIN_DIR ) );
+ if ( ! $result ) {
+ // set the string here since they are not available just yet.
+ $upgrader->generic_strings();
+ $this->feedback( 'fs_unavailable' );
+ }
+ }
+
+ /**
+ * Overwrites the error function
+ *
+ * @param \WP_Error|mixed $error The error object.
+ */
+ public function error( $error ) {
+ if ( is_wp_error( $error ) ) {
+ $this->feedback( $error );
+ }
+ }
+
+ /**
+ * Set the main error code.
+ *
+ * Don't set the process_failed as code since it is not that helpful unless we don't have one already set
+ *
+ * @param string $code The error code.
+ * @return void
+ */
+ private function set_main_error_code( $code ) {
+ $this->main_error_code = ( 'process_failed' === $code && $this->main_error_code ? $this->main_error_code : $code );
+ }
+
+ /**
+ * Set the main error message.
+ *
+ * Don't set the process_failed as message since it is not that helpful unless we don't have one already set
+ *
+ * @param string $message The error message.
+ * @param string $code The error code.
+ * @return void
+ */
+ private function set_main_error_message( $message, $code ) {
+ $this->main_error_message = ( 'process_failed' === $code && $this->main_error_message ? $this->main_error_message : $message );
+ }
+
+ /**
+ * Get the main error code
+ *
+ * @return string
+ */
+ public function get_main_error_code() {
+ return $this->main_error_code;
+ }
+
+ /**
+ * Get the main error message
+ *
+ * @return string
+ */
+ public function get_main_error_message() {
+ return $this->main_error_message;
+ }
+
+ /**
+ * Overwrites the feedback function
+ *
+ * @param string|array|WP_Error $data Data.
+ * @param mixed ...$args Optional text replacements.
+ */
+ public function feedback( $data, ...$args ) {
+
+ $current_error = null;
+ if ( is_wp_error( $data ) ) {
+ $this->set_main_error_code( $data->get_error_code() );
+ $string = $data->get_error_message();
+ } elseif ( is_array( $data ) ) {
+ return;
+ } else {
+ $string = $data;
+ }
+
+ if ( ! empty( $this->upgrader->strings[ $string ] ) ) {
+ $this->set_main_error_code( $string );
+
+ $current_error = $string;
+ $string = $this->upgrader->strings[ $string ];
+ }
+
+ if ( strpos( $string, '%' ) !== false ) {
+ if ( ! empty( $args ) ) {
+ $string = vsprintf( $string, $args );
+ }
+ }
+
+ $string = trim( $string );
+ $string = wp_kses(
+ $string,
+ array(
+ 'a' => array(
+ 'href' => true,
+ ),
+ 'br' => true,
+ 'em' => true,
+ 'strong' => true,
+ )
+ );
+
+ $this->set_main_error_message( $string, $current_error );
+ $this->messages[] = $string;
+ }
+}
diff --git a/projects/packages/plugins-installer/src/class-plugins-installer.php b/projects/packages/plugins-installer/src/class-plugins-installer.php
new file mode 100644
index 0000000000000..bae23baa0a249
--- /dev/null
+++ b/projects/packages/plugins-installer/src/class-plugins-installer.php
@@ -0,0 +1,238 @@
+install( $zip_url );
+
+ if ( is_wp_error( $result ) ) {
+ $mc_stats->add( 'install-plugin', "fail-$slug" );
+ return $result;
+ }
+
+ $plugin = self::get_plugin_id_by_slug( $slug );
+ $error_code = 'install_error';
+ if ( ! $plugin ) {
+ $error = __( 'There was an error installing your plugin', 'jetpack-plugins-installer' );
+ }
+
+ if ( ! $result ) {
+ $error_code = $upgrader->skin->get_main_error_code();
+ $message = $upgrader->skin->get_main_error_message();
+ $error = $message ? $message : __( 'An unknown error occurred during installation', 'jetpack-plugins-installer' );
+ }
+
+ if ( ! empty( $error ) ) {
+ if ( 'download_failed' === $error_code ) {
+ // For backwards compatibility: versions prior to 3.9 would return no_package instead of download_failed.
+ $error_code = 'no_package';
+ }
+
+ $mc_stats->add( 'install-plugin', "fail-$slug" );
+ return new WP_Error( $error_code, $error, 400 );
+ }
+
+ $mc_stats->add( 'install-plugin', "success-$slug" );
+ return (array) $upgrader->skin->get_upgrade_messages();
+ }
+
+ /**
+ * Get WordPress.org zip download link from a plugin slug
+ *
+ * @param string $plugin_slug Plugin slug.
+ */
+ protected static function generate_wordpress_org_plugin_download_link( $plugin_slug ) {
+ return "https://downloads.wordpress.org/plugin/$plugin_slug.latest-stable.zip";
+ }
+
+ /**
+ * Get the plugin ID (composed of the plugin slug and the name of the main plugin file) from a plugin slug.
+ *
+ * @param string $slug Plugin slug.
+ */
+ public static function get_plugin_id_by_slug( $slug ) {
+ // Check if get_plugins() function exists. This is required on the front end of the
+ // site, since it is in a file that is normally only loaded in the admin.
+ if ( ! function_exists( 'get_plugins' ) ) {
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
+ }
+
+ /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
+ $plugins = apply_filters( 'all_plugins', get_plugins() );
+ if ( ! is_array( $plugins ) ) {
+ return false;
+ }
+
+ foreach ( $plugins as $plugin_file => $plugin_data ) {
+ if ( self::get_slug_from_file_path( $plugin_file ) === $slug ) {
+ return $plugin_file;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the plugin slug from the plugin ID (composed of the plugin slug and the name of the main plugin file)
+ *
+ * @param string $plugin_file Plugin file (ID -- e.g. hello-dolly/hello.php).
+ */
+ protected static function get_slug_from_file_path( $plugin_file ) {
+ // Similar to get_plugin_slug() method.
+ $slug = dirname( $plugin_file );
+ if ( '.' === $slug ) {
+ $slug = preg_replace( '/(.+)\.php$/', '$1', $plugin_file );
+ }
+
+ return $slug;
+ }
+
+ /**
+ * Get the activation status for a plugin.
+ *
+ * @since-jetpack 8.9.0
+ *
+ * @param string $plugin_file The plugin file to check.
+ * @return string Either 'network-active', 'active' or 'inactive'.
+ */
+ public static function get_plugin_status( $plugin_file ) {
+ if ( self::is_plugin_active_for_network( $plugin_file ) ) {
+ return 'network-active';
+ }
+
+ if ( self::is_plugin_active( $plugin_file ) ) {
+ return 'active';
+ }
+
+ return 'inactive';
+ }
+
+ /**
+ * Safely checks if the plugin is active
+ *
+ * @since $next-version$
+ *
+ * @param string $plugin_file The plugin file to check.
+ * @return bool
+ */
+ public static function is_plugin_active( $plugin_file ) {
+ self::ensure_plugin_functions_are_loaded();
+ return is_plugin_active( $plugin_file );
+ }
+
+ /**
+ * Safely checks if the plugin is active for network
+ *
+ * @since $next-version$
+ *
+ * @param string $plugin_file The plugin file to check.
+ * @return bool
+ */
+ public static function is_plugin_active_for_network( $plugin_file ) {
+ self::ensure_plugin_functions_are_loaded();
+ return is_plugin_active_for_network( $plugin_file );
+ }
+
+ /**
+ * Returns a list of all plugins in the site.
+ *
+ * @since-jetpack 8.9.0
+ * @uses get_plugins()
+ *
+ * @return array
+ */
+ public static function get_plugins() {
+ self::ensure_plugin_functions_are_loaded();
+ /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
+ $plugins = apply_filters( 'all_plugins', get_plugins() );
+
+ if ( is_array( $plugins ) && ! empty( $plugins ) ) {
+ foreach ( $plugins as $plugin_slug => $plugin_data ) {
+ $plugins[ $plugin_slug ]['active'] = in_array(
+ self::get_plugin_status( $plugin_slug ),
+ array( 'active', 'network-active' ),
+ true
+ );
+ }
+ return $plugins;
+ }
+
+ return array();
+ }
+}
diff --git a/projects/packages/plugins-installer/tests/php/bootstrap.php b/projects/packages/plugins-installer/tests/php/bootstrap.php
new file mode 100644
index 0000000000000..46763b04a2cdb
--- /dev/null
+++ b/projects/packages/plugins-installer/tests/php/bootstrap.php
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/projects/packages/publicize/CHANGELOG.md b/projects/packages/publicize/CHANGELOG.md
new file mode 100644
index 0000000000000..721294abd00ad
--- /dev/null
+++ b/projects/packages/publicize/CHANGELOG.md
@@ -0,0 +1,7 @@
+# Changelog
+
+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).
+
diff --git a/projects/packages/publicize/README.md b/projects/packages/publicize/README.md
new file mode 100644
index 0000000000000..f2da9fce18e02
--- /dev/null
+++ b/projects/packages/publicize/README.md
@@ -0,0 +1,3 @@
+# Publicize
+
+Publicize makes it easy to share your site’s posts on several social media networks automatically when you publish a new post.
diff --git a/projects/packages/publicize/changelog/.gitkeep b/projects/packages/publicize/changelog/.gitkeep
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/projects/packages/publicize/changelog/add-publicize-composer-package b/projects/packages/publicize/changelog/add-publicize-composer-package
new file mode 100644
index 0000000000000..c12b9edd39cf9
--- /dev/null
+++ b/projects/packages/publicize/changelog/add-publicize-composer-package
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Added an empty shell package
diff --git a/projects/packages/publicize/composer.json b/projects/packages/publicize/composer.json
new file mode 100644
index 0000000000000..253cb81cd2d95
--- /dev/null
+++ b/projects/packages/publicize/composer.json
@@ -0,0 +1,51 @@
+{
+ "name": "automattic/jetpack-publicize",
+ "description": "Publicize makes it easy to share your site’s posts on several social media networks automatically when you publish a new post.",
+ "type": "library",
+ "license": "GPL-2.0-or-later",
+ "require": {},
+ "require-dev": {
+ "yoast/phpunit-polyfills": "1.0.3",
+ "automattic/jetpack-changelogger": "^3.0"
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "scripts": {
+ "phpunit": [
+ "./vendor/phpunit/phpunit/phpunit --colors=always"
+ ],
+ "test-coverage": [
+ "php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
+ ],
+ "test-php": [
+ "@composer phpunit"
+ ],
+ "build-production": "echo 'Add your build step to composer.json, please!'",
+ "build-development": "echo 'Add your build step to composer.json, please!'"
+ },
+ "repositories": [
+ {
+ "type": "path",
+ "url": "../../packages/*",
+ "options": {
+ "monorepo": true
+ }
+ }
+ ],
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "extra": {
+ "autotagger": true,
+ "mirror-repo": "Automattic/jetpack-publicize",
+ "textdomain": "jetpack-publicize-pkg",
+ "changelogger": {
+ "link-template": "https://github.com/Automattic/jetpack-publicize/compare/v${old}...v${new}"
+ },
+ "branch-alias": {
+ "dev-master": "0.1.x-dev"
+ }
+ }
+}
diff --git a/projects/packages/publicize/package.json b/projects/packages/publicize/package.json
new file mode 100644
index 0000000000000..e14b3b8349666
--- /dev/null
+++ b/projects/packages/publicize/package.json
@@ -0,0 +1,29 @@
+{
+ "private": true,
+ "name": "@automattic/jetpack-publicize",
+ "version": "0.1.0-alpha",
+ "description": "Publicize makes it easy to share your site’s posts on several social media networks automatically when you publish a new post.",
+ "homepage": "https://jetpack.com",
+ "bugs": {
+ "url": "https://github.com/Automattic/jetpack/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/Automattic/jetpack.git"
+ },
+ "license": "GPL-2.0-or-later",
+ "author": "Automattic",
+ "scripts": {
+ "build": "echo 'Not implemented.",
+ "build-js": "echo 'Not implemented.",
+ "build-production": "echo 'Not implemented.",
+ "build-production-js": "echo 'Not implemented.",
+ "clean": "true"
+ },
+ "devDependencies": {},
+ "engines": {
+ "node": "^14.18.3 || ^16.13.2",
+ "pnpm": "^6.23.6",
+ "yarn": "use pnpm instead - see docs/yarn-upgrade.md"
+ }
+}
diff --git a/projects/packages/publicize/phpunit.xml.dist b/projects/packages/publicize/phpunit.xml.dist
new file mode 100644
index 0000000000000..3223c32458db2
--- /dev/null
+++ b/projects/packages/publicize/phpunit.xml.dist
@@ -0,0 +1,14 @@
+
+
+
+ tests/php
+
+
+
+
+
+
+ src
+
+
+
diff --git a/projects/packages/publicize/src/example.php b/projects/packages/publicize/src/example.php
new file mode 100644
index 0000000000000..b3715119facc5
--- /dev/null
+++ b/projects/packages/publicize/src/example.php
@@ -0,0 +1,8 @@
+ $accepted_args,
+ 'function' => $cb,
+ );
+ };
+ // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
+ $add_action = $add_filter;
+}
+
+/**
+ * Adds the package slug and version to the package version tracker's data.
+ *
+ * @param array $package_versions The package version array.
+ *
+ * @return array The packge version array.
+ */
+function send_version_to_tracker( $package_versions ) {
+ $package_versions[ JETPACK_SEARCH_PKG__SLUG ] = JETPACK_SEARCH_PKG__VERSION;
+ return $package_versions;
+}
+
+/**
+ * Whether Jetpack Search Package's version maps to a public release, or a development version.
+ */
+function is_development_version() {
+ return (bool) apply_filters(
+ 'jetpack_search_pkg_version',
+ ! preg_match( '/^\d+(\.\d+)+$/', JETPACK_SEARCH_PKG__VERSION )
+ );
+}
+
+// Set up package version hook.
+$add_filter( 'jetpack_package_versions', __NAMESPACE__ . '\send_version_to_tracker' );
+
+if ( defined( 'WP_CLI' ) && \WP_CLI ) {
+ \WP_CLI::add_command( 'jetpack-search', __NAMESPACE__ . '\CLI' );
+}
diff --git a/projects/packages/search/src/class-cli.php b/projects/packages/search/src/class-cli.php
new file mode 100644
index 0000000000000..1f121f9a46a5e
--- /dev/null
+++ b/projects/packages/search/src/class-cli.php
@@ -0,0 +1,35 @@
+auto_config_search();
+ WP_CLI::line( 'Jetpack Search: auto config success!' );
+ } catch ( \Exception $e ) {
+ WP_CLI::error( $e->getMessage() );
+ }
+ }
+}
diff --git a/projects/packages/search/src/class-helper.php b/projects/packages/search/src/class-helper.php
index 3e2829a37e6c6..fd82e91265901 100644
--- a/projects/packages/search/src/class-helper.php
+++ b/projects/packages/search/src/class-helper.php
@@ -712,11 +712,9 @@ public static function is_valid_locale( $locale ) {
* @return string $script_version Version number.
*/
public static function get_asset_version( $file ) {
- // TODO: Replace Jetpack:: invocation.
- // TODO: Replace JETPACK__PLUGIN_DIR and JETPACK__VERSION.
- return Jetpack::is_development_version() && file_exists( JETPACK__PLUGIN_DIR . $file )
- ? filemtime( JETPACK__PLUGIN_DIR . $file )
- : JETPACK__VERSION;
+ return is_development_version() && file_exists( JETPACK_SEARCH_PKG__DIR . $file )
+ ? filemtime( JETPACK_SEARCH_PKG__DIR . $file )
+ : JETPACK_SEARCH_PKG__VERSION;
}
/**
diff --git a/projects/packages/search/src/class-plan.php b/projects/packages/search/src/class-plan.php
index 49e54f082fe52..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.
@@ -84,7 +87,7 @@ public function has_jetpack_search_product() {
*/
public function supports_instant_search() {
$plan_info = $this->get_plan_info();
- return isset( $plan_info['supports_instant_search'] ) && $plan_info['supports_instant_search'];
+ return ( isset( $plan_info['supports_instant_search'] ) && $plan_info['supports_instant_search'] ) || $this->has_jetpack_search_product();
}
/**
@@ -92,7 +95,7 @@ public function supports_instant_search() {
*/
public function supports_search() {
$plan_info = $this->get_plan_info();
- return isset( $plan_info['supports_search'] ) && $plan_info['supports_search'];
+ return ( isset( $plan_info['supports_search'] ) && $plan_info['supports_search'] ) || $this->has_jetpack_search_product();
}
/**
diff --git a/projects/packages/search/src/customberg/class-customberg2.php b/projects/packages/search/src/customberg/class-customberg2.php
index 4acee2f7a15b4..60d91dcc2eb20 100644
--- a/projects/packages/search/src/customberg/class-customberg2.php
+++ b/projects/packages/search/src/customberg/class-customberg2.php
@@ -98,11 +98,7 @@ public function jetpack_search_admin_page() {
* Loads assets for the customization experience.
*/
public function load_assets() {
- if ( defined( 'JETPACK_SEARCH_PACKAGE_DIRECTORY' ) ) {
- $this->load_assets_with_parameters(
- constant( 'JETPACK_SEARCH_PACKAGE_DIRECTORY' )
- );
- }
+ $this->load_assets_with_parameters( constant( 'JETPACK_SEARCH_PKG__DIR' ) );
}
/**
diff --git a/projects/packages/search/src/customberg/lib/_base-styles.scss b/projects/packages/search/src/customberg/lib/_base-styles.scss
index f7e9e505161bf..008884d6c98f0 100644
--- a/projects/packages/search/src/customberg/lib/_base-styles.scss
+++ b/projects/packages/search/src/customberg/lib/_base-styles.scss
@@ -1,3 +1,4 @@
+@use "sass:math";
@import '@wordpress/base-styles/z-index';
// Fill in missing z-layer values from a more recent @wordpress/base-styles version.
diff --git a/projects/packages/search/src/dashboard/class-dashboard.php b/projects/packages/search/src/dashboard/class-dashboard.php
index d8bb144484027..86935a20d749a 100644
--- a/projects/packages/search/src/dashboard/class-dashboard.php
+++ b/projects/packages/search/src/dashboard/class-dashboard.php
@@ -47,7 +47,8 @@ class Dashboard {
* @param Automattic\Jetpack\Search\Module_Control $module_control - Module_Control instance.
*/
public function __construct( $plan = null, $connection_manager = null, $module_control = null ) {
- $this->plan = $plan ? $plan : new Plan();
+ $this->plan = $plan ? $plan : new Plan();
+ // TODO: 'jetpack-search' better to be the current plugin where the package is running.
$this->connection_manager = $connection_manager ? $connection_manager : new Connection_Manager( 'jetpack-search' );
$this->module_control = $module_control ? $module_control : new Module_Control( $this->plan );
$this->plan->init_hooks();
diff --git a/projects/packages/search/src/dashboard/class-initial-state.php b/projects/packages/search/src/dashboard/class-initial-state.php
index 25d16b582f17d..32622c0b76f2c 100644
--- a/projects/packages/search/src/dashboard/class-initial-state.php
+++ b/projects/packages/search/src/dashboard/class-initial-state.php
@@ -23,7 +23,7 @@ class Initial_State {
protected $connection_manager;
/**
- * Search Moduel Control
+ * Search Module Control
*
* @var Module_Control
*/
@@ -36,6 +36,7 @@ class Initial_State {
* @param Module_Control $module_control - Module control instance.
*/
public function __construct( $connection_manager = null, $module_control = null ) {
+ // TODO: 'jetpack-search' better to be the current plugin where the package is running.
$this->connection_manager = $connection_manager ? $connection_manager : new Connection_Manager( 'jetpack-search' );
$this->module_control = $module_control ? $module_control : new Module_Control();
}
@@ -80,6 +81,8 @@ public function get_initial_state() {
'search' => $this->module_control->is_active(),
'instant_search_enabled' => $this->module_control->is_instant_search_enabled(),
),
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ 'features' => array_map( 'sanitize_text_field', explode( ',', $_GET['features'] ) ),
);
}
diff --git a/projects/packages/search/src/dashboard/components/dashboard/index.jsx b/projects/packages/search/src/dashboard/components/dashboard/index.jsx
index 20dafb6411409..a577fde1b4536 100644
--- a/projects/packages/search/src/dashboard/components/dashboard/index.jsx
+++ b/projects/packages/search/src/dashboard/components/dashboard/index.jsx
@@ -19,6 +19,7 @@ import ModuleControl from 'components/module-control';
import MockedSearch from 'components/mocked-search';
import { STORE_ID } from 'store';
import NoticesList from 'components/global-notices';
+import RecordMeter from 'components/record-meter';
import 'scss/rna-styles.scss';
import './style.scss';
@@ -169,6 +170,10 @@ export default function SearchDashboard() {
);
};
+ const isRecordMeterEnabled = useSelect( select =>
+ select( STORE_ID ).isFeatureEnabled( 'record-meter' )
+ );
+
return (
{ isLoading && (
@@ -179,6 +184,7 @@ export default function SearchDashboard() {
{ renderHeader() }
{ renderMockedSearchInterface() }
{ renderModuleControl() }
+ { isRecordMeterEnabled &&
}
{ renderFooter() }
) }
diff --git a/projects/packages/search/src/dashboard/components/record-meter/index.jsx b/projects/packages/search/src/dashboard/components/record-meter/index.jsx
new file mode 100644
index 0000000000000..3aad19b2091b4
--- /dev/null
+++ b/projects/packages/search/src/dashboard/components/record-meter/index.jsx
@@ -0,0 +1,28 @@
+/**
+ * External dependencies
+ */
+import React from 'react';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import './style.scss';
+
+/**
+ * Generate Record Meter showing how many records the user has indexed
+ *
+ * @returns {React.Component} RecordMeter React component
+ */
+export default function RecordMeter() {
+ return (
+
+
+
+
{ __( 'Your search records', 'jetpack-search-pkg' ) }
+
+
+
+
+ );
+}
diff --git a/projects/packages/search/src/dashboard/components/record-meter/style.scss b/projects/packages/search/src/dashboard/components/record-meter/style.scss
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/projects/packages/search/src/dashboard/store/reducer/feature.js b/projects/packages/search/src/dashboard/store/reducer/feature.js
new file mode 100644
index 0000000000000..f48b1801d45e9
--- /dev/null
+++ b/projects/packages/search/src/dashboard/store/reducer/feature.js
@@ -0,0 +1,5 @@
+const features = ( state = [] ) => {
+ return state;
+};
+
+export default features;
diff --git a/projects/packages/search/src/dashboard/store/reducer/index.js b/projects/packages/search/src/dashboard/store/reducer/index.js
index cf7340059e8f3..8b739110d9c3a 100644
--- a/projects/packages/search/src/dashboard/store/reducer/index.js
+++ b/projects/packages/search/src/dashboard/store/reducer/index.js
@@ -10,6 +10,7 @@ import siteData from './site-data';
import userData from './user-data';
import jetpackSettings from './jetpack-settings';
import sitePlan from './site-plan';
+import features from './feature';
import notices from 'components/global-notices/store/reducer';
const reducer = combineReducers( {
@@ -17,6 +18,7 @@ const reducer = combineReducers( {
jetpackSettings,
sitePlan,
userData,
+ features,
notices,
} );
diff --git a/projects/packages/search/src/dashboard/store/selectors/feature.js b/projects/packages/search/src/dashboard/store/selectors/feature.js
new file mode 100644
index 0000000000000..b9beed07d12ce
--- /dev/null
+++ b/projects/packages/search/src/dashboard/store/selectors/feature.js
@@ -0,0 +1,7 @@
+const featureSelectors = {
+ isFeatureEnabled: ( state, feature ) => {
+ return Array.isArray( state.features ) && state.features.includes( feature );
+ },
+};
+
+export default featureSelectors;
diff --git a/projects/packages/search/src/dashboard/store/selectors/index.js b/projects/packages/search/src/dashboard/store/selectors/index.js
index 8020ea192ae9a..73306f156503c 100644
--- a/projects/packages/search/src/dashboard/store/selectors/index.js
+++ b/projects/packages/search/src/dashboard/store/selectors/index.js
@@ -6,6 +6,7 @@ import jetpackSettingSelectors from './jetpack-settings';
import sitePlanSelectors from './site-plan';
import userDataSelectors from './user-data';
import noticeSelectors from 'components/global-notices/store/selectors';
+import featureSelectors from './feature';
const selectors = {
...siteDataSelectors,
@@ -13,6 +14,7 @@ const selectors = {
...sitePlanSelectors,
...userDataSelectors,
...noticeSelectors,
+ ...featureSelectors,
};
export default selectors;
diff --git a/projects/packages/search/src/initializers/class-jetpack-initializer.php b/projects/packages/search/src/initializers/class-jetpack-initializer.php
index 28a087b88f39d..3a70c6b0e1451 100644
--- a/projects/packages/search/src/initializers/class-jetpack-initializer.php
+++ b/projects/packages/search/src/initializers/class-jetpack-initializer.php
@@ -7,6 +7,8 @@
namespace Automattic\Jetpack\Search;
+use Automattic\Jetpack\Connection\Manager as Connection_Manager;
+
/**
* Initializer for the main Jetpack plugin. Instantiate to enable Jetpack Search functionality.
*/
@@ -34,13 +36,8 @@ public static function initialize() {
return;
}
- // TODO: Port the search widget to package for milestone 2.
- require_once JETPACK__PLUGIN_DIR . 'modules/widgets/search.php';
-
- /**
- * Location of built Jetpack Search assets, does not include trailing slash.
- */
- define( 'JETPACK_SEARCH_PACKAGE_DIRECTORY', dirname( dirname( __DIR__ ) ) );
+ // registers Jetpack Search widget.
+ add_action( 'widgets_init', array( 'Automattic\Jetpack\Search\Jetpack_Initializer', 'jetpack_search_widget_init' ) );
if ( Options::is_instant_enabled() ) {
// Enable the instant search experience.
@@ -68,7 +65,8 @@ public static function initialize() {
* Check if site has been connected.
*/
public static function is_connected() {
- return \Jetpack::is_connection_ready();
+ // TODO: 'jetpack-search' better to be the current plugin where the package is running.
+ return ( new Connection_Manager( 'jetpack-search' ) )->is_connected();
}
/**
@@ -77,4 +75,21 @@ public static function is_connected() {
public static function is_search_supported() {
return ( new Plan() )->supports_search();
}
+
+ /**
+ * Register the widget if Jetpack Search is available and enabled.
+ */
+ public static function jetpack_search_widget_init() {
+ if (
+ ! self::is_connected()
+ || ! self::is_search_supported()
+ || ! ( new Module_Control() )->is_active()
+ ) {
+ return;
+ }
+
+ // There won't be multiple widgets registered when Search stand alone plugin registers it again.
+ // Because the function tests the hash of the class, if they are the same, just register again.
+ register_widget( 'Automattic\Jetpack\Search\Search_Widget' );
+ }
}
diff --git a/projects/packages/search/src/instant-search/class-instant-search.php b/projects/packages/search/src/instant-search/class-instant-search.php
index cf832d000b744..ae1cdf3b7b0d7 100644
--- a/projects/packages/search/src/instant-search/class-instant-search.php
+++ b/projects/packages/search/src/instant-search/class-instant-search.php
@@ -70,11 +70,7 @@ public function init_hooks() {
* Loads assets for Jetpack Instant Search Prototype featuring Search As You Type experience.
*/
public function load_assets() {
- if ( defined( 'JETPACK_SEARCH_PACKAGE_DIRECTORY' ) ) {
- $this->load_assets_with_parameters(
- constant( 'JETPACK_SEARCH_PACKAGE_DIRECTORY' )
- );
- }
+ $this->load_assets_with_parameters( constant( 'JETPACK_SEARCH_PKG__DIR' ) );
}
/**
@@ -217,7 +213,7 @@ public function instant_api( array $args ) {
$request_args = array(
'timeout' => 10,
- 'user-agent' => "WordPress/{$wp_version} | Jetpack/" . constant( 'JETPACK__VERSION' ),
+ 'user-agent' => "WordPress/{$wp_version} | Jetpack-Search/" . constant( 'JETPACK_SEARCH_PKG__VERSION' ),
);
$request = wp_remote_get( esc_url_raw( $service_url ), $request_args );
@@ -313,10 +309,6 @@ public function get_search_aggregations_results() {
* @since 8.3.0
*/
public function auto_config_search() {
- if ( ! current_user_can( 'edit_theme_options' ) ) {
- return;
- }
-
// Set default result format to "expanded".
update_option( Options::OPTION_PREFIX . 'result_format', Options::RESULT_FORMAT_EXPANDED );
@@ -367,6 +359,7 @@ public function auto_config_overlay_sidebar_widgets() {
$widget_opt_name = Helper::get_widget_option_name();
$widget_options = get_option( $widget_opt_name, array() );
foreach ( $widget_options as $id => $w ) {
+ $id = intval( $id );
if ( $id >= $next_id ) {
$next_id = $id + 1;
}
diff --git a/projects/packages/search/src/instant-search/components/search-result-expanded.jsx b/projects/packages/search/src/instant-search/components/search-result-expanded.jsx
index d23c99005ce04..fcbdddb48a2af 100644
--- a/projects/packages/search/src/instant-search/components/search-result-expanded.jsx
+++ b/projects/packages/search/src/instant-search/components/search-result-expanded.jsx
@@ -77,7 +77,7 @@ export default function SearchResultExpanded( props ) {
{ firstImage ? (
{ firstImage ? (
module_control = new Module_Control();
parent::__construct(
Helper::FILTER_WIDGET_BASE,
/** This filter is documented in modules/widgets/facebook-likebox.php */
apply_filters( 'jetpack_widget_name', $name ),
array(
'classname' => 'jetpack-filters widget_search',
- 'description' => __( 'Instant search and filtering to help visitors quickly find relevant answers and explore your site.', 'jetpack' ),
+ 'description' => __( 'Instant search and filtering to help visitors quickly find relevant answers and explore your site.', 'jetpack-search-pkg' ),
)
);
@@ -111,7 +98,7 @@ public function __construct( $name = null ) {
* @since 6.3
*/
public function is_search_active() {
- return Jetpack::is_module_active( 'search' );
+ return $this->module_control->is_active();
}
/**
@@ -120,7 +107,7 @@ public function is_search_active() {
* @since 6.3
*/
public function activate_search() {
- Jetpack::activate_module( 'search', false, false );
+ return $this->module_control->activate();
}
/**
@@ -129,30 +116,33 @@ public function activate_search() {
* @since 5.7.0
*/
public function widget_admin_setup() {
- wp_enqueue_style(
- 'widget-jetpack-search-filters',
- plugins_url( 'search/css/search-widget-admin-ui.css', __FILE__ ),
- array(),
- JETPACK__VERSION
- );
-
// Register jp-tracks and jp-tracks-functions.
Tracking::register_tracks_functions_scripts();
- wp_register_script(
+ Assets::register_script(
'jetpack-search-widget-admin',
- plugins_url( 'search/js/search-widget-admin.js', __FILE__ ),
- array( 'jquery', 'jquery-ui-sortable', 'jp-tracks-functions' ),
- JETPACK__VERSION,
- false
+ 'js/search-widget-admin.js',
+ __FILE__,
+ array(
+ 'in_footer' => true,
+ 'textdomain' => 'jetpack-search-pkg',
+ 'css_path' => 'css/search-widget-admin-ui.css',
+ 'dependencies' => array( 'jquery', 'jquery-ui-sortable', 'jp-tracks-functions' ),
+ )
);
+ // TODO: 'jetpack-search' better to be the current plugin where the package is running.
+ $dotcom_data = ( new Connection_Manager( 'jetpack-search' ) )->get_connected_user_data();
+
wp_localize_script(
'jetpack-search-widget-admin',
'jetpack_search_filter_admin',
array(
'defaultFilterCount' => self::DEFAULT_FILTER_COUNT,
- 'tracksUserData' => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
+ 'tracksUserData' => ! empty( $dotcom_data ) ? array(
+ 'userid' => $dotcom_data['ID'],
+ 'username' => $dotcom_data['login'],
+ ) : false,
'tracksEventData' => array(
'is_customizer' => (int) is_customize_preview(),
),
@@ -165,7 +155,7 @@ public function widget_admin_setup() {
)
);
- wp_enqueue_script( 'jetpack-search-widget-admin' );
+ Assets::enqueue_script( 'jetpack-search-widget-admin' );
}
/**
@@ -177,51 +167,48 @@ public function enqueue_frontend_scripts() {
if ( ! is_active_widget( false, false, $this->id_base, true ) || Options::is_instant_enabled() ) {
return;
}
-
- wp_enqueue_script(
+ Assets::register_script(
'jetpack-search-widget',
- plugins_url( 'search/js/search-widget.js', __FILE__ ),
- array(),
- JETPACK__VERSION,
- true
- );
-
- wp_enqueue_style(
- 'jetpack-search-widget',
- plugins_url( 'search/css/search-widget-frontend.css', __FILE__ ),
- array(),
- JETPACK__VERSION
+ 'js/search-widget.js',
+ __FILE__,
+ array(
+ 'in_footer' => true,
+ 'textdomain' => 'jetpack-search-pkg',
+ // Jetpack the plugin would concatenated the style with other styles and minimize. And the style would be dequeued from WP.
+ // @see https://github.com/Automattic/jetpack/blob/b3de78dce3d88b0d9b283282a5b04515245c8057/projects/plugins/jetpack/tools/builder/frontend-css.js#L52.
+ // @see https://github.com/Automattic/jetpack/blob/bb1b6a9a9cfa98600441f8fa31c9f9c4ef9a04a5/projects/plugins/jetpack/class.jetpack.php#L106.
+ 'css_path' => 'css/search-widget-frontend.css',
+ )
);
+ Assets::enqueue_script( 'jetpack-search-widget' );
}
/**
* Get the list of valid sort types/orders.
*
- * @since 5.8.0
- *
* @return array The sort orders.
+ * @since 5.8.0
*/
private function get_sort_types() {
return array(
- 'relevance|DESC' => is_admin() ? esc_html__( 'Relevance (recommended)', 'jetpack' ) : esc_html__( 'Relevance', 'jetpack' ),
- 'date|DESC' => esc_html__( 'Newest first', 'jetpack' ),
- 'date|ASC' => esc_html__( 'Oldest first', 'jetpack' ),
+ 'relevance|DESC' => is_admin() ? esc_html__( 'Relevance (recommended)', 'jetpack-search-pkg' ) : esc_html__( 'Relevance', 'jetpack-search-pkg' ),
+ 'date|DESC' => esc_html__( 'Newest first', 'jetpack-search-pkg' ),
+ 'date|ASC' => esc_html__( 'Oldest first', 'jetpack-search-pkg' ),
);
}
/**
* Callback for an array_filter() call in order to only get filters for the current widget.
*
- * @see Jetpack_Search_Widget::widget()
- *
- * @since 5.7.0
- *
* @param array $item Filter item.
*
* @return bool Whether the current filter item is for the current widget.
+ * @see Search_Widget::widget()
+ *
+ * @since 5.7.0
*/
public function is_for_current_widget( $item ) {
- return isset( $item['widget_id'] ) && $this->id == $item['widget_id']; // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
+ return isset( $item['widget_id'] ) && $this->id == $item['widget_id']; // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
}
/**
@@ -230,9 +217,8 @@ public function is_for_current_widget( $item ) {
* This is meant to provide backwards-compatibility for VIP, and other professional plan users, that manually
* configured filters via `Automattic\Jetpack\Search\Classic_Search::set_filters()`.
*
- * @since 5.7.0
- *
* @return bool Whether the widget should display site-wide filters or not.
+ * @since 5.7.0
*/
public function should_display_sitewide_filters() {
$filter_widgets = get_option( 'widget_jetpack-search-filters' );
@@ -277,9 +263,9 @@ public function jetpack_search_populate_defaults( $instance ) {
/**
* Populates the instance array with appropriate default values.
*
- * @since 8.6.0
* @param array $instance Previously saved values from database.
* @return array Instance array with default values approprate for instant search
+ * @since 8.6.0
*/
public function populate_defaults_for_instant_search( $instance ) {
return wp_parse_args(
@@ -294,10 +280,9 @@ public function populate_defaults_for_instant_search( $instance ) {
/**
* Responsible for rendering the widget on the frontend.
*
- * @since 5.0.0
- *
* @param array $args Widgets args supplied by the theme.
* @param array $instance The current widget instance.
+ * @since 5.0.0
*/
public function widget( $args, $instance ) {
$instance = $this->jetpack_search_populate_defaults( $instance );
@@ -307,7 +292,7 @@ public function widget( $args, $instance ) {
?>
@@ -330,21 +315,20 @@ public function widget( $args, $instance ) {
/**
* Render the non-instant frontend widget.
*
- * @since 8.3.0
- *
* @param array $args Widgets args supplied by the theme.
* @param array $instance The current widget instance.
+ * @since 8.3.0
*/
public function widget_non_instant( $args, $instance ) {
$display_filters = false;
// Search instance must have been initialized before widget render.
- if ( is_search() && Automattic\Jetpack\Search\Classic_Search::instance() ) {
+ if ( is_search() && Classic_Search::instance() ) {
if ( Helper::should_rerun_search_in_customizer_preview() ) {
- Automattic\Jetpack\Search\Classic_Search::instance()->update_search_results_aggregations();
+ Classic_Search::instance()->update_search_results_aggregations();
}
- $filters = Automattic\Jetpack\Search\Classic_Search::instance()->get_filters();
+ $filters = Classic_Search::instance()->get_filters();
if ( ! Helper::are_filters_by_widget_disabled() && ! $this->should_display_sitewide_filters() ) {
$filters = array_filter( $filters, array( $this, 'is_for_current_widget' ) );
@@ -375,11 +359,10 @@ public function widget_non_instant( $args, $instance ) {
*
* @module search
*
- * @since 5.7.0
- *
* @param string $title The widget's title
* @param string $args['before_title'] The HTML tag to display before the title
* @param string $args['after_title'] The HTML tag to display after the title
+ *@since 5.7.0
*/
do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
}
@@ -391,14 +374,14 @@ public function widget_non_instant( $args, $instance ) {
// we need to dynamically inject the sort field into the search box when the search box is enabled, and display
// it separately when it's not.
if ( ! empty( $instance['search_box_enabled'] ) ) {
- Automattic\Jetpack\Search\Template_Tags::render_widget_search_form( $instance['post_types'], $orderby, $order );
+ Template_Tags::render_widget_search_form( $instance['post_types'], $orderby, $order );
}
if ( ! empty( $instance['search_box_enabled'] ) && ! empty( $instance['user_sort_enabled'] ) ) :
?>
-
+
get_sort_types() as $sort => $label ) { ?>
>
@@ -417,10 +400,9 @@ public function widget_non_instant( $args, $instance ) {
*
* @module search
*
- * @since 5.8.0
- *
* @param array $filters The possible filters for the current query.
* @param array $post_types An array of post types to limit filtering to.
+ *@since 5.8.0
*/
do_action(
'jetpack_search_render_filters',
@@ -438,22 +420,21 @@ public function widget_non_instant( $args, $instance ) {
/**
* Render the instant frontend widget.
*
- * @since 8.3.0
- *
* @param array $args Widgets args supplied by the theme.
* @param array $instance The current widget instance.
+ * @since 8.3.0
*/
public function widget_instant( $args, $instance ) {
// Exit early if search instance has not been initialized.
- if ( ! Automattic\Jetpack\Search\Instant_Search::instance() ) {
+ if ( ! Instant_Search::instance() ) {
return false;
}
if ( Helper::should_rerun_search_in_customizer_preview() ) {
- Automattic\Jetpack\Search\Instant_Search::instance()->update_search_results_aggregations();
+ Instant_Search::instance()->update_search_results_aggregations();
}
- $filters = Automattic\Jetpack\Search\Instant_Search::instance()->get_filters();
+ $filters = Instant_Search::instance()->get_filters();
if ( ! Helper::are_filters_by_widget_disabled() && ! $this->should_display_sitewide_filters() ) {
$filters = array_filter( $filters, array( $this, 'is_for_current_widget' ) );
}
@@ -476,16 +457,15 @@ public function widget_instant( $args, $instance ) {
*
* @module search
*
- * @since 5.7.0
- *
* @param string $title The widget's title
* @param string $args['before_title'] The HTML tag to display before the title
* @param string $args['after_title'] The HTML tag to display after the title
+ *@since 5.7.0
*/
do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
}
- Automattic\Jetpack\Search\Template_Tags::render_widget_search_form( array(), '', '' );
+ Template_Tags::render_widget_search_form( array(), '', '' );
if ( $display_filters ) {
/**
@@ -493,10 +473,9 @@ public function widget_instant( $args, $instance ) {
*
* @module search
*
- * @since 5.8.0
- *
* @param array $filters The possible filters for the current query.
* @param array $post_types An array of post types to limit filtering to.
+ *@since 5.8.0
*/
do_action(
'jetpack_search_render_filters',
@@ -512,10 +491,9 @@ public function widget_instant( $args, $instance ) {
/**
* Render the instant widget for the overlay.
*
- * @since 8.3.0
- *
* @param array $args Widgets args supplied by the theme.
* @param array $instance The current widget instance.
+ * @since 8.3.0
*/
public function widget_empty_instant( $args, $instance ) {
$title = isset( $instance['title'] ) ? $instance['title'] : '';
@@ -538,11 +516,10 @@ public function widget_empty_instant( $args, $instance ) {
*
* @module search
*
- * @since 5.7.0
- *
* @param string $title The widget's title
* @param string $args['before_title'] The HTML tag to display before the title
* @param string $args['after_title'] The HTML tag to display after the title
+ *@since 5.7.0
*/
do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
}
@@ -559,11 +536,10 @@ public function widget_empty_instant( $args, $instance ) {
* - find the orderby/order fields and set default values
* - detect changes to the sort field, if it exists, and use it to set the order field values
*
- * @since 5.8.0
- *
* @param array $instance The current widget instance.
* @param string $order The order to initialize the select with.
* @param string $orderby The orderby to initialize the select with.
+ * @since 5.8.0
*/
private function maybe_render_sort_javascript( $instance, $order, $orderby ) {
if ( Options::is_instant_enabled() ) {
@@ -621,11 +597,10 @@ private function maybe_render_sort_javascript( $instance, $order, $orderby ) {
/**
* Convert a sort string into the separate order by and order parts.
*
- * @since 5.8.0
- *
* @param string $sort A sort string.
*
* @return array Order by and order.
+ * @since 5.8.0
*/
private function sorting_to_wp_query_param( $sort ) {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
@@ -646,12 +621,11 @@ private function sorting_to_wp_query_param( $sort ) {
/**
* Updates a particular instance of the widget. Validates and sanitizes the options.
*
- * @since 5.0.0
- *
- * @param array $new_instance New settings for this instance as input by the user via Jetpack_Search_Widget::form().
+ * @param array $new_instance New settings for this instance as input by the user via Search_Widget::form().
* @param array $old_instance Old settings for this instance.
*
* @return array Settings to save.
+ * @since 5.0.0
*/
public function update( $new_instance, $old_instance ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
$new_instance = $this->maybe_reformat_widget( $new_instance );
@@ -660,7 +634,7 @@ public function update( $new_instance, $old_instance ) { // phpcs:ignore Variabl
$instance['title'] = sanitize_text_field( $new_instance['title'] );
$instance['search_box_enabled'] = empty( $new_instance['search_box_enabled'] ) ? '0' : '1';
$instance['user_sort_enabled'] = empty( $new_instance['user_sort_enabled'] ) ? '0' : '1';
- $instance['sort'] = $new_instance['sort'];
+ $instance['sort'] = empty( $new_instance['sort'] ) ? self::DEFAULT_SORT : $new_instance['sort'];
$instance['post_types'] = empty( $new_instance['post_types'] ) || empty( $instance['search_box_enabled'] )
? array()
: array_map( 'sanitize_key', $new_instance['post_types'] );
@@ -737,9 +711,8 @@ protected function maybe_reformat_widget( $widget_instance ) {
/**
* Outputs the settings update form.
*
- * @since 5.0.0
- *
* @param array $instance Previously saved values from database.
+ * @since 5.0.0
*/
public function form( $instance ) {
if ( Options::is_instant_enabled() ) {
@@ -762,7 +735,7 @@ public function form( $instance ) {
-
+
-
+
-
+
@@ -864,9 +837,8 @@ class="widefat jetpack-search-filters-widget__sort-order">
/**
* Outputs the widget update form to be used in the Customizer for Instant Search.
*
- * @since 8.6.0
- *
* @param array $instance Previously saved values from database.
+ * @since 8.6.0
*/
private function form_for_instant_search( $instance ) {
$instance = $this->populate_defaults_for_instant_search( $instance );
@@ -877,7 +849,7 @@ private function form_for_instant_search( $instance ) {
-
+
-
+
-
+
@@ -918,11 +890,10 @@ class="widefat"
* and native PHP (server-side). This helper function allows for easy rendering
* of attributes in both formats.
*
- * @since 5.8.0
- *
* @param string $name Attribute name.
* @param string $value Attribute value.
* @param bool $is_template Whether this is for an Underscore template or not.
+ * @since 5.8.0
*/
private function render_widget_attr( $name, $value, $is_template ) {
echo $is_template ? "<%= $name %>" : esc_attr( $value ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
@@ -933,12 +904,11 @@ private function render_widget_attr( $name, $value, $is_template ) {
* and native PHP (server-side). This helper function allows for easy rendering
* of the "selected" attribute in both formats.
*
- * @since 5.8.0
- *
* @param string $name Attribute name.
* @param string $value Attribute value.
* @param string $compare Value to compare to the attribute value to decide if it should be selected.
* @param bool $is_template Whether this is for an Underscore template or not.
+ * @since 5.8.0
*/
private function render_widget_option_selected( $name, $value, $compare, $is_template ) {
$compare_js = rawurlencode( $compare );
@@ -950,10 +920,9 @@ private function render_widget_option_selected( $name, $value, $compare, $is_tem
*
* We use this method for two purposes - rendering the fields server-side, and also rendering a script template for Underscore.
*
- * @since 5.7.0
- *
* @param array $filter The filter to render.
* @param bool $is_template Whether this is for an Underscore template or not.
+ * @since 5.7.0
*/
public function render_widget_edit_filter( $filter, $is_template = false ) {
$args = wp_parse_args(
@@ -975,16 +944,16 @@ public function render_widget_edit_filter( $filter, $is_template = false ) {
-
+
render_widget_option_selected( 'type', $args['type'], 'taxonomy', $is_template ); ?>>
-
+
render_widget_option_selected( 'type', $args['type'], 'post_type', $is_template ); ?>>
-
+
render_widget_option_selected( 'type', $args['type'], 'date_histogram', $is_template ); ?>>
-
+
@@ -993,7 +962,7 @@ public function render_widget_edit_filter( $filter, $is_template = false ) {
@@ -1003,7 +972,7 @@ public function render_widget_edit_filter( $filter, $is_template = false ) {
$label = in_array( $taxonomy->label, $seen_taxonomy_labels, true )
? sprintf(
/* translators: %1$s is the taxonomy name, %2s is the name of its type to help distinguish between several taxonomies with the same name, e.g. category and tag. */
- _x( '%1$s (%2$s)', 'A label for a taxonomy selector option', 'jetpack' ),
+ _x( '%1$s (%2$s)', 'A label for a taxonomy selector option', 'jetpack-search-pkg' ),
$taxonomy->label,
$taxonomy->name
)
@@ -1019,19 +988,19 @@ public function render_widget_edit_filter( $filter, $is_template = false ) {
-
+
render_widget_option_selected( 'field', $args['field'], 'post_date', $is_template ); ?>>
-
+
render_widget_option_selected( 'field', $args['field'], 'post_date_gmt', $is_template ); ?>>
-
+
render_widget_option_selected( 'field', $args['field'], 'post_modified', $is_template ); ?>>
-
+
render_widget_option_selected( 'field', $args['field'], 'post_modified_gmt', $is_template ); ?>>
-
+
@@ -1039,13 +1008,13 @@ public function render_widget_edit_filter( $filter, $is_template = false ) {
-
+
render_widget_option_selected( 'interval', $args['interval'], 'month', $is_template ); ?>>
-
+
render_widget_option_selected( 'interval', $args['interval'], 'year', $is_template ); ?>>
-
+
@@ -1053,7 +1022,7 @@ public function render_widget_edit_filter( $filter, $is_template = false ) {
-
+
-
+
-
+
id ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
+ if ( $checkout_id != $buffer->id ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseNotEqual
return new WP_Error( 'buffer_mismatch', 'The buffer you checked in was not checked out' );
}
diff --git a/projects/packages/sync/src/modules/class-plugins.php b/projects/packages/sync/src/modules/class-plugins.php
index b244834f54699..84c45b6de857e 100644
--- a/projects/packages/sync/src/modules/class-plugins.php
+++ b/projects/packages/sync/src/modules/class-plugins.php
@@ -205,7 +205,7 @@ private function get_plugin_info( $slug ) {
$plugins = get_plugins(); // Get the most up to date info.
if ( isset( $plugins[ $slug ] ) ) {
return array_merge( array( 'slug' => $slug ), $plugins[ $slug ] );
- };
+ }
// Try grabbing the info from before the update.
return isset( $this->plugins[ $slug ] ) ? array_merge( array( 'slug' => $slug ), $this->plugins[ $slug ] ) : array( 'slug' => $slug );
}
diff --git a/projects/packages/terms-of-service/CHANGELOG.md b/projects/packages/terms-of-service/CHANGELOG.md
index f9b91333e7dd0..b714f4d9b570b 100644
--- a/projects/packages/terms-of-service/CHANGELOG.md
+++ b/projects/packages/terms-of-service/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).
+## [1.9.19] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [1.9.18] - 2022-01-04
### Changed
- Switch to pcov for code coverage.
@@ -182,6 +186,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Package: Create new TOS package
+[1.9.19]: https://github.com/Automattic/jetpack-terms-of-service/compare/v1.9.18...v1.9.19
[1.9.18]: https://github.com/Automattic/jetpack-terms-of-service/compare/v1.9.17...v1.9.18
[1.9.17]: https://github.com/Automattic/jetpack-terms-of-service/compare/v1.9.16...v1.9.17
[1.9.16]: https://github.com/Automattic/jetpack-terms-of-service/compare/v1.9.15...v1.9.16
diff --git a/projects/packages/terms-of-service/changelog/add-visitor-status b/projects/packages/terms-of-service/changelog/add-visitor-status
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/packages/terms-of-service/changelog/add-visitor-status
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/packages/terms-of-service/changelog/update-project-scripts-no-install b/projects/packages/terms-of-service/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/terms-of-service/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/tracking/CHANGELOG.md b/projects/packages/tracking/CHANGELOG.md
index 970ab75772cab..92dcfaa26965d 100644
--- a/projects/packages/tracking/CHANGELOG.md
+++ b/projects/packages/tracking/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).
+## [1.14.1] - 2022-01-25
+### Changed
+- Updated package dependencies.
+
## [1.14.0] - 2022-01-04
### Changed
- Switch to pcov for code coverage.
@@ -206,6 +210,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Create package for Jetpack Tracking
+[1.14.1]: https://github.com/Automattic/jetpack-tracking/compare/v1.14.0...v1.14.1
[1.14.0]: https://github.com/Automattic/jetpack-tracking/compare/v1.13.19...v1.14.0
[1.13.19]: https://github.com/Automattic/jetpack-tracking/compare/v1.13.18...v1.13.19
[1.13.18]: https://github.com/Automattic/jetpack-tracking/compare/v1.13.17...v1.13.18
diff --git a/projects/packages/tracking/changelog/add-fancy-eslint-ignore b/projects/packages/tracking/changelog/add-fancy-eslint-ignore
deleted file mode 100644
index afe95e481cf32..0000000000000
--- a/projects/packages/tracking/changelog/add-fancy-eslint-ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: changed
-Comment: Hack eslint config to use `.gitignore` and per-dir `.eslintignore`.
-
-
diff --git a/projects/packages/tracking/changelog/add-visitor-status b/projects/packages/tracking/changelog/add-visitor-status
deleted file mode 100644
index c47cb18e82997..0000000000000
--- a/projects/packages/tracking/changelog/add-visitor-status
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: changed
-
-Updated package dependencies.
diff --git a/projects/packages/tracking/changelog/update-project-scripts-no-install b/projects/packages/tracking/changelog/update-project-scripts-no-install
deleted file mode 100644
index f45fac5ea79e2..0000000000000
--- a/projects/packages/tracking/changelog/update-project-scripts-no-install
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: removed
-Comment: Tooling only: Build and test scripts no longer call `composer install` or `pnpm install`.
-
-
diff --git a/projects/packages/tracking/composer.json b/projects/packages/tracking/composer.json
index ea086a77f7ed4..1b4d2e003bc87 100644
--- a/projects/packages/tracking/composer.json
+++ b/projects/packages/tracking/composer.json
@@ -4,7 +4,7 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
- "automattic/jetpack-assets": "^1.16",
+ "automattic/jetpack-assets": "^1.17",
"automattic/jetpack-options": "^1.14",
"automattic/jetpack-status": "^1.10",
"automattic/jetpack-terms-of-service": "^1.9"
diff --git a/projects/packages/waf/.gitattributes b/projects/packages/waf/.gitattributes
new file mode 100644
index 0000000000000..9f76af3f06e7e
--- /dev/null
+++ b/projects/packages/waf/.gitattributes
@@ -0,0 +1,16 @@
+# Files not needed to be distributed in the package.
+.gitattributes export-ignore
+.github/ export-ignore
+package.json export-ignore
+
+# Files to include in the mirror repo, but excluded via gitignore
+# Remember to end all directories with `/**` to properly tag every file.
+# /src/js/example.min.js production-include
+
+# Files to exclude from the mirror repo, but included in the monorepo.
+# Remember to end all directories with `/**` to properly tag every file.
+.gitignore production-exclude
+changelog/** production-exclude
+phpunit.xml.dist production-exclude
+.phpcs.dir.xml production-exclude
+tests/** production-exclude
diff --git a/projects/packages/waf/.gitignore b/projects/packages/waf/.gitignore
new file mode 100644
index 0000000000000..140fd587d2d52
--- /dev/null
+++ b/projects/packages/waf/.gitignore
@@ -0,0 +1,2 @@
+vendor/
+node_modules/
diff --git a/projects/packages/waf/.phpcs.dir.xml b/projects/packages/waf/.phpcs.dir.xml
new file mode 100644
index 0000000000000..0b414c4ced0b6
--- /dev/null
+++ b/projects/packages/waf/.phpcs.dir.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
diff --git a/projects/packages/waf/CHANGELOG.md b/projects/packages/waf/CHANGELOG.md
new file mode 100644
index 0000000000000..03a962f457f66
--- /dev/null
+++ b/projects/packages/waf/CHANGELOG.md
@@ -0,0 +1,6 @@
+# Changelog
+
+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).
diff --git a/projects/packages/waf/README.md b/projects/packages/waf/README.md
new file mode 100644
index 0000000000000..c72f0cf32aa6f
--- /dev/null
+++ b/projects/packages/waf/README.md
@@ -0,0 +1,9 @@
+# waf
+
+Tools to assist with the Jetpack Web Application Firewall
+
+Need to report a security vulnerability? Go to [https://automattic.com/security/](https://automattic.com/security/) or directly to our security bug bounty site [https://hackerone.com/automattic](https://hackerone.com/automattic).
+
+## License
+
+waf is licensed under [GNU General Public License v2 (or later)](./LICENSE.txt)
diff --git a/projects/packages/waf/changelog/.gitkeep b/projects/packages/waf/changelog/.gitkeep
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/projects/packages/waf/changelog/add-waf-package b/projects/packages/waf/changelog/add-waf-package
new file mode 100644
index 0000000000000..22245f547f57f
--- /dev/null
+++ b/projects/packages/waf/changelog/add-waf-package
@@ -0,0 +1,4 @@
+Significance: major
+Type: added
+
+added Initial version
diff --git a/projects/packages/waf/changelog/update-ignored-config-waf b/projects/packages/waf/changelog/update-ignored-config-waf
new file mode 100644
index 0000000000000..6a66cee0d2688
--- /dev/null
+++ b/projects/packages/waf/changelog/update-ignored-config-waf
@@ -0,0 +1,4 @@
+Significance: patch
+Type: changed
+
+Core: do not ship .phpcs.dir.xml in production builds.
diff --git a/projects/packages/waf/composer.json b/projects/packages/waf/composer.json
new file mode 100644
index 0000000000000..6978dd41e99fe
--- /dev/null
+++ b/projects/packages/waf/composer.json
@@ -0,0 +1,50 @@
+{
+ "name": "automattic/jetpack-waf",
+ "description": "Tools to assist with the Jetpack Web Application Firewall",
+ "type": "library",
+ "license": "GPL-2.0-or-later",
+ "require": {
+ "wikimedia/aho-corasick": "^1.0"
+ },
+ "require-dev": {
+ "yoast/phpunit-polyfills": "1.0.3",
+ "automattic/jetpack-changelogger": "^3.0"
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "scripts": {
+ "phpunit": [
+ "./vendor/phpunit/phpunit/phpunit --colors=always"
+ ],
+ "test-coverage": [
+ "php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
+ ],
+ "test-php": [
+ "@composer phpunit"
+ ]
+ },
+ "repositories": [
+ {
+ "type": "path",
+ "url": "../../packages/*",
+ "options": {
+ "monorepo": true
+ }
+ }
+ ],
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "extra": {
+ "autotagger": true,
+ "mirror-repo": "Automattic/jetpack-waf",
+ "changelogger": {
+ "link-template": "https://github.com/Automattic/jetpack-waf/compare/v${old}...v${new}"
+ },
+ "branch-alias": {
+ "dev-master": "0.1.x-dev"
+ }
+ }
+}
diff --git a/projects/packages/waf/phpunit.xml.dist b/projects/packages/waf/phpunit.xml.dist
new file mode 100644
index 0000000000000..3223c32458db2
--- /dev/null
+++ b/projects/packages/waf/phpunit.xml.dist
@@ -0,0 +1,14 @@
+
+
+
+ tests/php
+
+
+
+
+
+
+ src
+
+
+
diff --git a/projects/packages/waf/src/class-waf-operators.php b/projects/packages/waf/src/class-waf-operators.php
new file mode 100644
index 0000000000000..3ad7b537e6674
--- /dev/null
+++ b/projects/packages/waf/src/class-waf-operators.php
@@ -0,0 +1,286 @@
+= intval( $test )
+ ? $input
+ : false;
+ }
+
+ /**
+ * Returns true if the input value is greater than the test value.
+ * If either value cannot be converted to an int it will be treated as 0.
+ *
+ * @param mixed $input Input.
+ * @param mixed $test Test.
+ * @return int|false
+ */
+ public function gt( $input, $test ) {
+ return intval( $input ) > intval( $test )
+ ? $input
+ : false;
+ }
+
+ /**
+ * Returns true if the input value is less than or equal to the test value.
+ * If either value cannot be converted to an int it will be treated as 0.
+ *
+ * @param mixed $input Input.
+ * @param mixed $test Test.
+ * @return int|false
+ */
+ public function le( $input, $test ) {
+ return intval( $input ) <= intval( $test )
+ ? $input
+ : false;
+ }
+
+ /**
+ * Returns true if the input value is less than the test value.
+ * If either value cannot be converted to an int it will be treated as 0.
+ *
+ * @param mixed $input Input.
+ * @param mixed $test Test.
+ * @return int|false
+ */
+ public function lt( $input, $test ) {
+ return intval( $input ) < intval( $test )
+ ? $input
+ : false;
+ }
+
+ /**
+ * Returns false.
+ *
+ * @return false
+ */
+ public function no_match() {
+ return false;
+ }
+
+ /**
+ * Uses a multi-string matching algorithm to search through $input for a number of given $words.
+ *
+ * @param string $input Input.
+ * @param string[] $words \AhoCorasick\MultiStringMatcher $matcher.
+ * @return string[]|false Returns the words that were found in $input, or FALSE if no words were found.
+ */
+ public function pm( $input, $words ) {
+ $results = $this->get_multi_string_matcher( $words )->searchIn( $input );
+
+ return isset( $results[0] )
+ ? array_map(
+ function ( $r ) {
+ return $r[1]; },
+ $results
+ )
+ : false;
+ }
+
+ /**
+ * The last-used pattern-matching algorithm.
+ *
+ * @var array
+ */
+ private $last_multi_string_matcher = array( null, null );
+
+ /**
+ * Creates a matcher that uses the Aho-Corasick algorithm to efficiently find a number of words in an input string.
+ * Caches the last-used matcher so that the same word list doesn't have to be compiled multiple times.
+ *
+ * @param string[] $words Words.
+ * @return \AhoCorasick\MultiStringMatcher
+ */
+ private function get_multi_string_matcher( $words ) {
+ // only create a new matcher entity if we don't have one already for this word list.
+ if ( $this->last_multi_string_matcher[0] !== $words ) {
+ $this->last_multi_string_matcher = array( $words, new \AhoCorasick\MultiStringMatcher( $words ) );
+ }
+
+ return $this->last_multi_string_matcher[1];
+ }
+
+ /**
+ * Performs a regular expression match on the input subject using the given pattern.
+ * Returns false if the pattern does not match, or the substring(s) of the input
+ * that were matched by the pattern.
+ *
+ * @param string $subject Subject.
+ * @param string $pattern Pattern.
+ * @return string[]|false
+ */
+ public function rx( $subject, $pattern ) {
+ $matched = preg_match( $pattern, $subject, $matches );
+ return 1 === $matched
+ ? $matches
+ : false;
+ }
+
+ /**
+ * Returns true if the given input string matches the test string.
+ *
+ * @param string $input Input.
+ * @param string $test Test.
+ * @return string|false
+ */
+ public function streq( $input, $test ) {
+ return $input === $test
+ ? $test
+ : false;
+ }
+
+ /**
+ * Returns true.
+ *
+ * @param string $input Input.
+ * @return bool
+ */
+ public function unconditional_match( $input ) {
+ return $input;
+ }
+
+ /**
+ * Checks to see if the input string only contains characters within the given byte range
+ *
+ * @param string $input Input.
+ * @param array $valid_range Valid range.
+ * @return string
+ */
+ public function validate_byte_range( $input, $valid_range ) {
+ if ( '' === $input ) {
+ // an empty string is considered "valid".
+ return false;
+ }
+ $i = 0;
+ while ( isset( $input[ $i ] ) ) {
+ $n = ord( $input[ $i ] );
+ if ( $n < $valid_range['min'] || $n > $valid_range['max'] ) {
+ return $input[ $i ];
+ }
+ $valid = false;
+ foreach ( $valid_range['range'] as $b ) {
+ if ( $n === $b || is_array( $b ) && $n >= $b[0] && $n <= $b[1] ) {
+ $valid = true;
+ break;
+ }
+ }
+ if ( ! $valid ) {
+ return $input[ $i ];
+ }
+ $i++;
+ }
+
+ // if there weren't any invalid bytes, return false.
+ return false;
+ }
+
+ /**
+ * Returns true if the input value is found anywhere inside the test value
+ * (i.e. the inverse of @contains)
+ *
+ * @param mixed $input Input.
+ * @param mixed $test Test.
+ * @return string|false
+ */
+ public function within( $input, $test ) {
+ if ( '' === $input || '' === $test ) {
+ return false;
+ }
+
+ return stripos( $test, $input ) !== false
+ ? $input
+ : false;
+ }
+}
diff --git a/projects/packages/waf/src/class-waf-rule-compiler.php b/projects/packages/waf/src/class-waf-rule-compiler.php
new file mode 100644
index 0000000000000..9ffc18c3a29a0
--- /dev/null
+++ b/projects/packages/waf/src/class-waf-rule-compiler.php
@@ -0,0 +1,517 @@
+php[] = sprintf(
+ "\$rule = (object) array( 'id' => %s, 'reason' => %s, 'tags' => %s );",
+ var_export( $rule['id'], true ),
+ isset( $rule['reason'] ) ? $this->expand_macro( $rule['reason'] ) : "''",
+ var_export( isset( $rule['tags'] ) ? array_map( 'strtolower', $rule['tags'] ) : array(), true )
+ );
+ $rule_php = $this->compile_rule( $rule );
+ $this->add_rule( $rule['id'], isset( $rule['tags'] ) ? $rule['tags'] : array(), $rule_php );
+ } elseif ( isset( $rule['actions'] ) ) {
+ // this is an actions-only rule.
+ $rule_php = $this->compile_actions( $rule['actions'] );
+ $this->add_rule( $rule['id'], isset( $rule['tags'] ) ? $rule['tags'] : array(), $rule_php );
+ } elseif ( isset( $rule['marker'] ) ) {
+ // this is a marker.
+ $this->add_marker( $rule['marker'] );
+ } else {
+ var_dump( 'UNKNOWN RULE:', $rule );
+ exit;
+ }
+ }
+ // check to see if there are any leftover markers.
+ if ( ! empty( $this->marker_map ) ) {
+ var_dump( 'Marker Map Is Not Empty!', $this->marker_map );
+ exit;
+ }
+ }
+
+ /**
+ * Write rules to file.
+ *
+ * @param string $filepath File path.
+ * @throws \Exception If file writing fails.
+ */
+ public function write_to_file( $filepath ) {
+ $php_code = implode( PHP_EOL, $this->php );
+
+ $wp_filesystem = jpwaf_init_filesystem();
+ if ( ! $wp_filesystem ) {
+ throw new \Exception( 'No filesystem available' );
+ }
+ // ensure the folder exists.
+ if ( ! $wp_filesystem->is_writable( dirname( $filepath ) ) ) {
+ $wp_filesystem->mkdir( dirname( $filepath ) );
+ }
+ if ( ! $wp_filesystem->put_contents( $filepath, $php_code ) ) {
+ throw new \Exception( "Failed writing to: $filepath" );
+ }
+
+ return true;
+ }
+
+ /**
+ * Add rule.
+ *
+ * @param string $rule_id Rule id.
+ * @param array $rule_tags Rule tags.
+ * @param array $rule_php Rule php.
+ */
+ private function add_rule( $rule_id, $rule_tags, $rule_php ) {
+ // wrap in a "if rule removed" check (only if a previous rule might be removing this rule).
+ if ( $this->check_if_maybe_will_change_at_runtime( 'rule', $rule_id, $rule_tags ) ) {
+ array_unshift( $rule_php, 'if(!$waf->rule_removed($rule->id, $rule->tags)) {' );
+ $rule_php[] = '}';
+ }
+ $this->php = array_merge( $this->php, $rule_php );
+ // add a marker in case another rules wants to skipAfter this rule.
+ $this->add_marker( $rule_id );
+ }
+
+ /**
+ * Add marker.
+ *
+ * @param string $label Label.
+ */
+ private function add_marker( $label ) {
+ if ( isset( $this->marker_map[ $label ] ) ) {
+ $this->php[] = $this->marker_map[ $label ] . ": // $label";
+ unset( $this->marker_map[ $label ] );
+ }
+ }
+
+ /**
+ * Compile actions.
+ *
+ * @param array $actions Actions.
+ */
+ private function compile_actions( $actions ) {
+ return array_map(
+ function ( $act ) {
+ switch ( $act['name'] ) {
+ case 'set_var':
+ // key, op, value.
+ $k = $this->expand_macro( strtolower( $act['key'] ) );
+ if ( 'x' === $act['op'] ) {
+ return sprintf(
+ '$waf->unset_var(%s);',
+ $k
+ );
+ } else {
+ $v = $this->expand_macro( $act['value'] );
+ $fn = 'set_var';
+ if ( '+' === $act['op'] ) {
+ $fn = 'inc_var';
+ } elseif ( '-' === $act['op'] ) {
+ $fn = 'dec_var';
+ }
+ return sprintf(
+ '$waf->%s(%s,%s);',
+ $fn,
+ $k,
+ $v
+ );
+ }
+ case 'remove_target':
+ // make a note (for compiler use) other rules'
+ // targets may be updated based on ID or tag.
+ $this->maybe_will_change_at_runtime(
+ 'target',
+ $act['prop'],
+ $act['value']
+ );
+ // add a runtime line.
+ return sprintf(
+ '$waf->flag_target_for_removal(%s,%s,%s,%s);',
+ var_export( $act['prop'], true ),
+ var_export( strtolower( $act['value'] ), true ),
+ var_export( strtolower( $act['targetName'] ), true ),
+ var_export( $act['targetProp'], true )
+ );
+ case 'remove_rule':
+ // make a note (for compiler use) other rules
+ // may be removed based on ID or tag.
+ $this->maybe_will_change_at_runtime( 'rule', $act['prop'], $act['value'] );
+ // add a runtime line.
+ return sprintf(
+ '$waf->flag_rule_for_removal(%s,%s);',
+ var_export( $act['prop'], true ),
+ var_export( $act['value'], true )
+ );
+ case 'set_reason':
+ return sprintf(
+ '$rule->reason = %s;',
+ $this->expand_macro( $act['reason'] )
+ );
+ default:
+ var_dump( $act );
+ exit;
+ }
+ },
+ $actions
+ );
+ }
+
+ /**
+ * Compile rule.
+ *
+ * @param array $rule Rule.
+ */
+ private function compile_rule( $rule ) {
+ // compile the the disruptive action.
+ $disruptive_action = '';
+ if ( $rule['action'] ) {
+ switch ( $rule['action']['name'] ) {
+ case 'block':
+ case 'deny':
+ case 'drop':
+ $disruptive_action = sprintf(
+ 'return $waf->block(%s,$rule->id,$rule->reason,%s);',
+ var_export( $rule['action']['name'], true ),
+ var_export( $rule['action']['status'], true )
+ );
+ break;
+ case 'allow':
+ $disruptive_action = 'return;';
+ break;
+ case 'redirect':
+ $disruptive_action = sprintf(
+ 'return $waf->redirect($rule->id,%s);',
+ var_export( $rule['action']['url'], true )
+ );
+ break;
+ case 'skipAfter':
+ $disruptive_action = sprintf(
+ 'goto %s;',
+ $this->get_marker_label( $rule['action']['target'] )
+ );
+ break;
+ }
+ }
+ // compile the expressions.
+ return $this->compile_expression(
+ $rule['expression'],
+ $rule['id'],
+ isset( $rule['tags'] ) ? $rule['tags'] : array(),
+ $disruptive_action ? array( $disruptive_action ) : array(),
+ $rule
+ );
+ }
+
+ /**
+ * Add marker.
+ *
+ * @param array $expression Expression.
+ * @param string $rule_id Rule id.
+ * @param array $rule_tags Rule tags.
+ * @param array $other_actions Other actions.
+ * @param array $rule Rule.
+ */
+ private function compile_expression( $expression, $rule_id, $rule_tags, $other_actions, $rule ) {
+ $php_lines = array();
+ if ( 'and' === $expression['operator'] ) {
+ $php_lines = array_reduce(
+ array_reverse( $expression['expressions'] ),
+ function ( $lines, $expr ) use ( $rule_id, $rule_tags, $rule ) {
+ return $this->compile_expression( $expr, $rule_id, $rule_tags, $lines, $rule );
+ },
+ $other_actions
+ );
+ } elseif ( 'or' === $expression['operator'] ) {
+ die( 'or!' );
+ } else {
+ // Simple Expression
+ // prepare the targets.
+ $targets = array();
+ foreach ( $expression['targets'] as $t ) {
+ $t2 = $t;
+ if ( isset( $t2['only'] ) ) {
+ foreach ( $t2['only'] as $k => $v ) {
+ $t2['only'][ $k ] = '/' === $v[0]
+ ? $v
+ : strtolower( $v );
+ }
+ }
+ if ( isset( $t2['except'] ) ) {
+ foreach ( $t2['except'] as $k => $v ) {
+ $t2['except'][ $k ] = '/' === $v[0]
+ ? $v
+ : strtolower( $v );
+ }
+ }
+ unset( $t2['name'] );
+ $targets[ strtolower( $t['name'] ) ] = $t2;
+ }
+ $targets = var_export( $targets, true );
+ // if the targets for this rule may have been changed, then pass the targets
+ // to a method to update them at runtime.
+ if ( $this->check_if_maybe_will_change_at_runtime( 'target', $rule_id, $rule_tags ) ) {
+ $targets = sprintf(
+ '$waf->update_targets(%s, $rule->id, $rule->tags)',
+ $targets
+ );
+ }
+ // prepare the operation.
+ $op_name = strtolower( $expression['operator'] );
+ $op_value = $expression['value'];
+ // these operators support macro expansion, which we can do some of ahead of time.
+ // the rest have their values directly printed out in PHP.
+ $expand_macro_operators = array(
+ 'beginswith',
+ 'contains',
+ 'containsword',
+ 'endswith',
+ 'eq',
+ 'ge',
+ 'gt',
+ 'le',
+ 'lt',
+ 'rsub',
+ 'streq',
+ 'within',
+ '',
+ );
+ if ( in_array( $op_name, $expand_macro_operators, true ) ) {
+ $op_value = $this->expand_macro( $op_value );
+ } else {
+ if ( 'validatebyterange' === $op_name ) {
+ $op_value = self::parse_byte_range( $op_value );
+ }
+ $op_value = var_export( $op_value, true );
+ }
+ // write the condition.
+ $php_lines[] = sprintf(
+ 'if($waf->match_targets(%s,%s,%s,%s,%s,%s)) {',
+ var_export( isset( $rule['transforms'] ) ? $rule['transforms'] : array(), true ),
+ $targets,
+ var_export( $op_name, true ),
+ $op_value,
+ $expression['not'] ? 'true' : 'false',
+ $expression['capture'] ? 'true' : 'false'
+ );
+
+ // add the actions.
+ $php_lines = array_merge(
+ $php_lines,
+ $this->compile_actions(
+ isset( $expression['actions'] )
+ ? $expression['actions']
+ : array()
+ ),
+ $other_actions
+ );
+ // close the condition block.
+ $php_lines[] = '}';
+ }
+
+ return $php_lines;
+ }
+
+ /**
+ * Expand macro.
+ *
+ * @param string $v Value.
+ */
+ private function expand_macro( $v ) {
+ if ( '' === $v ) {
+ return "''";
+ }
+ $matched = array();
+ $replaced = preg_replace_callback(
+ '/%\{[\w\.\-]+\}/',
+ function ( $m ) use ( &$matched ) {
+ $k = strtolower( substr( $m[0], 2, -1 ) );
+ if ( 1 === preg_match( '/^(tx|ip)\./', $k ) ) {
+ $matched[] = '$waf->get_var(' . var_export( $k, true ) . ')';
+ } elseif ( strpos( $k, 'request_headers.' ) === 0 ) {
+ $matched[] = "\$waf->meta('headers', " . var_export( substr( $k, 16 ), true ) . ')';
+ } else {
+ switch ( $k ) {
+ case 'matched_var':
+ $matched[] = '$waf->matched_var';
+ break;
+ case 'matched_var_name':
+ $matched[] = '$waf->matched_var_name';
+ break;
+ case 'remote_addr':
+ case 'request_line':
+ $matched[] = '$waf->meta(\'' . $k . '\')';
+ break;
+ case 'rule.msg':
+ case 'rule.reason':
+ $matched[] = '$rule->reason';
+ break;
+ case 'reqbody_error_msg':
+ $matched[] = '';
+ // these are unsupported macros from modsecurity.
+ break;
+ default:
+ $matched[] = '';
+ var_dump( 'Unknown macro:', $m );
+ }
+ }
+ return "\r\f\r";
+ },
+ $v
+ );
+ $parts = array_filter(
+ preg_split( '/\r+/', $replaced ),
+ function ( $s ) {
+ return '' !== $s; }
+ );
+ $parts = array_map(
+ function ( $s ) use ( &$matched ) {
+ return "\f" === $s
+ ? array_shift( $matched )
+ : var_export( $s, true );
+ },
+ $parts
+ );
+ $parts = array_filter(
+ $parts,
+ function ( $s ) {
+ return '' !== $s;
+ }
+ );
+ $string = implode( '.', $parts );
+ if ( preg_match( '/\.$/', $string ) ) {
+ var_dump(
+ $v,
+ $parts,
+ array_filter(
+ $parts,
+ function ( $p ) {
+ return null !== $p;
+ }
+ ),
+ $string
+ );
+ exit;
+ }
+
+ return $string;
+ }
+
+ /**
+ * Maybe will change at runtime.
+ *
+ * @param string $type Type.
+ * @param string $prop Prop.
+ * @param string $value Value.
+ */
+ private function maybe_will_change_at_runtime( $type, $prop, $value ) {
+ $key = strtolower( "$type/$prop/$value" );
+ $this->maybe_will_change[ $key ] = true;
+ }
+
+ /**
+ * Check if maybe will change at runtime.
+ *
+ * @param string $type Type.
+ * @param string $rule_id Rule id.
+ * @param array $rule_tags Rule tags.
+ */
+ private function check_if_maybe_will_change_at_runtime( $type, $rule_id, $rule_tags ) {
+ // check to see if the rule ID might be targeted by a runtime action.
+ if ( isset( $this->maybe_will_change[ "$type/id/$rule_id" ] ) ) {
+ return true;
+ }
+ // check to see if one of the tags might be targeted by a runtime action.
+ foreach ( $rule_tags as $tag ) {
+ if ( isset( $this->maybe_will_change[ "$type/tag/$tag" ] ) ) {
+ return true;
+ }
+ }
+ // None of the rule's attributes are being targeted.
+ return false;
+ }
+
+ /**
+ * Get marker label.
+ *
+ * @param string $name Name.
+ */
+ private function get_marker_label( $name ) {
+ if ( ! isset( $this->marker_map[ $name ] ) ) {
+ $this->marker_map[ $name ] = preg_replace( '/\W/', '_', uniqid( 'marker_', true ) );
+ }
+ return $this->marker_map[ $name ];
+ }
+
+ /**
+ * Given an array of byte numbers and/or ranges of numbers, compile into a data structure that will
+ * be easier to evaluate at runtime.
+ *
+ * @param mixed $ranges Ranges.
+ * @return array
+ */
+ public static function parse_byte_range( $ranges ) {
+ $range = array();
+ $min = PHP_INT_MAX;
+ $max = 0;
+ foreach ( $ranges as $b ) {
+ if ( false === strpos( $b, '-' ) ) {
+ $b = intval( $b );
+ $min = min( $b, $min );
+ $max = max( $b, $max );
+ $range[] = $b;
+ } else {
+ list( $b0, $b1 ) = preg_split( '/\s*-\s*/', $b );
+ $b0 = intval( $b0 );
+ $b1 = intval( $b1 );
+ $min = min( $b0, $min );
+ $max = max( $b1, $max );
+ $range[] = array( $b0, $b1 );
+ }
+ }
+
+ return array(
+ 'min' => $min,
+ 'max' => $max,
+ 'range' => $range,
+ );
+ }
+}
diff --git a/projects/packages/waf/src/class-waf-runtime.php b/projects/packages/waf/src/class-waf-runtime.php
new file mode 100644
index 0000000000000..b4af3dbdad069
--- /dev/null
+++ b/projects/packages/waf/src/class-waf-runtime.php
@@ -0,0 +1,643 @@
+ array(),
+ 'tag' => array(),
+ );
+
+ /**
+ * Targets to remove.
+ *
+ * @var array[]
+ */
+ private $targets_to_remove = array(
+ 'id' => array(),
+ 'tag' => array(),
+ );
+
+ /**
+ * Constructor method.
+ *
+ * @param WafTransforms $transforms Transforms.
+ * @param WafOperators $operators Operators.
+ */
+ public function __construct( $transforms, $operators ) {
+ $this->transforms = $transforms;
+ $this->operators = $operators;
+ }
+
+ /**
+ * Rule removed method.
+ *
+ * @param string $id Ids.
+ * @param string[] $tags Tags.
+ */
+ public function rule_removed( $id, $tags ) {
+ if ( isset( $this->rules_to_remove['id'][ $id ] ) ) {
+ return true;
+ }
+ foreach ( $tags as $tag ) {
+ if ( isset( $this->rules_to_remove['tag'][ $tag ] ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Update Targets.
+ *
+ * @param array $targets Targets.
+ * @param string $rule_id Rule id.
+ * @param string[] $rule_tags Rule tags.
+ */
+ public function update_targets( $targets, $rule_id, $rule_tags ) {
+ $updates = array();
+ // look for target updates based on the rule's ID.
+ if ( isset( $this->targets_to_remove['id'][ $rule_id ] ) ) {
+ foreach ( $this->targets_to_remove['id'][ $rule_id ] as $name => $props ) {
+ $updates[] = array( $name, $props );
+ }
+ }
+ // look for target updates based on the rule's tags.
+ foreach ( $rule_tags as $tag ) {
+ if ( isset( $this->targets_to_remove['tag'][ $tag ] ) ) {
+ foreach ( $this->targets_to_remove['tag'][ $tag ] as $name => $props ) {
+ $updates[] = array( $name, $props );
+ }
+ }
+ }
+ // apply any found target updates.
+
+ foreach ( $updates as list( $name, $props ) ) {
+ if ( isset( $targets[ $name ] ) ) {
+ // we only need to remove targets that exist.
+ if ( true === $props ) {
+ // if the entire target is being removed, remove it.
+ unset( $targets[ $name ] );
+ } else {
+ // otherwise just mark single props to ignore.
+ $targets[ $name ]['except'] = array_merge(
+ isset( $targets[ $name ]['except'] ) ? $targets[ $name ]['except'] : array(),
+ $props
+ );
+ }
+ }
+ }
+ return $targets;
+ }
+
+ /**
+ * Return TRUE if at least one of the targets matches the rule.
+ *
+ * @param string[] $transforms One of the transform methods defined in the JetpackWafTransforms class.
+ * @param mixed $targets Targets.
+ * @param string $match_operator Match operator.
+ * @param mixed $match_value Match value.
+ * @param bool $match_not Match not.
+ * @param bool $capture Capture.
+ * @return bool
+ */
+ public function match_targets( $transforms, $targets, $match_operator, $match_value, $match_not, $capture = false ) {
+ $this->matched_vars = array();
+ $this->matched_var_names = array();
+ $this->matched_var = '';
+ $this->matched_var_name = '';
+ $match_found = false;
+
+ // get values.
+ $values = $this->normalize_targets( $targets );
+
+ // apply transforms.
+ foreach ( $transforms as $t ) {
+ foreach ( $values as &$v ) {
+ $v['value'] = $this->transforms->$t( $v['value'] );
+ }
+ }
+
+ // pass each target value to the operator to find any that match.
+ $matched = array();
+ $captures = array();
+ foreach ( $values as $v ) {
+ $match = $this->operators->{$match_operator}( $v['value'], $match_value );
+ $did_match = false !== $match;
+ if ( $match_not !== $did_match ) {
+ // If either:
+ // - rule is negated ("not" flag set) and the target was not matched
+ // - rule not negated and the target was matched
+ // then this is considered a match.
+ $match_found = true;
+ $this->matched_var_names[] = $v['source'];
+ $this->matched_vars[] = $v['value'];
+ $this->matched_var_name = end( $this->matched_var_names );
+ $this->matched_var = end( $this->matched_vars );
+ $matched[] = array( $v, $match );
+ // Set any captured matches into state if the rule has the "capture" flag.
+ if ( $capture ) {
+ $captures = is_array( $match ) ? $match : array( $match );
+ foreach ( array_slice( $captures, 0, 10 ) as $i => $c ) {
+ $this->set_var( "tx.$i", $c );
+ }
+ }
+ }
+ }
+
+ return $match_found;
+ }
+
+ /**
+ * Block.
+ *
+ * @param string $action Action.
+ * @param string $rule_id Rule id.
+ * @param string $reason Block reason.
+ * @param int $status_code Http status code.
+ */
+ public function block( $action, $rule_id, $reason, $status_code = 403 ) {
+ if ( ! $reason ) {
+ $reason = "rule $rule_id";
+ }
+ jpwaf_write_blocklog( $rule_id, $reason );
+ error_log( "Jetpack WAF Blocked Request\t$action\t$rule_id\t$status_code\t$reason" );
+ header( "X-JetpackWAF-Blocked: $status_code $reason" );
+ if ( defined( 'JETPACK_WAF_MODE' ) && 'normal' === JETPACK_WAF_MODE ) {
+ header( $_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden', true, $status_code );
+ die( "rule $rule_id" );
+ }
+ }
+
+ /**
+ * Redirect.
+ *
+ * @param string $rule_id Rule id.
+ * @param string $url Url.
+ */
+ public function redirect( $rule_id, $url ) {
+ error_log( "Jetpack WAF Redirected Request.\tRule:$rule_id\t$url" );
+ header( "Location: $url" );
+ exit;
+ }
+
+ /**
+ * Flag rule for removal.
+ *
+ * @param string $prop Prop.
+ * @param string $value Value.
+ */
+ public function flag_rule_for_removal( $prop, $value ) {
+ if ( 'id' === $prop ) {
+ $this->rules_to_remove['id'][ $value ] = true;
+ } else {
+ $this->rules_to_remove['tag'][ $value ] = true;
+ }
+ }
+
+ /**
+ * Flag target for removal.
+ *
+ * @param string $id_or_tag Id or tag.
+ * @param string $id_or_tag_value Id or tag value.
+ * @param string $name Name.
+ * @param string $prop Prop.
+ */
+ public function flag_target_for_removal( $id_or_tag, $id_or_tag_value, $name, $prop = null ) {
+ if ( null === $prop ) {
+ $this->targets_to_remove[ $id_or_tag ][ $id_or_tag_value ][ $name ] = true;
+ } else {
+ if (
+ ! isset( $this->targets_to_remove[ $id_or_tag ][ $id_or_tag_value ][ $name ] )
+ // if the entire target is already being removed then it would be redundant to remove a single property.
+ || true !== $this->targets_to_remove[ $id_or_tag ][ $id_or_tag_value ][ $name ]
+ ) {
+ $this->targets_to_remove[ $id_or_tag ][ $id_or_tag_value ][ $name ][] = $prop;
+ }
+ }
+ }
+
+ /**
+ * Get variable value.
+ *
+ * @param string $key Key.
+ */
+ public function get_var( $key ) {
+ return isset( $this->state[ $key ] )
+ ? $this->state[ $key ]
+ : '';
+ }
+
+ /**
+ * Set variable value.
+ *
+ * @param string $key Key.
+ * @param string $value Value.
+ */
+ public function set_var( $key, $value ) {
+ $this->state[ $key ] = $value;
+ }
+
+ /**
+ * Increment variable.
+ *
+ * @param string $key Key.
+ * @param mixed $value Value.
+ */
+ public function inc_var( $key, $value ) {
+ if ( ! isset( $this->state[ $key ] ) ) {
+ $this->state[ $key ] = 0;
+ }
+ $this->state[ $key ] += floatval( $value );
+ }
+
+ /**
+ * Decrement variable.
+ *
+ * @param string $key Key.
+ * @param mixed $value Value.
+ */
+ public function dec_var( $key, $value ) {
+ if ( ! isset( $this->state[ $key ] ) ) {
+ $this->state[ $key ] = 0;
+ }
+ $this->state[ $key ] -= floatval( $value );
+ }
+
+ /**
+ * Unset variable.
+ *
+ * @param string $key Key.
+ */
+ public function unset_var( $key ) {
+ unset( $this->state[ $key ] );
+ }
+
+ /**
+ * Meta.
+ *
+ * @param string $key Key.
+ * @param string $prop Prop.
+ */
+ public function meta( $key, $prop = false ) {
+ if ( ! isset( $this->metadata[ $key ] ) ) {
+ $value = null;
+ switch ( $key ) {
+ case 'headers':
+ $value = array();
+ foreach ( $_SERVER as $k => $v ) {
+ $k = strtolower( $k );
+ if ( 'http_' === substr( $k, 0, 5 ) ) {
+ $value[ $this->normalizeHeaderName( substr( $k, 0, 5 ) ) ] = $v;
+ } elseif ( 'content_type' === $k ) {
+ $value['content-type'] = $v;
+ } elseif ( 'content_length' === $k ) {
+ $value['content-length'] = $v;
+ }
+ }
+ $value['content-type'] = ( ! isset( $value['content-type'] ) || '' === $value['content-type'] )
+ // default Content-Type per RFC 7231 section 3.1.5.5.
+ ? 'application/octet-stream'
+ : $value['content-type'];
+ $value['content-length'] = ( isset( $value['content-length'] ) && '' !== $value['content-length'] )
+ ? $value['content-length']
+ // if the content-length header is missing, default it to zero.
+ : '0';
+ break;
+ case 'remote_addr':
+ $value = '';
+ if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
+ $value = $_SERVER['HTTP_CLIENT_IP'];
+ } elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
+ $value = $_SERVER['HTTP_X_FORWARDED_FOR'];
+ } elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
+ $value = $_SERVER['REMOTE_ADDR'];
+ }
+ break;
+ case 'request_method':
+ $value = empty( $_SERVER['REQUEST_METHOD'] )
+ ? 'GET'
+ : $_SERVER['REQUEST_METHOD'];
+ break;
+ case 'request_protocol':
+ $value = empty( $_SERVER['SERVER_PROTOCOL'] )
+ ? ( empty( $_SERVER['HTTPS'] ) ? 'HTTP' : 'HTTPS' )
+ : $_SERVER['SERVER_PROTOCOL'];
+ break;
+ case 'request_uri':
+ $value = isset( $_SERVER['REQUEST_URI'] )
+ ? $_SERVER['REQUEST_URI']
+ : '';
+ break;
+ case 'request_uri_raw':
+ $value = ( isset( $_SERVER['https'] ) ? 'https://' : 'http://' ) . $_SERVER['SERVER_NAME'] . $this->meta( 'request_uri' );
+ break;
+ case 'request_filename':
+ $value = strtok(
+ isset( $_SERVER['REQUEST_URI'] )
+ ? $_SERVER['REQUEST_URI']
+ : '',
+ '?'
+ );
+ break;
+ case 'request_line':
+ $value = sprintf(
+ '%s %s %s',
+ $this->meta( 'request_method' ),
+ $this->meta( 'request_uri' ),
+ $this->meta( 'request_protocol' )
+ );
+ break;
+ case 'request_basename':
+ $value = basename( $this->meta( 'request_filename' ) );
+ break;
+ case 'request_body':
+ $value = file_get_contents( 'php://input' );
+ break;
+ case 'query_string':
+ $value = isset( $_SERVER['QUERY_STRING'] ) ? $_SERVER['QUERY_STRING'] : '';
+ }
+ $this->metadata[ $key ] = $value;
+ }
+
+ return false === $prop
+ ? $this->metadata[ $key ]
+ : ( isset( $this->metadata[ $key ][ $prop ] ) ? $this->metadata[ $key ][ $prop ] : '' );
+ }
+
+ /**
+ * State values.
+ *
+ * @param string $prefix Prefix.
+ */
+ private function state_values( $prefix ) {
+ $output = array();
+ $len = strlen( $prefix );
+ foreach ( $this->state as $k => $v ) {
+ if ( 0 === stripos( $k, $prefix ) ) {
+ $output[ substr( $k, $len ) ] = $v;
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * Change a string to all lowercase and replace spaces and underscores with dashes.
+ *
+ * @param string $name Name.
+ * @return string
+ */
+ public function normalize_header_name( $name ) {
+ return str_replace( array( ' ', '_' ), '-', strtolower( $name ) );
+ }
+
+ /**
+ * Normalize targets.
+ *
+ * @param array $targets Targets.
+ */
+ public function normalize_targets( $targets ) {
+ $return = array();
+ foreach ( $targets as $k => $v ) {
+ $count_only = isset( $v['count'] );
+ $only = isset( $v['only'] ) ? $v['only'] : array();
+ $except = isset( $v['except'] ) ? $v['except'] : array();
+ $_k = strtolower( $k );
+ switch ( $_k ) {
+ case 'request_headers':
+ $only = array_map(
+ function ( $t ) {
+ return '/' === $t[0] ? $t : $this->normalize_header_name( $t );
+ },
+ $only
+ );
+ $except = array_map(
+ function ( $t ) {
+ return '/' === $t[0] ? $t : $this->normalize_header_name( $t );
+ },
+ $except
+ );
+ $this->normalize_array_target( $this->meta( 'headers' ), $only, $except, $k, $return, $count_only );
+ continue 2;
+ case 'request_headers_names':
+ $this->normalize_array_target( array_keys( $this->meta( 'headers' ) ), array(), array(), $k, $return, $count_only );
+ continue 2;
+ case 'request_method':
+ case 'request_protocol':
+ case 'request_uri':
+ case 'request_uri_raw':
+ case 'request_filename':
+ case 'remote_addr':
+ case 'request_basename':
+ case 'request_body':
+ case 'query_string':
+ case 'request_line':
+ $v = $this->meta( $_k );
+ break;
+ case 'tx':
+ case 'ip':
+ $this->normalize_array_target( $this->state_values( "$k." ), $only, $except, $k, $return, $count_only );
+ continue 2;
+ case 'request_cookies':
+ $this->normalize_array_target( $_COOKIE, $only, $except, $k, $return, $count_only );
+ continue 2;
+ case 'request_cookies_names':
+ $this->normalize_array_target( array_keys( $_COOKIE ), array(), array(), $k, $return, $count_only );
+ continue 2;
+ case 'args':
+ $this->normalize_array_target( $_REQUEST, $only, $except, $k, $return, $count_only );
+ continue 2;
+ case 'args_names':
+ $this->normalize_array_target( array_keys( $_REQUEST ), array(), array(), $k, $return, $count_only );
+ continue 2;
+ case 'args_get':
+ $this->normalize_array_target( $_GET, $only, $except, $k, $return, $count_only );
+ continue 2;
+ case 'args_get_names':
+ $this->normalize_array_target( array_keys( $_GET ), array(), array(), $k, $return, $count_only );
+ continue 2;
+ case 'args_post':
+ $this->normalize_array_target( $_POST, $only, $except, $k, $return, $count_only );
+ continue 2;
+ case 'args_post_names':
+ $this->normalize_array_target( array_keys( $_POST ), array(), array(), $k, $return, $count_only );
+ continue 2;
+ case 'files':
+ $names = array_map(
+ function ( $f ) {
+ return $f['name'];
+ },
+ $_FILES
+ );
+ $this->normalize_array_target( $names, $only, $except, $k, $return, $count_only );
+ continue 2;
+ case 'files_names':
+ $this->normalize_array_target( array_keys( $_FILES ), $only, $except, $k, $return, $count_only );
+ continue 2;
+ default:
+ var_dump( 'Unknown target', $k, $v );
+ exit;
+ }
+ $return[] = array(
+ 'name' => $k,
+ 'value' => $v,
+ 'source' => $k,
+ );
+ }
+
+ return $return;
+ }
+
+ /**
+ * Normalize array target.
+ *
+ * @param array $source Source.
+ * @param array $only Only.
+ * @param array $excl Excl.
+ * @param string $name Name.
+ * @param array $results Results.
+ * @param bool $count_only Count only.
+ */
+ private function normalize_array_target( $source, $only, $excl, $name, &$results, $count_only ) {
+ $output = array();
+ $has_only = isset( $only[0] );
+ $has_excl = isset( $excl[0] );
+
+ if ( $has_only ) {
+ foreach ( $only as $prop ) {
+ if ( isset( $source[ $prop ] ) && $this->key_matches( $prop, $only ) ) {
+ $output[ $prop ] = $source[ $prop ];
+ }
+ }
+ } else {
+ $output = $source;
+ }
+
+ if ( $has_excl ) {
+ foreach ( array_keys( $output ) as $k ) {
+ if ( $this->key_matches( $k, $excl ) ) {
+ unset( $output[ $k ] );
+ }
+ }
+ }
+
+ // todo: normalize nested arrays in values??
+ if ( $count_only ) {
+ $results[] = array(
+ 'name' => $name,
+ 'value' => count( $output ),
+ 'source' => '&' . $name,
+ );
+ } else {
+ foreach ( $output as $tk => $tv ) {
+ $results[] = array(
+ 'name' => $tk,
+ 'value' => $tv,
+ 'source' => "$name:$tk",
+ );
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Key matches.
+ *
+ * @param string $input Input.
+ * @param array $patterns Patterns.
+ */
+ private function key_matches( $input, $patterns ) {
+ foreach ( $patterns as $p ) {
+ if ( '/' === $p[0] ) {
+ if ( 1 === preg_match( $p, $input ) ) {
+ return true;
+ }
+ } else {
+ if ( 0 === strcasecmp( $p, $input ) ) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/projects/packages/waf/src/class-waf-transforms.php b/projects/packages/waf/src/class-waf-transforms.php
new file mode 100644
index 0000000000000..496a1fa315a9f
--- /dev/null
+++ b/projects/packages/waf/src/class-waf-transforms.php
@@ -0,0 +1,342 @@
+normalize_path( str_replace( '\\', '/', $value ) );
+ }
+
+ /**
+ * Removes all NUL bytes from input.
+ *
+ * @param string $value value to be filtered.
+ * @return string
+ */
+ public function remove_nulls( $value ) {
+ return str_replace( "\x0", '', $value );
+ }
+
+ /**
+ * Remove all whitespace characters from input.
+ *
+ * @param string $value value to be filtered.
+ * @return string
+ */
+ public function remove_whitespace( $value ) {
+ return preg_replace( '/\s/', '', $value );
+ }
+
+ /**
+ * Replaces each occurrence of a C-style comment (/ * ... * /) with a single space.
+ * Unterminated comments will also be replaced with a space. However, a standalone termination of a comment (* /) will not be acted upon.
+ *
+ * @param string $value value to be filtered.
+ * @return string
+ */
+ public function replace_comments( $value ) {
+ $value = preg_replace( '~/\*.*?\*/|/\*.*?$~Ds', ' ', $value );
+ return explode( '/*', $value, 2 )[0];
+ }
+
+ /**
+ * Removes common comments chars (/ *, * /, --, #).
+ *
+ * @param string $value value to be filtered.
+ * @return string
+ */
+ public function remove_comments_char( $value ) {
+ return preg_replace( '~/*|*/|--|#|//~', '', $value );
+ }
+
+ /**
+ * Replaces each NUL byte in input with a space.
+ *
+ * @param string $value value to be filtered.
+ * @return string
+ */
+ public function replace_nulls( $value ) {
+ return str_replace( "\x0", ' ', $value );
+ }
+
+ /**
+ * Decode a URL-encoded input string.
+ *
+ * @param string $value value to be decoded.
+ * @return string
+ */
+ public function url_decode( $value ) {
+ return urldecode( $value );
+ }
+
+ /**
+ * Decode a URL-encoded input string.
+ *
+ * @param string $value value to be decoded.
+ * @return string
+ */
+ public function url_decode_uni( $value ) {
+ error_log( 'JETPACKWAF TRANSFORM NOT IMPLEMENTED: urlDecodeUni' );
+ return $value;
+ }
+
+ /**
+ * Decode a json encoded input string.
+ *
+ * @param string $value value to be decoded.
+ * @return string
+ */
+ public function js_decode( $value ) {
+ error_log( 'JETPACKWAF TRANSFORM NOT IMPLEMENTED: jsDecode' );
+ return $value;
+ }
+
+ /**
+ * Convert all characters to uppercase.
+ *
+ * @param string $value value to be encoded.
+ * @return string
+ */
+ public function uppercase( $value ) {
+ return strtoupper( $value );
+ }
+
+ /**
+ * Calculate a SHA1 hash from the input string.
+ *
+ * @param mixed $value value to be hashed.
+ * @return string
+ */
+ public function sha1( $value ) {
+ return sha1( $value, true );
+ }
+
+ /**
+ * Remove whitespace from the left side of the input string.
+ *
+ * @param string $value value to be trimmed.
+ * @return string
+ */
+ public function trim_left( $value ) {
+ return ltrim( $value );
+ }
+
+ /**
+ * Remove whitespace from the right side of the input string.
+ *
+ * @param string $value value to be trimmed.
+ * @return string
+ */
+ public function trim_right( $value ) {
+ return rtrim( $value );
+ }
+
+ /**
+ * Remove whitespace from both sides of the input string.
+ *
+ * @param string $value value to be trimmed.
+ * @return string
+ */
+ public function trim( $value ) {
+ return trim( $value );
+ }
+
+ /**
+ * Convert utf-8 characters to unicode characters
+ *
+ * @param string $value value to be encoded.
+ * @return string
+ */
+ public function utf8_to_unicode( $value ) {
+ return preg_replace( '/\\\u(?=[a-f0-9]{4})/', '%u', substr( json_encode( $value ), 1, -1 ) );
+ }
+}
diff --git a/projects/packages/waf/tests/php/bootstrap.php b/projects/packages/waf/tests/php/bootstrap.php
new file mode 100644
index 0000000000000..46763b04a2cdb
--- /dev/null
+++ b/projects/packages/waf/tests/php/bootstrap.php
@@ -0,0 +1,11 @@
+o = new WafOperators();
+ }
+
+ /**
+ * Main test function
+ *
+ * @param string $ofn The name of the operator function that is being tested.
+ * @param array ...$tests The tests cases.
+ *
+ * @dataProvider transformDataProvider
+ */
+ public function testOperators( $ofn, ...$tests ) {
+ $n = 1;
+ for ( $i = 0, $z = count( $tests ); $i < $z; $i += 3 ) {
+ $input = $tests[ $i ];
+ $param = $tests[ $i + 1 ];
+ $expected = $tests[ $i + 2 ];
+ $this->assertSame(
+ $expected,
+ $this->o->$ofn( $input, $param ),
+ sprintf( 'Failed %s assertion #%d with input: %s ', $ofn, $n, $input )
+ );
+ $n++;
+ }
+ }
+
+ /**
+ * Test data provider
+ */
+ public function transformDataProvider() {
+ yield array(
+ 'begins_with',
+ // input, paramToMatch, expected_return.
+ '',
+ '',
+ '',
+ 'TestCase',
+ '',
+ '',
+ 'abcdef',
+ 'abcdef',
+ 'abcdef',
+ 'abcdefghi',
+ 'abcdef',
+ 'abcdef',
+ '',
+ 'TestCase',
+ false,
+ 'abc',
+ 'abcdef',
+ false,
+ );
+
+ yield array(
+ 'contains',
+ // input, paramToMatch, expected_return.
+ '',
+ '',
+ false,
+ 'TestCase',
+ '',
+ false,
+ 'abcdefghi',
+ 'abc',
+ 'abc',
+ 'abcdefghi',
+ 'ghi',
+ 'ghi',
+ 'x',
+ 'x',
+ 'x',
+ 'xyz',
+ 'y',
+ 'y',
+ 'hidinX<-not quite, but is later on->hiding',
+ 'hiding',
+ 'hiding',
+ );
+
+ yield array(
+ 'contains_word',
+ // input, paramToMatch, expected_return.
+ '',
+ '',
+ '',
+ 'TestCase',
+ '',
+ '',
+ 'abc def ghi',
+ 'abc',
+ 'abc',
+ 'abc def ghi',
+ 'def',
+ 'def',
+ 'abc def ghi',
+ 'ghi',
+ 'ghi',
+ "abc\0def ghi",
+ 'abc',
+ 'abc',
+ "abc\0def ghi",
+ 'def',
+ 'def',
+ 'x',
+ 'x',
+ 'x',
+ ' x ',
+ 'x',
+ 'x',
+ 'hidingX<-not on word boundary, but is later on->hiding',
+ 'hiding',
+ 'hiding',
+ '',
+ 'TestCase',
+ false,
+ 'abcdefghi',
+ 'abc',
+ false,
+ 'abcdefghi',
+ 'def',
+ false,
+ 'abcdefghi',
+ 'ghi',
+ false,
+ 'xyz',
+ 'y',
+ false,
+ );
+
+ yield array(
+ 'ends_with',
+ // input, paramToMatch, expected_return.
+ '',
+ '',
+ '',
+ 'TestCase',
+ '',
+ '',
+ 'abcdefghi',
+ 'ghi',
+ 'ghi',
+ "abcdef\0ghi",
+ 'ghi',
+ 'ghi',
+ '',
+ 'TestCase',
+ false,
+ 'abcdefghi',
+ 'abc',
+ false,
+ 'abcdefghi',
+ 'def',
+ false,
+ );
+
+ yield array(
+ 'eq',
+ // input, paramToMatch, expected_return.
+ '',
+ '0',
+ '',
+ '0',
+ 'xxx',
+ '0',
+ 'xxx',
+ '0',
+ 'xxx',
+ '0',
+ '0',
+ '0',
+ '5',
+ '5',
+ '5',
+ '-5',
+ '-5',
+ '-5',
+ '',
+ '5',
+ false,
+ '5',
+ 'xxx',
+ false,
+ '-1',
+ 'xxx',
+ false,
+ 'xxx',
+ '5',
+ false,
+ '-5',
+ '0',
+ false,
+ '5',
+ '0',
+ false,
+ '0',
+ '5',
+ false,
+ '10',
+ '5',
+ false,
+ );
+
+ yield array(
+ 'ge',
+ // input, paramToMatch, expected_return.
+ '',
+ '0',
+ '',
+ '5',
+ 'xxx',
+ '5',
+ 'xxx',
+ '0',
+ 'xxx',
+ '0',
+ '0',
+ '0',
+ '5',
+ '0',
+ '5',
+ '5',
+ '5',
+ '5',
+ '10',
+ '5',
+ '10',
+ '',
+ '5',
+ false,
+ '-1',
+ 'xxx',
+ false,
+ 'xxx',
+ '5',
+ false,
+ '-5',
+ '',
+ false,
+ '0',
+ '5',
+ false,
+ );
+
+ yield array(
+ 'gt',
+ // input, paramToMatch, expected_return.
+ '5',
+ 'xxx',
+ '5',
+ 'xxx',
+ '-1',
+ 'xxx',
+ '5',
+ '0',
+ '5',
+ '10',
+ '5',
+ '10',
+ '',
+ '0',
+ false,
+ '',
+ '5',
+ false,
+ '-1',
+ 'xxx',
+ false,
+ 'xxx',
+ '5',
+ false,
+ '-5',
+ '0',
+ false,
+ '0',
+ '0',
+ false,
+ '0',
+ '5',
+ false,
+ '5',
+ '5',
+ false,
+ );
+
+ yield array(
+ 'le',
+ // input, paramToMatch, expected_return.
+ '',
+ '0',
+ '',
+ '',
+ '5',
+ '',
+ '5',
+ 'xxx',
+ false,
+ '-1',
+ 'xxx',
+ '-1',
+ 'xxx',
+ '0',
+ 'xxx',
+ 'xxx',
+ '5',
+ 'xxx',
+ '-5',
+ '0',
+ '-5',
+ '0',
+ '0',
+ '0',
+ '5',
+ '0',
+ false,
+ '0',
+ '5',
+ '0',
+ '5',
+ '5',
+ '5',
+ '10',
+ '5',
+ false,
+ );
+
+ yield array(
+ 'lt',
+ // input, paramToMatch, expected_return.
+ '',
+ '0',
+ false,
+ '',
+ '5',
+ '',
+ '5',
+ 'xxx',
+ false,
+ '-1',
+ 'xxx',
+ '-1',
+ 'xxx',
+ '-1',
+ false,
+ 'xxx',
+ '5',
+ 'xxx',
+ '-5',
+ '0',
+ '-5',
+ '0',
+ '0',
+ false,
+ '5',
+ '0',
+ false,
+ '0',
+ '5',
+ '0',
+ '5',
+ '5',
+ false,
+ '10',
+ '5',
+ false,
+ );
+
+ yield array(
+ 'no_match',
+ // input, paramToMatch, expected_return.
+ '',
+ '',
+ false,
+ 'TestCase',
+ '',
+ false,
+ '',
+ 'TestCase',
+ false,
+ );
+
+ yield array(
+ 'rx',
+ // input, paramToMatch, expected_return.
+ '',
+ '//Ds',
+ array( '' ),
+ '',
+ '/TestCase/Ds',
+ false,
+ 'TestCase',
+ '//Ds',
+ array( '' ),
+ 'abcdefghi',
+ '/abc/Ds',
+ array( 'abc' ),
+ 'abcdefghi',
+ '/def/Ds',
+ array( 'def' ),
+ 'abcdefghi',
+ '/ghi/Ds',
+ array( 'ghi' ),
+ 'abcdefghi',
+ '/ghij/Ds',
+ false,
+ 'SELECT pg_sleep(10);',
+ '/(?i:(sleep\\((\\s*?)(\\d*?)(\\s*?)\\)|benchmark\\((.*?)\\,(.*?)\\)))/Ds',
+ array( 'sleep(10)', 'sleep(10)', '', '10', '' ),
+ );
+
+ yield array(
+ 'streq',
+ // input, paramToMatch, expected_return.
+ '',
+ '',
+ '',
+ '',
+ 'TestCase',
+ false,
+ 'TestCase',
+ '',
+ false,
+ 'abcdefghi',
+ 'abc',
+ false,
+ 'abcdefghi',
+ 'def',
+ false,
+ 'abcdefghi',
+ 'ghi',
+ false,
+ 'abcdefghi',
+ 'abcdefghi',
+ 'abcdefghi',
+ );
+
+ yield array(
+ 'unconditional_match',
+ // input, paramToMatch, expected_return.
+ '',
+ '',
+ '',
+ '',
+ 'TestCase',
+ '',
+ 'TestCase',
+ '',
+ 'TestCase',
+ );
+
+ $a2i = ord( 'a' ) . '-' . ord( 'i' );
+ yield array(
+ 'validate_byte_range',
+ // input, paramToMatch, expected_return.
+ '',
+ WafRuleCompiler::parse_byte_range( array( '0-255' ) ),
+ false,
+ 'abcdefghi',
+ WafRuleCompiler::parse_byte_range( array( '0-255' ) ),
+ false,
+ 'abcdefghi',
+ WafRuleCompiler::parse_byte_range( array( $a2i ) ),
+ false,
+ 'abcdefghij',
+ WafRuleCompiler::parse_byte_range( array( $a2i ) ),
+ 'j',
+ );
+
+ yield array(
+ 'within',
+ // input, paramToMatch, expected_return.
+ '',
+ '',
+ false,
+ '',
+ 'TestCase',
+ false,
+ 'TestCase',
+ '',
+ false,
+ 'abc',
+ 'abcdefghi',
+ 'abc',
+ 'def',
+ 'abcdefghi',
+ 'def',
+ 'ghi',
+ 'abcdefghi',
+ 'ghi',
+ 'ghij',
+ 'abcdefghi',
+ false,
+ );
+ }
+}
diff --git a/projects/packages/waf/tests/php/test-waf-runtime.php b/projects/packages/waf/tests/php/test-waf-runtime.php
new file mode 100644
index 0000000000000..e4d932bcc0e92
--- /dev/null
+++ b/projects/packages/waf/tests/php/test-waf-runtime.php
@@ -0,0 +1,281 @@
+runtime = new WafRuntime( new WafTransforms(), new WafOperators() );
+ }
+
+ /**
+ * Test removing rule by id
+ */
+ public function testRemovingRuleById() {
+ $this->runtime->flag_rule_for_removal( 'id', '111' );
+ $this->assertTrue( $this->runtime->rule_removed( '111', array() ) );
+ $this->assertFalse( $this->runtime->rule_removed( '222', array() ) );
+ }
+
+ /**
+ * Test removing rule by tag
+ */
+ public function testRemovingRuleByTag() {
+ $this->runtime->flag_rule_for_removal( 'tag', 'abc' );
+ $this->assertTrue( $this->runtime->rule_removed( '111', array( 'abc' ) ) );
+ $this->assertTrue( $this->runtime->rule_removed( '111', array( 'abc', 'def' ) ) );
+ $this->assertTrue( $this->runtime->rule_removed( '111', array( '789', 'abc', 'def' ) ) );
+ $this->assertFalse( $this->runtime->rule_removed( '111', array() ) );
+ $this->assertFalse( $this->runtime->rule_removed( '111', array( 'abcdef' ) ) );
+ }
+
+ /**
+ * Test removing target rule by id
+ */
+ public function testRemovingTargetByRuleId() {
+ $this->runtime->flag_target_for_removal( 'id', '111', 'args' );
+ $this->assertEquals(
+ array( 'auth_type' => array() ),
+ $this->runtime->update_targets(
+ array(
+ 'args' => array(),
+ 'auth_type' => array(),
+ ),
+ '111',
+ array()
+ ),
+ 'Did not remove target'
+ );
+ $this->assertEquals(
+ array(
+ 'auth_type' => array(),
+ 'args' => array(),
+ ),
+ $this->runtime->update_targets(
+ array(
+ 'args' => array(),
+ 'auth_type' => array(),
+ ),
+ '222',
+ array()
+ ),
+ 'Incorrectly removed target of non-matching rule'
+ );
+ }
+
+ /**
+ * Test removing targed prop by rule id
+ */
+ public function testRemovingTargetPropByRuleId() {
+ $this->runtime->flag_target_for_removal( 'id', '111', 'args', 'p' );
+ $this->assertEquals(
+ array(
+ 'auth_type' => array(),
+ 'args' => array( 'except' => array( 'p' ) ),
+ ),
+ $this->runtime->update_targets(
+ array(
+ 'args' => array(),
+ 'auth_type' => array(),
+ ),
+ '111',
+ array()
+ ),
+ 'Did not create except list for removed prop'
+ );
+ $this->assertEquals(
+ array(
+ 'auth_type' => array(),
+ 'args' => array( 'except' => array( 'o', 'p' ) ),
+ ),
+ $this->runtime->update_targets(
+ array(
+ 'args' => array( 'except' => array( 'o' ) ),
+ 'auth_type' => array(),
+ ),
+ '111',
+ array()
+ ),
+ 'Did not add prop to existing except list'
+ );
+ $this->assertEquals(
+ array(
+ 'auth_type' => array(),
+ 'args' => array(
+ 'only' => array( 'z' ),
+ 'except' => array( 'o', 'p' ),
+ ),
+ ),
+ $this->runtime->update_targets(
+ array(
+ 'args' => array(
+ 'only' => array( 'z' ),
+ 'except' => array( 'o' ),
+ ),
+ 'auth_type' => array(),
+ ),
+ '111',
+ array()
+ )
+ );
+ $this->assertEquals(
+ array(
+ 'auth_type' => array(),
+ 'args' => array(),
+ ),
+ $this->runtime->update_targets(
+ array(
+ 'args' => array(),
+ 'auth_type' => array(),
+ ),
+ '222',
+ array()
+ ),
+ 'Incorrectly updated target of non-matching rule'
+ );
+ }
+
+ /**
+ * Test removing targed by rule tag
+ */
+ public function testRemovingTargetByRuleTag() {
+ $this->runtime->flag_target_for_removal( 'tag', 'abc', 'args' );
+ $this->assertEquals(
+ array( 'auth_type' => array() ),
+ $this->runtime->update_targets(
+ array(
+ 'args' => array(),
+ 'auth_type' => array(),
+ ),
+ '111',
+ array( 'abc' )
+ ),
+ 'Did not remove target'
+ );
+ $this->assertEquals(
+ array(
+ 'auth_type' => array(),
+ 'args' => array(),
+ ),
+ $this->runtime->update_targets(
+ array(
+ 'args' => array(),
+ 'auth_type' => array(),
+ ),
+ '222',
+ array( 'def' )
+ ),
+ 'Incorrectly removed target of non-matching rule'
+ );
+ }
+
+ /**
+ * Test removing targed prop by rule tag
+ */
+ public function testRemovingTargetPropByRuleTag() {
+ $this->runtime->flag_target_for_removal( 'tag', 'abc', 'args', 'p' );
+ $this->assertEquals(
+ array(
+ 'auth_type' => array(),
+ 'args' => array( 'except' => array( 'p' ) ),
+ ),
+ $this->runtime->update_targets(
+ array(
+ 'args' => array(),
+ 'auth_type' => array(),
+ ),
+ '111',
+ array( 'abc' )
+ ),
+ 'Did not create except list for removed prop'
+ );
+ $this->assertEquals(
+ array(
+ 'auth_type' => array(),
+ 'args' => array( 'except' => array( 'o', 'p' ) ),
+ ),
+ $this->runtime->update_targets(
+ array(
+ 'args' => array( 'except' => array( 'o' ) ),
+ 'auth_type' => array(),
+ ),
+ '222',
+ array( 'abc' )
+ ),
+ 'Did not add prop to existing except list'
+ );
+ $this->assertEquals(
+ array(
+ 'auth_type' => array(),
+ 'args' => array(
+ 'only' => array( 'z' ),
+ 'except' => array( 'o', 'p' ),
+ ),
+ ),
+ $this->runtime->update_targets(
+ array(
+ 'args' => array(
+ 'only' => array( 'z' ),
+ 'except' => array( 'o' ),
+ ),
+ 'auth_type' => array(),
+ ),
+ '333',
+ array( 'abc' )
+ )
+ );
+ $this->assertEquals(
+ array(
+ 'auth_type' => array(),
+ 'args' => array(),
+ ),
+ $this->runtime->update_targets(
+ array(
+ 'args' => array(),
+ 'auth_type' => array(),
+ ),
+ '444',
+ array( 'def' )
+ ),
+ 'Incorrectly updated target of non-matching rule'
+ );
+ }
+
+ /**
+ * Test vars
+ */
+ public function testVars() {
+ $this->assertSame( '', $this->runtime->get_var( 'abc' ) );
+ $this->runtime->set_var( 'abc', '123' );
+ $this->assertSame( '123', $this->runtime->get_var( 'abc' ) );
+ $this->runtime->inc_var( 'abc', 3 );
+ $this->assertEquals( 126, $this->runtime->get_var( 'abc' ) );
+ $this->runtime->dec_var( 'abc', 10 );
+ $this->assertEquals( 116, $this->runtime->get_var( 'abc' ) );
+ $this->runtime->inc_var( 'def', 2 );
+ $this->assertSame( 2.0, $this->runtime->get_var( 'def' ) );
+ $this->runtime->unset_var( 'abc' );
+ $this->assertSame( '', $this->runtime->get_var( 'abc' ) );
+ }
+}
diff --git a/projects/packages/waf/tests/php/test-waf-transforms.php b/projects/packages/waf/tests/php/test-waf-transforms.php
new file mode 100644
index 0000000000000..4e1ed160e24b0
--- /dev/null
+++ b/projects/packages/waf/tests/php/test-waf-transforms.php
@@ -0,0 +1,379 @@
+t = new GlobalWafTransforms();
+ }
+
+ /**
+ * Main test function
+ *
+ * @param string $tfn The name of the transform function that is being tested.
+ * @param array $tests The tests cases, each key in the array is the raw value with the value being the expected transformed value.
+ *
+ * @dataProvider transformDataProvider
+ */
+ public function testTransforms( $tfn, $tests ) {
+ $i = 1;
+ foreach ( $tests as $in => $out ) {
+ $this->assertSame( $out, $this->t->$tfn( $in ), "Failed #$i with input: $in" );
+ $i++;
+ }
+ }
+
+ /**
+ * Test data provider
+ */
+ public function transformDataProvider() {
+ yield array(
+ 'base64_decode',
+ array(
+ '' => '',
+ 'VGVzdENhc2U=' => 'TestCase',
+ 'VGVzdENhc2Ux' => 'TestCase1',
+ 'VGVzdENhc2UxMg==' => 'TestCase12',
+ ),
+ );
+
+ yield array(
+ 'base64_encode',
+ array(
+ '' => '',
+ 'TestCase' => 'VGVzdENhc2U=',
+ 'TestCase1' => 'VGVzdENhc2Ux',
+ 'TestCase12' => 'VGVzdENhc2UxMg==',
+ "Test\0Case" => 'VGVzdABDYXNl',
+ ),
+ );
+
+ yield array(
+ 'compress_whitespace',
+ array(
+ '' => '',
+ 'TestCase' => 'TestCase',
+ "Test\0Case" => "Test\0Case",
+ 'Test Case' => 'Test Case',
+ " Test \t Case " => ' Test Case ',
+ "This is a test case with a tab \t, vtab \x0b, newline \x0a, return \x0d, formfeed \f, and a NUL\0 in it with a CRLF at the end.\x0d\x0a" => "This is a test case with a tab , vtab , newline , return , formfeed , and a NUL\0 in it with a CRLF at the end. ",
+ ),
+ );
+
+ yield array(
+ 'hex_decode',
+ array(
+ '' => '',
+ '5465737443617365' => 'TestCase',
+ '546573740043617365' => "Test\0Case",
+ // todo: these are invalid hex strings that PHP handles differently than modsecurity's code
+ // '01234567890a0z01234567890a' => "\x01#Eg\x89\x0a#\x01#Eg\x89\x0a",
+ // '01234567890az' => "\x01#Eg\x89\x0a",
+ // '01234567890a0' => "\x01#Eg\x89\x0a", .
+ ),
+ );
+
+ yield array(
+ 'hex_encode',
+ array(
+ 'TestCase' => '5465737443617365',
+ "Test\0Case" => '546573740043617365',
+ ),
+ );
+
+ yield array(
+ 'html_entity_decode',
+ array(
+ '' => '',
+ 'TestCase' => 'TestCase',
+ "Test\0Case" => "Test\0Case",
+ // todo: PHP's html_entity_decode works differently than modsecurity, fails these tests
+ // " \0d"&<> " => "\0\0\x20\x20\0\x20\0\x64\"&<>\xa0",
+ // " \0d"&<> " => "\0\0\x20\x20\0\x20\0\x64\"&<>\xa0",
+ // "g;g;g0;g;a;\0a2;aa00;a0;
a;&foo;" => "g;g;g0;\x02g;a;\0a2;\x03aa00;\x01a0;\x0aa;&foo;",
+ // "ggg0ga\0a2aa00a0
a&foo" => "ggg0\x02ga\0a2\x03aa00\x01a0\x0aa&foo", .
+ ),
+ );
+
+ yield array(
+ 'length',
+ array(
+ '0123456789abcdef' => 16,
+ "0123456789\tabcdef" => 17,
+ "Test\0Case" => 9,
+ ),
+ );
+
+ yield array(
+ 'lowercase',
+ array(
+ '' => '',
+ 'testcase' => 'testcase',
+ "test\0case" => "test\0case",
+ 'TestCase' => 'testcase',
+ "Test\0Case" => "test\0case",
+ ),
+ );
+
+ yield array(
+ 'md5',
+ array(
+ '' => "\xd4\x1d\x8c\xd9\x8f\x00\xb2\x04\xe9\x80\x09\x98\xec\xf8\x42\x7e",
+ 'TestCase' => "\xc9\xab\xa2\xc3\xe6\x01\x26\x16\x9e\x80\xe9\xa2\x6b\xa2\x73\xc1",
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08" => "\xa6\xe7\xd3\xb4\x6f\xdf\xaf\x0b\xde\x2a\x1f\x83\x2a\x00\xd2\xde",
+ ),
+ );
+
+ yield array(
+ 'normalize_path',
+ array(
+ '' => '',
+ '/foo/bar/baz' => '/foo/bar/baz',
+ "/foo/bar\0/baz" => "/foo/bar\0/baz",
+ 'x' => 'x',
+ '.' => '',
+ './' => '',
+ './..' => '..',
+ './../' => '../',
+ '..' => '..',
+ '../' => '../',
+ '../.' => '..',
+ '.././' => '../',
+ '../..' => '../..',
+ '../../' => '../../',
+ '/dir/foo//bar' => '/dir/foo/bar',
+ 'dir/foo//bar/' => 'dir/foo/bar/',
+ 'dir/../foo' => 'foo',
+ 'dir/../../foo' => '../foo',
+ 'dir/./.././../../foo/bar' => '../../foo/bar',
+ 'dir/./.././../../foo/bar/.' => '../../foo/bar',
+ 'dir/./.././../../foo/bar/./' => '../../foo/bar/',
+ 'dir/./.././../../foo/bar/..' => '../../foo',
+ 'dir/./.././../../foo/bar/../' => '../../foo/',
+ 'dir/./.././../../foo/bar/' => '../../foo/bar/',
+ 'dir//.//..//.//..//..//foo//bar' => '../../foo/bar',
+ 'dir//.//..//.//..//..//foo//bar//' => '../../foo/bar/',
+ 'dir/subdir/subsubdir/subsubsubdir/../../..' => 'dir',
+ 'dir/./subdir/./subsubdir/./subsubsubdir/../../..' => 'dir',
+ 'dir/./subdir/../subsubdir/../subsubsubdir/..' => 'dir',
+ '/dir/./subdir/../subsubdir/../subsubsubdir/../' => '/dir/',
+ // todo: I have no idea how the input is supposed to turn into the output for this test:
+ // "/./.././../../../../../../../\0/../etc/./passwd" => '/etc/passwd', .
+ ),
+ );
+
+ yield array(
+ 'normalize_path_win',
+ array(
+ '' => '',
+ '\\foo\\bar\\baz' => '/foo/bar/baz',
+ "\\foo\\bar\0\\baz" => "/foo/bar\0/baz",
+ 'x' => 'x',
+ '.' => '',
+ '.\\' => '',
+ '.\\..' => '..',
+ '.\\..\\' => '../',
+ '..' => '..',
+ '..\\' => '../',
+ '..\\.' => '..',
+ '..\\.\\' => '../',
+ '..\\..' => '../..',
+ '..\\..\\' => '../../',
+ '\\dir\\foo\\\\bar' => '/dir/foo/bar',
+ 'dir\\foo\\\\bar\\' => 'dir/foo/bar/',
+ 'dir\\..\\foo' => 'foo',
+ 'dir\\..\\..\\foo' => '../foo',
+ 'dir\\.\\..\\.\\..\\..\\foo\\bar' => '../../foo/bar',
+ 'dir\\.\\..\\.\\..\\..\\foo\\bar\\.' => '../../foo/bar',
+ 'dir\\.\\..\\.\\..\\..\\foo\\bar\\.\\' => '../../foo/bar/',
+ 'dir\\.\\..\\.\\..\\..\\foo\\bar\\..' => '../../foo',
+ 'dir\\.\\..\\.\\..\\..\\foo\\bar\\../' => '../../foo/',
+ 'dir\\.\\..\\.\\..\\..\\foo\\bar\\' => '../../foo/bar/',
+ 'dir\\\\.\\\\..\\\\.\\\\..\\\\..\\\\foo\\\\bar' => '../../foo/bar',
+ 'dir\\\\.\\\\..\\\\.\\\\..\\\\..\\\\foo\\\\bar\\\\' => '../../foo/bar/',
+ 'dir\\subdir\\subsubdir\\subsubsubdir\\..\\..\\..' => 'dir',
+ 'dir\\.\\subdir\\.\\subsubdir\\.\\subsubsubdir\\..\\..\\..' => 'dir',
+ 'dir\\.\\subdir\\..\\subsubdir\\..\\subsubsubdir\\..' => 'dir',
+ '\\dir\\.\\subdir\\..\\subsubdir\\..\\subsubsubdir\\..\\' => '/dir/',
+ // todo: I have no idea how the input is supposed to turn into the output for this test:
+ // "\\.\\..\\.\\..\\..\\..\\..\\..\\..\\..\\\0\\..\\etc\\./passwd" => '/etc/passwd', .
+ ),
+ );
+
+ yield array(
+ 'remove_nulls',
+ array(
+ '' => '',
+ 'TestCase' => 'TestCase',
+ "Test\x01Case" => "Test\x01Case",
+ "\0TestCase" => 'TestCase',
+ "Test\0Case" => 'TestCase',
+ "Test\0\0Case" => 'TestCase',
+ "TestCase\0" => 'TestCase',
+ "\0Test\0Case\0" => 'TestCase',
+ ),
+ );
+
+ yield array(
+ 'remove_whitespace',
+ array(
+ '' => '',
+ 'TestCase' => 'TestCase',
+ "Test\0Case" => "Test\0Case",
+ " Test \t Case " => 'TestCase',
+ "This is a test case with a tab \t, vtab \x0b, newline \x0a, return \x0d, formfeed \f, and a NUL\0 in it with a CRLF at the end.\x0d\x0a" => "Thisisatestcasewithatab,vtab,newline,return,formfeed,andaNUL\0initwithaCRLFattheend.",
+ ),
+ );
+
+ yield array(
+ 'replace_comments',
+ array(
+ '' => '',
+ 'TestCase' => 'TestCase',
+ "Test\0Case" => "Test\0Case",
+ '/* TestCase */' => ' ',
+ '/*TestCase*/' => ' ',
+ '/* TestCase*/' => ' ',
+ '/*TestCase */' => ' ',
+ 'Before/* TestCase */After' => 'Before After',
+ 'Before /* TestCase */ After' => 'Before After',
+ "/* Test\nCase */" => ' ',
+ "/* Test\x0d\x0aCase */" => ' ',
+ "/* Test\x0aCase */" => ' ',
+ "/* Test\x0dCase */" => ' ',
+ "Before/* Test\x0d\x0aCase " => 'Before ',
+ "Before /* Test\x0aCase " => 'Before ',
+ "Before/* Test\x0d\x0aCase " => 'Before ',
+ "Before /* Test\x0aCase " => 'Before ',
+ "Test\x0d\x0aCase */After" => "Test\x0d\x0aCase */After",
+ "Test\x0aCase */ After" => "Test\x0aCase */ After",
+ "Test\x0d\x0aCase */After" => "Test\x0d\x0aCase */After",
+ "Test\x0aCase */ After" => "Test\x0aCase */ After",
+ ),
+ );
+
+ yield array(
+ 'replace_nulls',
+ array(
+ '' => '',
+ 'TestCase' => 'TestCase',
+ "\0TestCase" => ' TestCase',
+ "Test\0Case" => 'Test Case',
+ "Test\0\0Case" => 'Test Case',
+ "TestCase\0" => 'TestCase ',
+ "\0Test\0Case\0" => ' Test Case ',
+ ),
+ );
+
+ yield array(
+ 'sha1',
+ array(
+ '' => "\xda\x39\xa3\xee\x5e\x6b\x4b\x0d\x32\x55\xbf\xef\x95\x60\x18\x90\xaf\xd8\x07\x09",
+ 'TestCase' => "\xa7\x0c\xe3\x83\x89\xe3\x18\xbd\x2b\xe1\x8a\x01\x11\xc6\xdc\x76\xbd\x2c\xd9\xed",
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08" => "\x63\xbf\x60\xc7\x10\x5a\x07\xa2\xb1\x25\xbb\xf8\x9e\x61\xab\xda\xbc\x69\x78\xc2",
+ ),
+ );
+
+ yield array(
+ 'trim',
+ array(
+ '' => '',
+ 'TestCase' => 'TestCase',
+ "Test\0Case" => "Test\0Case",
+ ' TestCase' => 'TestCase',
+ 'TestCase ' => 'TestCase',
+ ' TestCase ' => 'TestCase',
+ ' Test Case ' => 'Test Case',
+ " Test \0 Case " => "Test \0 Case",
+ " Test \0 Case \r\n " => "Test \0 Case",
+ ),
+ );
+
+ yield array(
+ 'trim_left',
+ array(
+ '' => '',
+ 'TestCase' => 'TestCase',
+ "Test\0Case" => "Test\0Case",
+ 'TestCase ' => 'TestCase ',
+ ' TestCase' => 'TestCase',
+ ' TestCase ' => 'TestCase ',
+ ' Test Case ' => 'Test Case ',
+ " Test \0 Case " => "Test \0 Case ",
+ " Test \0 Case \r\n " => "Test \0 Case \r\n ",
+ ),
+ );
+
+ yield array(
+ 'trim_right',
+ array(
+ '' => '',
+ 'TestCase' => 'TestCase',
+ ' TestCase' => ' TestCase',
+ 'TestCase ' => 'TestCase',
+ ' TestCase ' => ' TestCase',
+ ' Test Case ' => ' Test Case',
+ " Test \0 Case " => " Test \0 Case",
+ " Test \0 Case \r\n " => " Test \0 Case",
+ ),
+ );
+
+ yield array(
+ 'url_decode',
+ array(
+ '' => '',
+ 'TestCase' => 'TestCase',
+ "Test\0Case" => "Test\0Case",
+ '+%00%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%20%21%22%23%24%25%26%27%28%29%2a%2b%2c%2d%2e%2f%30%31%32%33%34%35%36%37%38%39%3a%3b%3c%3d%3e%3f%40%41%42%43%44%45%46%47%48%49%4a%4b%4c%4d%4e%4f%50%51%52%53%54%55%56%57%58%59%5a%5b%5c%5d%5e%5f%60%61%62%63%64%65%66%67%68%69%6a%6b%6c%6d%6e%6f%70%71%72%73%74%75%76%77%78%79%7a%7b%7c%7d%7e%7f%80%81%82%83%84%85%86%87%88%89%8a%8b%8c%8d%8e%8f%90%91%92%93%94%95%96%97%98%99%9a%9b%9c%9d%9e%9f%a0%a1%a2%a3%a4%a5%a6%a7%a8%a9%aa%ab%ac%ad%ae%af%b0%b1%b2%b3%b4%b5%b6%b7%b8%b9%ba%bb%bc%bd%be%bf%c0%c1%c2%c3%c4%c5%c6%c7%c8%c9%ca%cb%cc%cd%ce%cf%d0%d1%d2%d3%d4%d5%d6%d7%d8%d9%da%db%dc%dd%de%df%e0%e1%e2%e3%e4%e5%e6%e7%e8%e9%ea%eb%ec%ed%ee%ef%f0%f1%f2%f3%f4%f5%f6%f7%f8%f9%fa%fb%fc%fd%fe%ff' => " \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f \x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
+ 'Test+Case' => 'Test Case',
+ '%+' => '% ',
+ '%%20' => '% ',
+ '%0g%20' => '%0g ',
+ '%0%20' => '%0 ',
+ '%g0%20' => '%g0 ',
+ '%g%20' => '%g ',
+ '%0%1%2%3%4%5%6%7%8%9%0%a%b%c%d%e%f' => '%0%1%2%3%4%5%6%7%8%9%0%a%b%c%d%e%f',
+ '%g0%g1%g2%g3%g4%g5%g6%g7%g8%g9%g0%ga%gb%gc%gd%ge%gf' => '%g0%g1%g2%g3%g4%g5%g6%g7%g8%g9%g0%ga%gb%gc%gd%ge%gf',
+ '%0g%1g%2g%3g%4g%5g%6g%7g%8g%9g%0g%ag%bg%cg%dg%eg%fg' => '%0g%1g%2g%3g%4g%5g%6g%7g%8g%9g%0g%ag%bg%cg%dg%eg%fg',
+ '%' => '%',
+ '%%' => '%%',
+ '%0g' => '%0g',
+ '%gg' => '%gg',
+ ),
+ );
+
+ yield array(
+ 'utf8_to_unicode',
+ array(
+ 'Ā ā Ă ă Ą ą Ć ć Ĉ ĉ Ċ ċ Č č Ď ď Đ đ Ē ē Ĕ ĕ Ė ė Ę ę Ě ě Ĝ ĝ Ğ ğ Ġ ġ Ģ ģ Ĥ ĥ Ħ ħ Ĩ ĩ Ī ī Ĭ ĭ Į į İ ı IJ ij Ĵ ĵ Ķ ķ ĸ Ĺ ĺ Ļ ļ Ľ ľ Ŀ ŀ Ł ł Ń ń Ņ ņ Ň ň ʼn Ŋ ŋ Ō ō Ŏ ŏ Ő ő Œ œ Ŕ ŕ Ŗ ŗ Ř ř Ś ś Ŝ ŝ Ş ş Š š Ţ ţ Ť ť Ŧ ŧ Ũ ũ Ū ū Ŭ ŭ Ů ů Ű ű Ų ų Ŵ ŵ Ŷ ŷ Ÿ Ź ź Ż ż Ž ž ſ' => '%u0100 %u0101 %u0102 %u0103 %u0104 %u0105 %u0106 %u0107 %u0108 %u0109 %u010a %u010b %u010c %u010d %u010e %u010f %u0110 %u0111 %u0112 %u0113 %u0114 %u0115 %u0116 %u0117 %u0118 %u0119 %u011a %u011b %u011c %u011d %u011e %u011f %u0120 %u0121 %u0122 %u0123 %u0124 %u0125 %u0126 %u0127 %u0128 %u0129 %u012a %u012b %u012c %u012d %u012e %u012f %u0130 %u0131 %u0132 %u0133 %u0134 %u0135 %u0136 %u0137 %u0138 %u0139 %u013a %u013b %u013c %u013d %u013e %u013f %u0140 %u0141 %u0142 %u0143 %u0144 %u0145 %u0146 %u0147 %u0148 %u0149 %u014a %u014b %u014c %u014d %u014e %u014f %u0150 %u0151 %u0152 %u0153 %u0154 %u0155 %u0156 %u0157 %u0158 %u0159 %u015a %u015b %u015c %u015d %u015e %u015f %u0160 %u0161 %u0162 %u0163 %u0164 %u0165 %u0166 %u0167 %u0168 %u0169 %u016a %u016b %u016c %u016d %u016e %u016f %u0170 %u0171 %u0172 %u0173 %u0174 %u0175 %u0176 %u0177 %u0178 %u0179 %u017a %u017b %u017c %u017d %u017e %u017f',
+ 'Ѐ Ё Ђ Ѓ Є Ѕ І Ї Ј Љ Њ Ћ Ќ Ѝ Ў Џ А Б В Г Д Е Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я а б в г д е ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я ѐ ё ђ ѓ є ѕ і ї ј љ њ ћ ќ ѝ ў џ Ѡ ѡ Ѣ ѣ Ѥ ѥ Ѧ ѧ Ѩ ѩ Ѫ ѫ Ѭ ѭ Ѯ ѯ Ѱ ѱ Ѳ ѳ Ѵ ѵ Ѷ ѷ Ѹ ѹ Ѻ ѻ Ѽ ѽ Ѿ ѿ Ҁ ҁ ҂ ҈ ҉ Ҋ ҋ Ҍ ҍ Ҏ ҏ Ґ ґ Ғ ғ Ҕ ҕ Җ җ Ҙ ҙ Қ қ Ҝ ҝ Ҟ ҟ Ҡ ҡ Ң ң Ҥ ҥ Ҧ ҧ Ҩ ҩ Ҫ ҫ Ҭ ҭ Ү ү Ұ ұ Ҳ ҳ Ҵ ҵ Ҷ ҷ Ҹ ҹ Һ һ Ҽ ҽ Ҿ ҿ Ӏ Ӂ ӂ Ӄ ӄ Ӆ ӆ Ӈ ӈ Ӊ ӊ Ӌ ӌ Ӎ ӎ ӏ Ӑ ӑ Ӓ ӓ Ӕ ӕ Ӗ ӗ Ә ә Ӛ ӛ Ӝ ӝ Ӟ ӟ Ӡ ӡ Ӣ ӣ Ӥ ӥ Ӧ ӧ Ө ө Ӫ ӫ Ӭ ӭ Ӯ ӯ Ӱ ӱ Ӳ ӳ Ӵ ӵ Ӷ ӷ Ӹ ӹ Ӻ ӻ Ӽ ӽ Ӿ ӿ ͵ ͺ ͻ ͼ ͽ ΄ ΅ Ά Έ Ή Ί Ό Ύ Ώ ΐ Α Β Γ Δ Ε Ζ Η Θ Ι Κ Λ Μ Ν Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω Ϊ Ϋ ά έ ή ί ΰ α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ ς σ τ υ φ χ ψ ω ϊ ϋ ό ύ ώ ϐ ϑ ϒ ϓ ϔ ϕ ϖ ϗ Ϙ ϙ Ϛ ϛ Ϝ ϝ Ϟ ϟ Ϡ ϡ Ϣ ϣ Ϥ ϥ Ϧ ϧ Ϩ ϩ Ϫ ϫ Ϭ ϭ Ϯ ϯ ϰ ϱ ϲ ϳ ϴ ϵ ϶ Ϸ ϸ Ϲ Ϻ ϻ ϼ Ͻ Ͼ Ͽ ־ ׀ ׃ ׆ א ב ג ד ה ו ז ח ט י ך כ ל ם מ ן נ ס ע ף פ ץ צ ק ר ש ת װ ױ ײ ׳ ״' => '%u0400 %u0401 %u0402 %u0403 %u0404 %u0405 %u0406 %u0407 %u0408 %u0409 %u040a %u040b %u040c %u040d %u040e %u040f %u0410 %u0411 %u0412 %u0413 %u0414 %u0415 %u0416 %u0417 %u0418 %u0419 %u041a %u041b %u041c %u041d %u041e %u041f %u0420 %u0421 %u0422 %u0423 %u0424 %u0425 %u0426 %u0427 %u0428 %u0429 %u042a %u042b %u042c %u042d %u042e %u042f %u0430 %u0431 %u0432 %u0433 %u0434 %u0435 %u0436 %u0437 %u0438 %u0439 %u043a %u043b %u043c %u043d %u043e %u043f %u0440 %u0441 %u0442 %u0443 %u0444 %u0445 %u0446 %u0447 %u0448 %u0449 %u044a %u044b %u044c %u044d %u044e %u044f %u0450 %u0451 %u0452 %u0453 %u0454 %u0455 %u0456 %u0457 %u0458 %u0459 %u045a %u045b %u045c %u045d %u045e %u045f %u0460 %u0461 %u0462 %u0463 %u0464 %u0465 %u0466 %u0467 %u0468 %u0469 %u046a %u046b %u046c %u046d %u046e %u046f %u0470 %u0471 %u0472 %u0473 %u0474 %u0475 %u0476 %u0477 %u0478 %u0479 %u047a %u047b %u047c %u047d %u047e %u047f %u0480 %u0481 %u0482 %u0488 %u0489 %u048a %u048b %u048c %u048d %u048e %u048f %u0490 %u0491 %u0492 %u0493 %u0494 %u0495 %u0496 %u0497 %u0498 %u0499 %u049a %u049b %u049c %u049d %u049e %u049f %u04a0 %u04a1 %u04a2 %u04a3 %u04a4 %u04a5 %u04a6 %u04a7 %u04a8 %u04a9 %u04aa %u04ab %u04ac %u04ad %u04ae %u04af %u04b0 %u04b1 %u04b2 %u04b3 %u04b4 %u04b5 %u04b6 %u04b7 %u04b8 %u04b9 %u04ba %u04bb %u04bc %u04bd %u04be %u04bf %u04c0 %u04c1 %u04c2 %u04c3 %u04c4 %u04c5 %u04c6 %u04c7 %u04c8 %u04c9 %u04ca %u04cb %u04cc %u04cd %u04ce %u04cf %u04d0 %u04d1 %u04d2 %u04d3 %u04d4 %u04d5 %u04d6 %u04d7 %u04d8 %u04d9 %u04da %u04db %u04dc %u04dd %u04de %u04df %u04e0 %u04e1 %u04e2 %u04e3 %u04e4 %u04e5 %u04e6 %u04e7 %u04e8 %u04e9 %u04ea %u04eb %u04ec %u04ed %u04ee %u04ef %u04f0 %u04f1 %u04f2 %u04f3 %u04f4 %u04f5 %u04f6 %u04f7 %u04f8 %u04f9 %u04fa %u04fb %u04fc %u04fd %u04fe %u04ff %u0375 %u037a %u037b %u037c %u037d %u0384 %u0385 %u0386 %u0388 %u0389 %u038a %u038c %u038e %u038f %u0390 %u0391 %u0392 %u0393 %u0394 %u0395 %u0396 %u0397 %u0398 %u0399 %u039a %u039b %u039c %u039d %u039e %u039f %u03a0 %u03a1 %u03a3 %u03a4 %u03a5 %u03a6 %u03a7 %u03a8 %u03a9 %u03aa %u03ab %u03ac %u03ad %u03ae %u03af %u03b0 %u03b1 %u03b2 %u03b3 %u03b4 %u03b5 %u03b6 %u03b7 %u03b8 %u03b9 %u03ba %u03bb %u03bc %u03bd %u03be %u03bf %u03c0 %u03c1 %u03c2 %u03c3 %u03c4 %u03c5 %u03c6 %u03c7 %u03c8 %u03c9 %u03ca %u03cb %u03cc %u03cd %u03ce %u03d0 %u03d1 %u03d2 %u03d3 %u03d4 %u03d5 %u03d6 %u03d7 %u03d8 %u03d9 %u03da %u03db %u03dc %u03dd %u03de %u03df %u03e0 %u03e1 %u03e2 %u03e3 %u03e4 %u03e5 %u03e6 %u03e7 %u03e8 %u03e9 %u03ea %u03eb %u03ec %u03ed %u03ee %u03ef %u03f0 %u03f1 %u03f2 %u03f3 %u03f4 %u03f5 %u03f6 %u03f7 %u03f8 %u03f9 %u03fa %u03fb %u03fc %u03fd %u03fe %u03ff %u05be %u05c0 %u05c3 %u05c6 %u05d0 %u05d1 %u05d2 %u05d3 %u05d4 %u05d5 %u05d6 %u05d7 %u05d8 %u05d9 %u05da %u05db %u05dc %u05dd %u05de %u05df %u05e0 %u05e1 %u05e2 %u05e3 %u05e4 %u05e5 %u05e6 %u05e7 %u05e8 %u05e9 %u05ea %u05f0 %u05f1 %u05f2 %u05f3 %u05f4',
+ 'ب ة ت ث ج ح خ د ذ ر ز س ش ص ض ط ظ ع غ ـ ف ق ك ل م ن ه و ى ي ٖ ٗ ٘ ٙ ٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩ ٪ ٫ ٬ ٭ ٮ ٯ ٱ ٲ ٳ ٴ ٵ ٶ ٷ ٸ ٹ ٺ ٻ ټ ٽ پ ٿ ڀ ځ ڂ ڃ ڄ څ چ ڇ ڈ ډ ڊ ڋ ڌ ڍ ڎ ڏ ڐ ڑ ڒ ړ ڔ ڕ ږ ڗ ژ ڙ ښ ڛ ڜ ڝ ڞ ڟ ڠ ڡ ڢ ڣ ڤ ڥ ڦ ڧ ڨ ک ڪ ګ ڬ ڭ ڮ گ ڰ ڱ ڲ ڳ ڴ ڵ ڶ ڷ ڸ ڹ ں ڻ ڼ ڽ ھ ڿ ۀ ہ ۂ ۃ ۄ ۅ ۆ ۇ ۈ ۉ ۊ ۋ ی ۍ ێ ۏ ې ۑ ے ۓ ۔ ە ۞ ۥ ۦ ۩ ۮ ۯ ۰ ۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ ۺ ۻ ۼ ۽ ۾ ' => '%u0628 %u0629 %u062a %u062b %u062c %u062d %u062e %u062f %u0630 %u0631 %u0632 %u0633 %u0634 %u0635 %u0636 %u0637 %u0638 %u0639 %u063a %u0640 %u0641 %u0642 %u0643 %u0644 %u0645 %u0646 %u0647 %u0648 %u0649 %u064a %u0656 %u0657 %u0658 %u0659 %u0660 %u0661 %u0662 %u0663 %u0664 %u0665 %u0666 %u0667 %u0668 %u0669 %u066a %u066b %u066c %u066d %u066e %u066f %u0671 %u0672 %u0673 %u0674 %u0675 %u0676 %u0677 %u0678 %u0679 %u067a %u067b %u067c %u067d %u067e %u067f %u0680 %u0681 %u0682 %u0683 %u0684 %u0685 %u0686 %u0687 %u0688 %u0689 %u068a %u068b %u068c %u068d %u068e %u068f %u0690 %u0691 %u0692 %u0693 %u0694 %u0695 %u0696 %u0697 %u0698 %u0699 %u069a %u069b %u069c %u069d %u069e %u069f %u06a0 %u06a1 %u06a2 %u06a3 %u06a4 %u06a5 %u06a6 %u06a7 %u06a8 %u06a9 %u06aa %u06ab %u06ac %u06ad %u06ae %u06af %u06b0 %u06b1 %u06b2 %u06b3 %u06b4 %u06b5 %u06b6 %u06b7 %u06b8 %u06b9 %u06ba %u06bb %u06bc %u06bd %u06be %u06bf %u06c0 %u06c1 %u06c2 %u06c3 %u06c4 %u06c5 %u06c6 %u06c7 %u06c8 %u06c9 %u06ca %u06cb %u06cc %u06cd %u06ce %u06cf %u06d0 %u06d1 %u06d2 %u06d3 %u06d4 %u06d5 %u06dd %u06de %u06e5 %u06e6 %u06e9 %u06ee %u06ef %u06f0 %u06f1 %u06f2 %u06f3 %u06f4 %u06f5 %u06f6 %u06f7 %u06f8 %u06f9 %u06fa %u06fb %u06fc %u06fd %u06fe ',
+ '■ □ ▢ ▣ ▤ ▥ ▦ ▧ ▨ ▩ ▪ ▫ ▬ ▭ ▮ ▯ ▰ ▱ ▲ △ ▴ ▵ ▶ ▷ ▸ ▹ ► ▻ ▼ ▽ ▾ ▿ ◀ ◁ ◂ ◃ ◄ ◅ ◆ ◇ ◈ ◉ ◊ ○ ◌ ◍ ◎ ● ◐ ◑ ◒ ◓ ◔ ◕ ◖ ◗ ◘ ◙ ◚ ◛ ◜ ◝ ◞ ◟ ◠ ◡ ◢ ◣ ◤ ◥ ◦ ◧ ◨ ◩ ◪ ◫ ◬ ◭ ◮ ◯ ◰ ◱ ◲ ◳ ◴ ◵ ◶ ◷ ◸ ◹ ◺ ◻ ◼ ◽ ◾ ◿' => '%u25a0 %u25a1 %u25a2 %u25a3 %u25a4 %u25a5 %u25a6 %u25a7 %u25a8 %u25a9 %u25aa %u25ab %u25ac %u25ad %u25ae %u25af %u25b0 %u25b1 %u25b2 %u25b3 %u25b4 %u25b5 %u25b6 %u25b7 %u25b8 %u25b9 %u25ba %u25bb %u25bc %u25bd %u25be %u25bf %u25c0 %u25c1 %u25c2 %u25c3 %u25c4 %u25c5 %u25c6 %u25c7 %u25c8 %u25c9 %u25ca %u25cb %u25cc %u25cd %u25ce %u25cf %u25d0 %u25d1 %u25d2 %u25d3 %u25d4 %u25d5 %u25d6 %u25d7 %u25d8 %u25d9 %u25da %u25db %u25dc %u25dd %u25de %u25df %u25e0 %u25e1 %u25e2 %u25e3 %u25e4 %u25e5 %u25e6 %u25e7 %u25e8 %u25e9 %u25ea %u25eb %u25ec %u25ed %u25ee %u25ef %u25f0 %u25f1 %u25f2 %u25f3 %u25f4 %u25f5 %u25f6 %u25f7 %u25f8 %u25f9 %u25fa %u25fb %u25fc %u25fd %u25fe %u25ff',
+ ' ✁ ✂ ✃ ✄ ✅ ✆ ✇ ✈ ✉ ✊ ✋ ✌ ✍ ✎ ✏ ✐ ✑ ✒ ✓ ✔ ✕ ✖ ✗ ✘ ✙ ✚ ✛ ✜ ✝ ✞ ✟ ✠ ✡ ✢ ✣ ✤ ✥ ✦ ✧ ✨ ✩ ✪ ✫ ✬ ✭ ✮ ✯ ✰ ✱ ✲ ✳ ✴ ✵ ✶ ✷ ✸ ✹ ✺ ✻ ✼ ✽ ✾ ✿ ❀ ❁ ❂ ❃ ❄ ❅ ❆ ❇ ❈ ❉ ❊ ❋ ❌ ❍ ❎ ❏ ❐ ❑ ❒ ❓ ❔ ❕ ❖ ❗ ❘ ❙ ❚ ❛ ❜ ❝ ❣ ❤ ❥ ❦ ❧ ❨ ❩ ❪ ❫ ❬ ❭ ❮ ❯ ❰ ❱ ❲ ❳ ❴ ❵ ❶ ❷ ❸ ❹ ❺ ❻ ❼ ❽ ❾ ❿ ➀ ➁ ➂ ➃ ➄ ➅ ➆ ➇ ➈ ➉ ➊ ➋ ➌ ➍ ➎ ➏ ➐ ➑ ➒ ➓ ➔ ➘ ➙ ➚ ➛ ➜ ➝ ➞ ➟ ➠ ➡ ➢ ➣ ➤ ➥ ➦ ➧ ➨ ➩ ➪ ➫ ➬ ➭ ➮ ➯ ➰ ➱ ➲ ➳ ➴ ➵ ➶ ➷ ➸ ➹ ➺ ➻ ➼ ➽ ➾ ➿' => ' %u2701 %u2702 %u2703 %u2704 %u2705 %u2706 %u2707 %u2708 %u2709 %u270a %u270b %u270c %u270d %u270e %u270f %u2710 %u2711 %u2712 %u2713 %u2714 %u2715 %u2716 %u2717 %u2718 %u2719 %u271a %u271b %u271c %u271d %u271e %u271f %u2720 %u2721 %u2722 %u2723 %u2724 %u2725 %u2726 %u2727 %u2728 %u2729 %u272a %u272b %u272c %u272d %u272e %u272f %u2730 %u2731 %u2732 %u2733 %u2734 %u2735 %u2736 %u2737 %u2738 %u2739 %u273a %u273b %u273c %u273d %u273e %u273f %u2740 %u2741 %u2742 %u2743 %u2744 %u2745 %u2746 %u2747 %u2748 %u2749 %u274a %u274b %u274c %u274d %u274e %u274f %u2750 %u2751 %u2752 %u2753 %u2754 %u2755 %u2756 %u2757 %u2758 %u2759 %u275a %u275b %u275c %u275d %u2763 %u2764 %u2765 %u2766 %u2767 %u2768 %u2769 %u276a %u276b %u276c %u276d %u276e %u276f %u2770 %u2771 %u2772 %u2773 %u2774 %u2775 %u2776 %u2777 %u2778 %u2779 %u277a %u277b %u277c %u277d %u277e %u277f %u2780 %u2781 %u2782 %u2783 %u2784 %u2785 %u2786 %u2787 %u2788 %u2789 %u278a %u278b %u278c %u278d %u278e %u278f %u2790 %u2791 %u2792 %u2793 %u2794 %u2798 %u2799 %u279a %u279b %u279c %u279d %u279e %u279f %u27a0 %u27a1 %u27a2 %u27a3 %u27a4 %u27a5 %u27a6 %u27a7 %u27a8 %u27a9 %u27aa %u27ab %u27ac %u27ad %u27ae %u27af %u27b0 %u27b1 %u27b2 %u27b3 %u27b4 %u27b5 %u27b6 %u27b7 %u27b8 %u27b9 %u27ba %u27bb %u27bc %u27bd %u27be %u27bf',
+ ' ⟀ ⟁ ⟂ ⟃ ⟄ ⟅ ⟆ ⟇ ⟈ ⟉ ⟊ ⟌ ⟐ ⟑ ⟒ ⟓ ⟔ ⟕ ⟖ ⟗ ⟘ ⟙ ⟚ ⟛ ⟜ ⟝ ⟞ ⟟ ⟠ ⟡ ⟢ ⟣ ⟤ ⟥ ⟦ ⟧ ⟨ ⟩ ⟪ ⟫ ⟬ ⟭ ⟮ ⟯' => ' %u27c0 %u27c1 %u27c2 %u27c3 %u27c4 %u27c5 %u27c6 %u27c7 %u27c8 %u27c9 %u27ca %u27cc %u27d0 %u27d1 %u27d2 %u27d3 %u27d4 %u27d5 %u27d6 %u27d7 %u27d8 %u27d9 %u27da %u27db %u27dc %u27dd %u27de %u27df %u27e0 %u27e1 %u27e2 %u27e3 %u27e4 %u27e5 %u27e6 %u27e7 %u27e8 %u27e9 %u27ea %u27eb %u27ec %u27ed %u27ee %u27ef',
+ ' ⟰ ⟱ ⟲ ⟳ ⟴ ⟵ ⟶ ⟷ ⟸ ⟹ ⟺ ⟻ ⟼ ⟽ ⟾ ⟿' => ' %u27f0 %u27f1 %u27f2 %u27f3 %u27f4 %u27f5 %u27f6 %u27f7 %u27f8 %u27f9 %u27fa %u27fb %u27fc %u27fd %u27fe %u27ff',
+ ),
+ );
+ }
+}
diff --git a/projects/plugins/backup/CHANGELOG.md b/projects/plugins/backup/CHANGELOG.md
index bfacd050ec085..5c04e6a47b115 100644
--- a/projects/plugins/backup/CHANGELOG.md
+++ b/projects/plugins/backup/CHANGELOG.md
@@ -5,7 +5,7 @@ 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).
-## 1.1.0-beta - 2022-01-13
+## 1.1.0 - 2022-01-26
### Added
- Added My Jetpack page work in progress behind a feature flag.
- Added pricing information to Jetpack Connection screen.
diff --git a/projects/js-packages/storybook/changelog/add-10.1-changelog b/projects/plugins/backup/changelog/2022-01-25-15-58-16-336664
similarity index 100%
rename from projects/js-packages/storybook/changelog/add-10.1-changelog
rename to projects/plugins/backup/changelog/2022-01-25-15-58-16-336664
diff --git a/projects/js-packages/storybook/changelog/add-changelogger-squash-command b/projects/plugins/backup/changelog/2022-01-27-11-15-38-111622
similarity index 100%
rename from projects/js-packages/storybook/changelog/add-changelogger-squash-command
rename to projects/plugins/backup/changelog/2022-01-27-11-15-38-111622
diff --git a/projects/js-packages/storybook/changelog/add-idc-errors b/projects/plugins/backup/changelog/2022-02-02-15-16-49-411998
similarity index 100%
rename from projects/js-packages/storybook/changelog/add-idc-errors
rename to projects/plugins/backup/changelog/2022-02-02-15-16-49-411998
diff --git a/projects/plugins/vaultpress/changelog/add-idc-customizate-labels b/projects/plugins/backup/changelog/add-anti-spam-my-jetpack
similarity index 100%
rename from projects/plugins/vaultpress/changelog/add-idc-customizate-labels
rename to projects/plugins/backup/changelog/add-anti-spam-my-jetpack
diff --git a/projects/js-packages/storybook/changelog/add-idc-non-admin b/projects/plugins/backup/changelog/add-dependency-analysis-in-cli
similarity index 100%
rename from projects/js-packages/storybook/changelog/add-idc-non-admin
rename to projects/plugins/backup/changelog/add-dependency-analysis-in-cli
diff --git a/projects/js-packages/storybook/changelog/add-jetpack-spinner-to-storybook b/projects/plugins/backup/changelog/add-disconnect-modal-click-events
similarity index 100%
rename from projects/js-packages/storybook/changelog/add-jetpack-spinner-to-storybook
rename to projects/plugins/backup/changelog/add-disconnect-modal-click-events
diff --git a/projects/js-packages/storybook/changelog/add-js-packages-exports b/projects/plugins/backup/changelog/add-idc-safe-mode-bar
similarity index 100%
rename from projects/js-packages/storybook/changelog/add-js-packages-exports
rename to projects/plugins/backup/changelog/add-idc-safe-mode-bar
diff --git a/projects/js-packages/storybook/changelog/add-my-jetpack-sections b/projects/plugins/backup/changelog/add-my-jetpack-plugin-installation
similarity index 100%
rename from projects/js-packages/storybook/changelog/add-my-jetpack-sections
rename to projects/plugins/backup/changelog/add-my-jetpack-plugin-installation
diff --git a/projects/js-packages/storybook/changelog/renovate-storybook-monorepo#6 b/projects/plugins/backup/changelog/add-my_jetpack_has_plan
similarity index 54%
rename from projects/js-packages/storybook/changelog/renovate-storybook-monorepo#6
rename to projects/plugins/backup/changelog/add-my_jetpack_has_plan
index 302aa8cf46b5c..650406ad22bff 100644
--- a/projects/js-packages/storybook/changelog/renovate-storybook-monorepo#6
+++ b/projects/plugins/backup/changelog/add-my_jetpack_has_plan
@@ -1,4 +1,4 @@
Significance: patch
Type: changed
-Updated package dependencies
+updated lock file
diff --git a/projects/plugins/vaultpress/changelog/add-idc-customizer-tags b/projects/plugins/backup/changelog/add-my_jetpack_has_plan#2
similarity index 100%
rename from projects/plugins/vaultpress/changelog/add-idc-customizer-tags
rename to projects/plugins/backup/changelog/add-my_jetpack_has_plan#2
diff --git a/projects/plugins/vaultpress/changelog/add-rna-connect-screen-required-plan b/projects/plugins/backup/changelog/add-plugins-install-mirror-repo
similarity index 100%
rename from projects/plugins/vaultpress/changelog/add-rna-connect-screen-required-plan
rename to projects/plugins/backup/changelog/add-plugins-install-mirror-repo
diff --git a/projects/js-packages/storybook/changelog/add-new-attach-license-method b/projects/plugins/backup/changelog/fix-connection-status-card-display_name
similarity index 100%
rename from projects/js-packages/storybook/changelog/add-new-attach-license-method
rename to projects/plugins/backup/changelog/fix-connection-status-card-display_name
diff --git a/projects/plugins/vaultpress/changelog/feature-sync-error-capture b/projects/plugins/backup/changelog/fix-my-jetpack-tests
similarity index 100%
rename from projects/plugins/vaultpress/changelog/feature-sync-error-capture
rename to projects/plugins/backup/changelog/fix-my-jetpack-tests
diff --git a/projects/js-packages/storybook/changelog/renovate-storybook-monorepo#4 b/projects/plugins/backup/changelog/renovate-wordpress-monorepo
similarity index 100%
rename from projects/js-packages/storybook/changelog/renovate-storybook-monorepo#4
rename to projects/plugins/backup/changelog/renovate-wordpress-monorepo
diff --git a/projects/js-packages/storybook/changelog/add-rna-connect-screen-required-plan#2 b/projects/plugins/backup/changelog/renovate-wordpress-monorepo#2
similarity index 100%
rename from projects/js-packages/storybook/changelog/add-rna-connect-screen-required-plan#2
rename to projects/plugins/backup/changelog/renovate-wordpress-monorepo#2
diff --git a/projects/js-packages/storybook/changelog/add-user-license-activate-notice-2 b/projects/plugins/backup/changelog/update-better-structure-for-i18n-loader
similarity index 100%
rename from projects/js-packages/storybook/changelog/add-user-license-activate-notice-2
rename to projects/plugins/backup/changelog/update-better-structure-for-i18n-loader
diff --git a/projects/plugins/backup/changelog/update-changelog-readme b/projects/plugins/backup/changelog/update-changelog-readme
new file mode 100644
index 0000000000000..74d631b06c790
--- /dev/null
+++ b/projects/plugins/backup/changelog/update-changelog-readme
@@ -0,0 +1,4 @@
+Significance: patch
+Type: changed
+
+Updated changelog and readme with 1.1.0 version identifier
diff --git a/projects/js-packages/storybook/changelog/add-user-license-activate-notice-2#2 b/projects/plugins/backup/changelog/update-move-i18n-loader-pkg-path-into-assets
similarity index 100%
rename from projects/js-packages/storybook/changelog/add-user-license-activate-notice-2#2
rename to projects/plugins/backup/changelog/update-move-i18n-loader-pkg-path-into-assets
diff --git a/projects/js-packages/storybook/changelog/backup-branch-1.1.0 b/projects/plugins/backup/changelog/update-my-jetpack-styles
similarity index 100%
rename from projects/js-packages/storybook/changelog/backup-branch-1.1.0
rename to projects/plugins/backup/changelog/update-my-jetpack-styles
diff --git a/projects/plugins/backup/composer.json b/projects/plugins/backup/composer.json
index 462cc8e0b7c17..d57f24e6fbecd 100644
--- a/projects/plugins/backup/composer.json
+++ b/projects/plugins/backup/composer.json
@@ -4,16 +4,16 @@
"type": "library",
"license": "GPL-2.0-or-later",
"require": {
- "automattic/jetpack-assets": "1.16.x-dev",
+ "automattic/jetpack-assets": "1.17.x-dev",
"automattic/jetpack-admin-ui": "0.2.x-dev",
"automattic/jetpack-autoloader": "2.10.x-dev",
"automattic/jetpack-backup": "1.2.x-dev",
- "automattic/jetpack-composer-plugin": "1.0.x-dev",
+ "automattic/jetpack-composer-plugin": "1.1.x-dev",
"automattic/jetpack-config": "1.6.x-dev",
"automattic/jetpack-connection": "1.36.x-dev",
"automattic/jetpack-connection-ui": "2.3.x-dev",
- "automattic/jetpack-identity-crisis": "0.6.x-dev",
- "automattic/jetpack-my-jetpack": "0.4.x-dev",
+ "automattic/jetpack-identity-crisis": "0.7.x-dev",
+ "automattic/jetpack-my-jetpack": "0.5.x-dev",
"automattic/jetpack-sync": "1.29.x-dev",
"automattic/jetpack-status": "1.10.x-dev"
},
diff --git a/projects/plugins/backup/composer.lock b/projects/plugins/backup/composer.lock
index 89904c6485ed5..12a44dfd7877b 100644
--- a/projects/plugins/backup/composer.lock
+++ b/projects/plugins/backup/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "f6c6a6654288c4971fe6b4c1cb93f238",
+ "content-hash": "c3a6891977d5f9e3a9b540f58682effe",
"packages": [
{
"name": "automattic/jetpack-a8c-mc-stats",
@@ -60,7 +60,7 @@
"dist": {
"type": "path",
"url": "../../packages/admin-ui",
- "reference": "e9c471e1ac68c4dd7cf656fd17a4b96fdc4c6ff1"
+ "reference": "bbb92b9b9852760e84b7aaee877ee081616efcb3"
},
"require-dev": {
"automattic/jetpack-changelogger": "^3.0",
@@ -71,6 +71,7 @@
"extra": {
"autotagger": true,
"mirror-repo": "Automattic/jetpack-admin-ui",
+ "textdomain": "jetpack-admin-ui",
"changelogger": {
"link-template": "https://github.com/Automattic/jetpack-admin-ui/compare/${old}...${new}"
},
@@ -115,7 +116,7 @@
"dist": {
"type": "path",
"url": "../../packages/assets",
- "reference": "558a4eabba5541257e2b496aeba6da3a06e169f5"
+ "reference": "810134d18a0baedd94aae5398f2f506f213bac7b"
},
"require": {
"automattic/jetpack-constants": "^1.6"
@@ -135,7 +136,7 @@
"link-template": "https://github.com/Automattic/jetpack-assets/compare/v${old}...v${new}"
},
"branch-alias": {
- "dev-master": "1.16.x-dev"
+ "dev-master": "1.17.x-dev"
}
},
"autoload": {
@@ -147,11 +148,21 @@
]
},
"scripts": {
+ "build-development": [
+ "pnpm run build"
+ ],
+ "build-production": [
+ "pnpm run build-production"
+ ],
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
],
"test-coverage": [
- "php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
+ "php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/php/clover.xml\"",
+ "pnpm run test-coverage"
+ ],
+ "test-js": [
+ "pnpm run test"
],
"test-php": [
"@composer phpunit"
@@ -291,7 +302,7 @@
"dist": {
"type": "path",
"url": "../../packages/composer-plugin",
- "reference": "d9a79b97dbf363bdffb2f748e0dbed8216753c36"
+ "reference": "c3fc0e4cf179c619f896174a6c9db8e70cff74c3"
},
"require": {
"composer-plugin-api": "^2.1.0"
@@ -310,7 +321,7 @@
},
"autotagger": true,
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.1.x-dev"
}
},
"autoload": {
@@ -450,14 +461,14 @@
"dist": {
"type": "path",
"url": "../../packages/connection-ui",
- "reference": "73a9cff560f04b981ca97d26225cc8fbb91c0be5"
+ "reference": "af0b68a96950278824fbd56aad4fc5f5a2af4240"
},
"require": {
- "automattic/jetpack-assets": "^1.16",
+ "automattic/jetpack-assets": "^1.17",
"automattic/jetpack-connection": "^1.36",
"automattic/jetpack-constants": "^1.6",
"automattic/jetpack-device-detection": "^1.4",
- "automattic/jetpack-identity-crisis": "^0.6"
+ "automattic/jetpack-identity-crisis": "^0.7"
},
"require-dev": {
"automattic/jetpack-changelogger": "^3.0"
@@ -644,10 +655,10 @@
"dist": {
"type": "path",
"url": "../../packages/identity-crisis",
- "reference": "daea57d4601da806be82d3265429cb9c2a304c00"
+ "reference": "80cc6904f5337f7cfa194199ad307e054fece278"
},
"require": {
- "automattic/jetpack-assets": "^1.16",
+ "automattic/jetpack-assets": "^1.17",
"automattic/jetpack-connection": "^1.36",
"automattic/jetpack-constants": "^1.6",
"automattic/jetpack-logo": "^1.5",
@@ -672,7 +683,7 @@
"link-template": "https://github.com/Automattic/jetpack-identity-crisis/compare/v${old}...v${new}"
},
"branch-alias": {
- "dev-master": "0.6.x-dev"
+ "dev-master": "0.7.x-dev"
}
},
"autoload": {
@@ -767,17 +778,20 @@
"dist": {
"type": "path",
"url": "../../packages/my-jetpack",
- "reference": "a5d7c6f0f3652d649cac7b11dfe35db034634fb7"
+ "reference": "0a4354d247242a1fbc56373c2ddd4ccacab62d52"
},
"require": {
"automattic/jetpack-admin-ui": "^0.2",
- "automattic/jetpack-assets": "^1.16",
+ "automattic/jetpack-assets": "^1.17",
"automattic/jetpack-connection": "^1.36",
+ "automattic/jetpack-plugins-installer": "^0.1",
"automattic/jetpack-terms-of-service": "^1.9",
"automattic/jetpack-tracking": "^1.14"
},
"require-dev": {
"automattic/jetpack-changelogger": "^3.0",
+ "automattic/jetpack-options": "^1.14",
+ "automattic/jetpack-search": "^0.7",
"automattic/wordbless": "@dev",
"yoast/phpunit-polyfills": "1.0.3"
},
@@ -790,7 +804,7 @@
"link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}"
},
"branch-alias": {
- "dev-master": "0.4.x-dev"
+ "dev-master": "0.5.x-dev"
}
},
"autoload": {
@@ -933,6 +947,58 @@
"relative": true
}
},
+ {
+ "name": "automattic/jetpack-plugins-installer",
+ "version": "dev-master",
+ "dist": {
+ "type": "path",
+ "url": "../../packages/plugins-installer",
+ "reference": "19c930344c337c188d411a51938c7c870ea511a1"
+ },
+ "require": {
+ "automattic/jetpack-a8c-mc-stats": "^1.4"
+ },
+ "require-dev": {
+ "automattic/jetpack-changelogger": "^3.0",
+ "yoast/phpunit-polyfills": "1.0.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.1.x-dev"
+ },
+ "mirror-repo": "Automattic/jetpack-plugins-installer",
+ "changelogger": {
+ "link-template": "https://github.com/Automattic/jetpack-plugins-installer/compare/v${old}...v${new}"
+ },
+ "autotagger": true,
+ "textdomain": "jetpack-plugins-installer"
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "scripts": {
+ "phpunit": [
+ "./vendor/phpunit/phpunit/phpunit --colors=always"
+ ],
+ "test-coverage": [
+ "php -dpcov.directory=. ./vendor/bin/phpunit --coverage-clover \"$COVERAGE_DIR/clover.xml\""
+ ],
+ "test-php": [
+ "@composer phpunit"
+ ]
+ },
+ "license": [
+ "GPL-2.0-or-later"
+ ],
+ "description": "Handle installation of plugins from WP.org",
+ "transport-options": {
+ "monorepo": true,
+ "relative": true
+ }
+ },
{
"name": "automattic/jetpack-redirect",
"version": "dev-master",
@@ -1092,13 +1158,13 @@
"dist": {
"type": "path",
"url": "../../packages/sync",
- "reference": "cad6e550e4c76cc17cab3ddef0829b67e2c04838"
+ "reference": "08c63a81a1ac29072c140383f3bf9266a4c3c6be"
},
"require": {
"automattic/jetpack-connection": "^1.36",
"automattic/jetpack-constants": "^1.6",
"automattic/jetpack-heartbeat": "^1.4",
- "automattic/jetpack-identity-crisis": "^0.6",
+ "automattic/jetpack-identity-crisis": "^0.7",
"automattic/jetpack-options": "^1.14",
"automattic/jetpack-password-checker": "^0.2",
"automattic/jetpack-roles": "^1.4",
@@ -1211,10 +1277,10 @@
"dist": {
"type": "path",
"url": "../../packages/tracking",
- "reference": "ed6f897aaaa8e843271556d271e6c7a829cb8727"
+ "reference": "60dd2a4462221bcc460aadf9f86daeb308fd8027"
},
"require": {
- "automattic/jetpack-assets": "^1.16",
+ "automattic/jetpack-assets": "^1.17",
"automattic/jetpack-options": "^1.14",
"automattic/jetpack-status": "^1.10",
"automattic/jetpack-terms-of-service": "^1.9"
@@ -1423,9 +1489,6 @@
"require": {
"php": "^7.1 || ^8.0"
},
- "replace": {
- "myclabs/deep-copy": "self.version"
- },
"require-dev": {
"doctrine/collections": "^1.0",
"doctrine/common": "^2.6",
diff --git a/projects/plugins/backup/package.json b/projects/plugins/backup/package.json
index 5c9a0d3570a08..2c22996fe254b 100644
--- a/projects/plugins/backup/package.json
+++ b/projects/plugins/backup/package.json
@@ -24,20 +24,20 @@
"extends @wordpress/browserslist-config"
],
"dependencies": {
- "@automattic/jetpack-api": "workspace:^0.8.3-alpha",
- "@automattic/jetpack-components": "workspace:^0.10.3-alpha",
- "@automattic/jetpack-connection": "workspace:^0.14.0-alpha",
- "@wordpress/api-fetch": "5.2.6",
- "@wordpress/data": "6.1.5",
- "@wordpress/element": "4.0.4",
- "@wordpress/date": "4.2.3",
- "@wordpress/i18n": "4.2.4",
+ "@automattic/jetpack-api": "workspace:^0.8.4-alpha",
+ "@automattic/jetpack-components": "workspace:^0.10.5-alpha",
+ "@automattic/jetpack-connection": "workspace:^0.15.1-alpha",
+ "@wordpress/api-fetch": "6.0.0",
+ "@wordpress/data": "6.2.0",
+ "@wordpress/element": "4.1.0",
+ "@wordpress/date": "4.3.0",
+ "@wordpress/i18n": "4.3.0",
"react": "17.0.2",
"react-dom": "17.0.2"
},
"devDependencies": {
- "@automattic/jetpack-base-styles": "workspace:^0.1.7-alpha",
- "@automattic/jetpack-webpack-config": "workspace:^1.0.3-alpha",
+ "@automattic/jetpack-base-styles": "workspace:^0.1.8-alpha",
+ "@automattic/jetpack-webpack-config": "workspace:^1.1.2-alpha",
"@babel/core": "7.16.0",
"@babel/preset-env": "7.16.4",
"@babel/register": "7.16.0",
diff --git a/projects/plugins/backup/readme.txt b/projects/plugins/backup/readme.txt
index 28e2b6b77d369..786c204352019 100644
--- a/projects/plugins/backup/readme.txt
+++ b/projects/plugins/backup/readme.txt
@@ -4,7 +4,7 @@ Tags: jetpack
Requires at least: 5.8
Requires PHP: 5.6
Tested up to: 5.9
-Stable tag: 1.0.1
+Stable tag: 1.1.0
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
@@ -158,7 +158,7 @@ No, Jetpack Backup does not currently support split site or split home URLs.
2. Your site backups are stored in multiple locations on our world-class cloud infrastructure so you can recover them at any moment.
== Changelog ==
-### 1.1.0-beta - 2022-01-13
+### 1.1.0 - 2022-01-26
#### Added
- Added My Jetpack page work in progress behind a feature flag.
- Added pricing information to Jetpack Connection screen.
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/beta/changelog/renovate-major-eslint-packages b/projects/plugins/beta/changelog/renovate-major-eslint-packages
new file mode 100644
index 0000000000000..f8a4b188c86c1
--- /dev/null
+++ b/projects/plugins/beta/changelog/renovate-major-eslint-packages
@@ -0,0 +1,4 @@
+Significance: patch
+Type: added
+
+Added docs to JS file.
diff --git a/projects/plugins/beta/src/admin/updates.js b/projects/plugins/beta/src/admin/updates.js
index badcf5e315c2c..6ac54c55e5c62 100644
--- a/projects/plugins/beta/src/admin/updates.js
+++ b/projects/plugins/beta/src/admin/updates.js
@@ -1,4 +1,6 @@
/**
+ * Update message hooks.
+ *
* @param {jQuery} $ - jQuery object.
* @param {object} wp - WP object.
* @param {object} i18n - I18n data.
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/api/api.ts b/projects/plugins/boost/app/assets/src/js/api/api.ts
index bc6831bcecfb8..51d37883c4cd9 100644
--- a/projects/plugins/boost/app/assets/src/js/api/api.ts
+++ b/projects/plugins/boost/app/assets/src/js/api/api.ts
@@ -15,6 +15,7 @@ import { ApiError } from './api-error';
import type { JSONObject } from '../utils/json-types';
function getEndpointUrl( path: string ): string {
+ // eslint-disable-next-line camelcase
return wpApiSettings.root + Jetpack_Boost.api.namespace + Jetpack_Boost.api.prefix + path;
}
diff --git a/projects/plugins/boost/app/assets/src/js/api/speed-scores.ts b/projects/plugins/boost/app/assets/src/js/api/speed-scores.ts
index 4d4cf27bd128a..014133d6eba8e 100644
--- a/projects/plugins/boost/app/assets/src/js/api/speed-scores.ts
+++ b/projects/plugins/boost/app/assets/src/js/api/speed-scores.ts
@@ -43,6 +43,7 @@ export async function requestSpeedScores( force = false ): Promise< SpeedScoresS
// Request metrics
const response = parseResponse(
await api.post( force ? '/speed-scores/refresh' : '/speed-scores', {
+ // eslint-disable-next-line camelcase
url: Jetpack_Boost.site.url,
} )
);
@@ -121,6 +122,7 @@ async function pollRequest(): Promise< SpeedScoresSet > {
timeoutError: __( 'Timed out while waiting for speed-score.', 'jetpack-boost' ),
callback: async resolve => {
const response = parseResponse(
+ // eslint-disable-next-line camelcase
await api.post( '/speed-scores', { url: Jetpack_Boost.site.url } )
);
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/pages/settings/elements/RatingCard.svelte b/projects/plugins/boost/app/assets/src/js/pages/settings/elements/RatingCard.svelte
index cbca5742929f0..9371e4dd16fb7 100644
--- a/projects/plugins/boost/app/assets/src/js/pages/settings/elements/RatingCard.svelte
+++ b/projects/plugins/boost/app/assets/src/js/pages/settings/elements/RatingCard.svelte
@@ -24,6 +24,7 @@
await makeAdminAjaxRequest( {
action: 'set_show_rating_prompt',
value: false,
+ // eslint-disable-next-line camelcase
nonce: Jetpack_Boost.showRatingPromptNonce,
} );
diff --git a/projects/plugins/boost/app/assets/src/js/pages/settings/sections/Score.svelte b/projects/plugins/boost/app/assets/src/js/pages/settings/sections/Score.svelte
index 248947810c484..b2f62e26d96c1 100644
--- a/projects/plugins/boost/app/assets/src/js/pages/settings/sections/Score.svelte
+++ b/projects/plugins/boost/app/assets/src/js/pages/settings/sections/Score.svelte
@@ -25,6 +25,7 @@
*/
import { __ } from '@wordpress/i18n';
+ // eslint-disable-next-line camelcase
const siteIsOnline = Jetpack_Boost.site.online;
let loadError;
@@ -120,6 +121,7 @@
}
}, 2000 );
+ // eslint-disable-next-line camelcase
const respawnRatingPrompt = writable( Jetpack_Boost.preferences.showRatingPrompt );
const showRatingCard = derived(
diff --git a/projects/plugins/boost/app/assets/src/js/stores/connection.ts b/projects/plugins/boost/app/assets/src/js/stores/connection.ts
index f0849625e2c4d..c8ddcf54ca75b 100644
--- a/projects/plugins/boost/app/assets/src/js/stores/connection.ts
+++ b/projects/plugins/boost/app/assets/src/js/stores/connection.ts
@@ -14,6 +14,7 @@ export type ConnectionStatus = {
error: null | string;
};
+// eslint-disable-next-line camelcase
const initialState = Jetpack_Boost.connection;
const { subscribe, update } = writable< ConnectionStatus >( initialState );
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..ee0f67931040f 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 = [
@@ -20,6 +20,7 @@ const importantProviders = [
'singular_post',
];
+// eslint-disable-next-line camelcase
const initialState = Jetpack_Boost.criticalCssDismissedRecommendations || [];
const dismissed = writable< string[] >( initialState );
@@ -118,10 +119,10 @@ 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,
+ // eslint-disable-next-line camelcase
+ nonce: Jetpack_Boost.nonces[ 'recommendations/dismiss' ],
} );
dismissed.update( keys => [ ...keys, key ] );
}
@@ -130,9 +131,9 @@ 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', {
+ // eslint-disable-next-line camelcase
+ nonce: Jetpack_Boost.nonces[ 'recommendations/reset' ],
} );
dismissed.set( [] );
}
diff --git a/projects/plugins/boost/app/assets/src/js/stores/critical-css-status.ts b/projects/plugins/boost/app/assets/src/js/stores/critical-css-status.ts
index 7f9a7d82e8f7d..1254c3f1d107a 100644
--- a/projects/plugins/boost/app/assets/src/js/stores/critical-css-status.ts
+++ b/projects/plugins/boost/app/assets/src/js/stores/critical-css-status.ts
@@ -47,6 +47,7 @@ export interface CriticalCssStatus {
const success = 'success';
const fail = 'fail';
+// eslint-disable-next-line camelcase
const initialState = Jetpack_Boost.criticalCssStatus || {
generating: false,
progress: 0,
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/analytics.ts b/projects/plugins/boost/app/assets/src/js/utils/analytics.ts
index 73da7fe910d27..fa35b4d6f29e9 100644
--- a/projects/plugins/boost/app/assets/src/js/utils/analytics.ts
+++ b/projects/plugins/boost/app/assets/src/js/utils/analytics.ts
@@ -5,6 +5,7 @@ export function recordBoostEvent(
): void {
// eslint-disable-next-line camelcase
if ( ! ( 'boost_version' in eventProp ) && 'version' in Jetpack_Boost ) {
+ // eslint-disable-next-line camelcase
eventProp.boost_version = Jetpack_Boost.version;
}
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/assets/src/js/utils/load-critical-css-library.ts b/projects/plugins/boost/app/assets/src/js/utils/load-critical-css-library.ts
index 366c27df0f3dc..c0f7b43742607 100644
--- a/projects/plugins/boost/app/assets/src/js/utils/load-critical-css-library.ts
+++ b/projects/plugins/boost/app/assets/src/js/utils/load-critical-css-library.ts
@@ -15,6 +15,7 @@ export async function loadCriticalCssLibrary(): Promise< void > {
loadLibraryPromise = new Promise< void >( ( resolve, reject ) => {
const scriptUrl =
+ // eslint-disable-next-line camelcase
Jetpack_Boost.site.assetPath + '/critical-css-gen.js?ver=' + Jetpack_Boost.version;
const scriptTag = document.createElement( 'script' );
scriptTag.src = scriptUrl;
diff --git a/projects/plugins/boost/app/assets/src/js/utils/remove-admin-notices.ts b/projects/plugins/boost/app/assets/src/js/utils/remove-admin-notices.ts
index df7502bb5ce5d..7338403ee25d1 100644
--- a/projects/plugins/boost/app/assets/src/js/utils/remove-admin-notices.ts
+++ b/projects/plugins/boost/app/assets/src/js/utils/remove-admin-notices.ts
@@ -4,6 +4,7 @@
* @param {string} moduleSlug Module Slug
*/
export function removeShownAdminNotices( moduleSlug: string ): void {
+ // eslint-disable-next-line camelcase
for ( const adminNoticeId of Jetpack_Boost.shownAdminNoticeIds ) {
if ( adminNoticeId.includes( moduleSlug ) ) {
const notice = document.getElementById( adminNoticeId );
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 = '' . $html . ' ';
+
+ // 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