From 483e87b5def417d2cf5844b43bf7c51f028a7717 Mon Sep 17 00:00:00 2001 From: Kim Brown <50059399+kbrown9@users.noreply.github.com> Date: Wed, 17 Feb 2021 11:09:57 -0500 Subject: [PATCH 01/34] JITM: move jetpack-jitm.js to the JITM package (#18341) * JITM: move js file to the package - Move the jetpack-jitm.js file from the Jetpack plugin to the JITM package. - Place the css and js files in src/css and src/js directories. This matches the file structure of the existing lazy-images package. - Update the build files to generate the minified jetpack-jitm.js file during the build process. --- .eslintignore | 1 + package.json | 4 +-- projects/packages/jitm/.gitattributes | 16 ++++++--- projects/packages/jitm/.gitignore | 5 +++ projects/packages/jitm/composer.json | 8 +++++ projects/packages/jitm/package.json | 33 +++++++++++++++++++ projects/packages/jitm/src/class-jitm.php | 6 ++-- .../css}/jetpack-admin-jitm-rtl.css | 0 .../css}/jetpack-admin-jitm-rtl.min.css | 0 .../css}/jetpack-admin-jitm.css | 0 .../css}/jetpack-admin-jitm.min.css | 0 .../css}/jetpack-admin-jitm.scss | 0 .../jitm/src/js}/jetpack-jitm.js | 0 projects/packages/jitm/webpack.config.js | 16 +++++++++ projects/plugins/jetpack/.gitattributes | 2 +- projects/plugins/jetpack/composer.lock | 10 +++++- .../plugins/jetpack/tools/builder/sass.js | 2 +- 17 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 projects/packages/jitm/.gitignore create mode 100644 projects/packages/jitm/package.json rename projects/packages/jitm/{assets => src/css}/jetpack-admin-jitm-rtl.css (100%) rename projects/packages/jitm/{assets => src/css}/jetpack-admin-jitm-rtl.min.css (100%) rename projects/packages/jitm/{assets => src/css}/jetpack-admin-jitm.css (100%) rename projects/packages/jitm/{assets => src/css}/jetpack-admin-jitm.min.css (100%) rename projects/packages/jitm/{assets => src/css}/jetpack-admin-jitm.scss (100%) rename projects/{plugins/jetpack/_inc => packages/jitm/src/js}/jetpack-jitm.js (100%) create mode 100644 projects/packages/jitm/webpack.config.js diff --git a/.eslintignore b/.eslintignore index 93649d56ecb5e..8151553db3da8 100644 --- a/.eslintignore +++ b/.eslintignore @@ -30,6 +30,7 @@ projects/plugins/jetpack/vendor/ projects/plugins/jetpack/extensions/**/test/ ### Packages +projects/packages/jitm/src/js/jetpack-jitm.js projects/packages/lazy-images/src/js/IntersectionObserver-polyfill.js projects/packages/*/vendor projects/packages/*/wordpress diff --git a/package.json b/package.json index b7a892686180d..a18775cda6d24 100644 --- a/package.json +++ b/package.json @@ -17,13 +17,13 @@ "build-concurrently": "yarn install-if-deps-outdated && yarn clean && yarn concurrently 'yarn build-php' 'yarn build-packages' 'yarn build-jetpack-concurrently'", "build-jetpack": "cd projects/plugins/jetpack && yarn build", "build-jetpack-concurrently": "cd projects/plugins/jetpack && yarn build-concurrently", - "build-packages": "(cd projects/packages/lazy-images && yarn build) && (cd projects/packages/connection-ui && yarn build)", + "build-packages": "(cd projects/packages/lazy-images && yarn build) && (cd projects/packages/connection-ui && yarn build) && (cd projects/packages/jitm && yarn build)", "build-php": "composer install --ignore-platform-reqs", "build-production": "yarn distclean && yarn install --production=false && yarn build-production-php && yarn build-production-packages && yarn build-production-jetpack", "build-production-concurrently": "yarn distclean && yarn install --production=false && yarn concurrently 'yarn build-production-php' 'yarn build-production-packages' 'yarn build-production-jetpack-concurrently'", "build-production-jetpack": "cd projects/plugins/jetpack && yarn build-production", "build-production-jetpack-concurrently": "cd projects/plugins/jetpack && yarn build-production-concurrently", - "build-production-packages": "(cd projects/packages/lazy-images && yarn build-production) && yarn validate-es5 -- ./projects/packages/tracking/", + "build-production-packages": "(cd projects/packages/lazy-images && yarn build-production) && yarn validate-es5 -- ./projects/packages/tracking/ && (cd projects/packages/jitm && yarn build-production)", "build-production-php": "COMPOSER_MIRROR_PATH_REPOS=1 composer install -o --no-dev --classmap-authoritative --prefer-dist", "clean": "yarn clean-jetpack && yarn clean-composer", "clean-composer": "rm -rf vendor/", diff --git a/projects/packages/jitm/.gitattributes b/projects/packages/jitm/.gitattributes index f6c45a494026f..ad2ea09e6ce91 100644 --- a/projects/packages/jitm/.gitattributes +++ b/projects/packages/jitm/.gitattributes @@ -1,5 +1,13 @@ # 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 +package.json export-ignore +webpack.config.js export-ignore + +# Files to include in the mirror repo +/src/js/jetpack-jitm.min.js production-include + +# Files to exclude from the mirror repo +.gitignore production-exclude +phpunit.xml.dist production-exclude +tests/** production-exclude diff --git a/projects/packages/jitm/.gitignore b/projects/packages/jitm/.gitignore new file mode 100644 index 0000000000000..90a0a690d6eb9 --- /dev/null +++ b/projects/packages/jitm/.gitignore @@ -0,0 +1,5 @@ +node_modules +vendor +composer.lock +src/js/*.min.js +yarn.lock diff --git a/projects/packages/jitm/composer.json b/projects/packages/jitm/composer.json index b7ab02c6fe71b..69c7c7d9709b8 100644 --- a/projects/packages/jitm/composer.json +++ b/projects/packages/jitm/composer.json @@ -33,6 +33,14 @@ } ], "scripts": { + "build-production": [ + "Composer\\Config::disableProcessTimeout", + "yarn build-production" + ], + "build-development": [ + "Composer\\Config::disableProcessTimeout", + "yarn build" + ], "phpunit": [ "@composer install", "./vendor/phpunit/phpunit/phpunit --colors=always" diff --git a/projects/packages/jitm/package.json b/projects/packages/jitm/package.json new file mode 100644 index 0000000000000..93a9b270df97e --- /dev/null +++ b/projects/packages/jitm/package.json @@ -0,0 +1,33 @@ +{ + "private": true, + "description": "Display Just In Time Messages (JITMs) on admin pages.", + "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": "yarn install-if-deps-outdated && yarn clean && yarn build-js", + "build-js": "webpack --config ./webpack.config.js", + "build-production": "yarn distclean && yarn install --production=false && yarn build-production-js", + "build-production-js": "NODE_ENV=production BABEL_ENV=production yarn build-js && yarn validate-es5 ./src/", + "clean": "true", + "distclean": "rm -rf node_modules && yarn clean", + "install-if-deps-outdated": "yarn install --check-files --production=false --frozen-lockfile", + "validate-es5": "eslint --parser-options=ecmaVersion:5 --no-eslintrc --no-ignore" + }, + "devDependencies": { + "eslint": "7.18.0", + "webpack": "4.46.0", + "webpack-cli": "4.5.0" + }, + "engines": { + "node": "^12.20.1", + "yarn": "^1.3.2" + } +} diff --git a/projects/packages/jitm/src/class-jitm.php b/projects/packages/jitm/src/class-jitm.php index a3d8c3cc472c8..cb2a4c3a9ac20 100644 --- a/projects/packages/jitm/src/class-jitm.php +++ b/projects/packages/jitm/src/class-jitm.php @@ -19,7 +19,7 @@ */ class JITM { - const PACKAGE_VERSION = '1.7.3'; // TODO: Keep in sync with version specified in composer.json. + const PACKAGE_VERSION = '1.13.6'; // TODO: Keep in sync with version specified in composer.json. /** * The configuration method that is called from the jetpack-config package. @@ -104,7 +104,7 @@ public function jitm_enqueue_files() { $min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; wp_register_style( 'jetpack-jitm-css', - plugins_url( "assets/jetpack-admin-jitm{$min}.css", __DIR__ ), + plugins_url( "css/jetpack-admin-jitm{$min}.css", __FILE__ ), false, self::PACKAGE_VERSION . '-201243242' @@ -115,7 +115,7 @@ public function jitm_enqueue_files() { wp_enqueue_script( 'jetpack-jitm-new', - Assets::get_file_url_for_environment( '_inc/build/jetpack-jitm.min.js', '_inc/jetpack-jitm.js' ), + Assets::get_file_url_for_environment( 'js/jetpack-jitm.min.js', 'js/jetpack-jitm.js', __FILE__ ), array( 'jquery' ), self::PACKAGE_VERSION, true diff --git a/projects/packages/jitm/assets/jetpack-admin-jitm-rtl.css b/projects/packages/jitm/src/css/jetpack-admin-jitm-rtl.css similarity index 100% rename from projects/packages/jitm/assets/jetpack-admin-jitm-rtl.css rename to projects/packages/jitm/src/css/jetpack-admin-jitm-rtl.css diff --git a/projects/packages/jitm/assets/jetpack-admin-jitm-rtl.min.css b/projects/packages/jitm/src/css/jetpack-admin-jitm-rtl.min.css similarity index 100% rename from projects/packages/jitm/assets/jetpack-admin-jitm-rtl.min.css rename to projects/packages/jitm/src/css/jetpack-admin-jitm-rtl.min.css diff --git a/projects/packages/jitm/assets/jetpack-admin-jitm.css b/projects/packages/jitm/src/css/jetpack-admin-jitm.css similarity index 100% rename from projects/packages/jitm/assets/jetpack-admin-jitm.css rename to projects/packages/jitm/src/css/jetpack-admin-jitm.css diff --git a/projects/packages/jitm/assets/jetpack-admin-jitm.min.css b/projects/packages/jitm/src/css/jetpack-admin-jitm.min.css similarity index 100% rename from projects/packages/jitm/assets/jetpack-admin-jitm.min.css rename to projects/packages/jitm/src/css/jetpack-admin-jitm.min.css diff --git a/projects/packages/jitm/assets/jetpack-admin-jitm.scss b/projects/packages/jitm/src/css/jetpack-admin-jitm.scss similarity index 100% rename from projects/packages/jitm/assets/jetpack-admin-jitm.scss rename to projects/packages/jitm/src/css/jetpack-admin-jitm.scss diff --git a/projects/plugins/jetpack/_inc/jetpack-jitm.js b/projects/packages/jitm/src/js/jetpack-jitm.js similarity index 100% rename from projects/plugins/jetpack/_inc/jetpack-jitm.js rename to projects/packages/jitm/src/js/jetpack-jitm.js diff --git a/projects/packages/jitm/webpack.config.js b/projects/packages/jitm/webpack.config.js new file mode 100644 index 0000000000000..9330c017cec15 --- /dev/null +++ b/projects/packages/jitm/webpack.config.js @@ -0,0 +1,16 @@ +// @todo Remove this, use calypso-build instead. See https://github.com/Automattic/jetpack/pull/17571. +// That should also allow us to remove webpack from package.json. +const path = require( 'path' ); +const packagesFolder = path.resolve( __dirname, 'src/js' ); + +module.exports = [ + { + mode: 'production', + context: packagesFolder, + entry: './jetpack-jitm.js', + output: { + path: packagesFolder, + filename: 'jetpack-jitm.min.js', + }, + }, +]; diff --git a/projects/plugins/jetpack/.gitattributes b/projects/plugins/jetpack/.gitattributes index 1c5d9d2478bc2..019b374403b14 100644 --- a/projects/plugins/jetpack/.gitattributes +++ b/projects/plugins/jetpack/.gitattributes @@ -58,7 +58,7 @@ /tests/** production-exclude /tools/** production-exclude /vendor/automattic/**/README.md production-exclude -/vendor/automattic/**/assets/*.scss production-exclude +/vendor/automattic/**/src/css/*.scss production-exclude /vendor/automattic/**/composer.json production-exclude /vendor/automattic/jetpack-autoloader/** production-exclude /wp-cli-templates/** production-exclude diff --git a/projects/plugins/jetpack/composer.lock b/projects/plugins/jetpack/composer.lock index 7035916ef9be1..12fb23cf292ab 100644 --- a/projects/plugins/jetpack/composer.lock +++ b/projects/plugins/jetpack/composer.lock @@ -584,7 +584,7 @@ "dist": { "type": "path", "url": "../../packages/jitm", - "reference": "40bb94e8719f9b4c4e2b15c0db6c6d2af91098f0" + "reference": "aaa4ddbbc37befbf82b59b642d8adf88fe2e78aa" }, "require": { "automattic/jetpack-assets": "@dev", @@ -611,6 +611,14 @@ ] }, "scripts": { + "build-production": [ + "Composer\\Config::disableProcessTimeout", + "yarn build-production" + ], + "build-development": [ + "Composer\\Config::disableProcessTimeout", + "yarn build" + ], "phpunit": [ "@composer install", "./vendor/phpunit/phpunit/phpunit --colors=always" diff --git a/projects/plugins/jetpack/tools/builder/sass.js b/projects/plugins/jetpack/tools/builder/sass.js index 37e48b83a0cc9..64b2952757556 100644 --- a/projects/plugins/jetpack/tools/builder/sass.js +++ b/projects/plugins/jetpack/tools/builder/sass.js @@ -256,7 +256,7 @@ export const watch = function () { export const watchPackages = function () { return gulp.watch( - [ './packages/jitm/assets/*.scss', ...alwaysIgnoredPaths ], + [ './packages/jitm/src/css/*.scss', ...alwaysIgnoredPaths ], gulp.parallel( 'sass:packages:rtl' ) ); }; From 1fa6f79c3eba069c14540da3f34d403eb17edb5e Mon Sep 17 00:00:00 2001 From: Dan Roundhill Date: Wed, 17 Feb 2021 10:08:08 -0700 Subject: [PATCH 02/34] VideoPress Block: Add UI for Customizing Player Seekbar Colors (#18155) * Adding back changes after reverting to current master. * Adding missing arguments passed to `getVideoPressUrl()` --- .../extensions/blocks/videopress/edit.js | 21 ++++- .../extensions/blocks/videopress/editor.js | 12 +++ .../extensions/blocks/videopress/save.js | 6 ++ .../videopress/seekbar-color-settings.js | 80 +++++++++++++++++++ .../extensions/blocks/videopress/url.js | 18 ++++- 5 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 projects/plugins/jetpack/extensions/blocks/videopress/seekbar-color-settings.js diff --git a/projects/plugins/jetpack/extensions/blocks/videopress/edit.js b/projects/plugins/jetpack/extensions/blocks/videopress/edit.js index 8b408f53a2fbc..5d75eb15b9c31 100644 --- a/projects/plugins/jetpack/extensions/blocks/videopress/edit.js +++ b/projects/plugins/jetpack/extensions/blocks/videopress/edit.js @@ -34,6 +34,7 @@ import { get, indexOf } from 'lodash'; import Loading from './loading'; import { getVideoPressUrl } from './url'; import { getClassNames } from './utils'; +import SeekbarColorSettings from './seekbar-color-settings'; const VIDEO_POSTER_ALLOWED_MEDIA_TYPES = [ 'image' ]; @@ -363,6 +364,9 @@ const VideoPressEdit = CoreVideoEdit => + + + export default createHigherOrderComponent( compose( [ withSelect( ( select, ownProps ) => { - const { autoplay, controls, guid, loop, muted, poster, preload, src } = ownProps.attributes; + const { + autoplay, + controls, + guid, + loop, + muted, + poster, + preload, + seekbarColor, + seekbarLoadingColor, + seekbarPlayedColor, + src + } = ownProps.attributes; const { getEmbedPreview, isRequestingEmbedPreview } = select( 'core' ); const url = getVideoPressUrl( guid, { @@ -482,6 +498,9 @@ export default createHigherOrderComponent( muted, poster, preload, + seekbarColor, + seekbarLoadingColor, + seekbarPlayedColor } ); const preview = !! url && getEmbedPreview( url ); diff --git a/projects/plugins/jetpack/extensions/blocks/videopress/editor.js b/projects/plugins/jetpack/extensions/blocks/videopress/editor.js index 8e35fa0b43625..cba7f92354486 100644 --- a/projects/plugins/jetpack/extensions/blocks/videopress/editor.js +++ b/projects/plugins/jetpack/extensions/blocks/videopress/editor.js @@ -163,6 +163,18 @@ const addVideoPressSupport = ( settings, name ) => { type: 'string', default: 'metadata', }, + seekbarPlayedColor: { + type: 'string', + default: '', + }, + seekbarLoadingColor: { + type: 'string', + default: '', + }, + seekbarColor: { + type: 'string', + default: '', + }, src: { type: 'string', }, diff --git a/projects/plugins/jetpack/extensions/blocks/videopress/save.js b/projects/plugins/jetpack/extensions/blocks/videopress/save.js index cea506b7dcb1f..d2ab9ac56a97b 100644 --- a/projects/plugins/jetpack/extensions/blocks/videopress/save.js +++ b/projects/plugins/jetpack/extensions/blocks/videopress/save.js @@ -24,6 +24,9 @@ const VideoPressSave = CoreVideoSave => props => { videoPressClassNames, className, align, + seekbarColor, + seekbarPlayedColor, + seekbarLoadingColor, } = {}, } = props; @@ -47,6 +50,9 @@ const VideoPressSave = CoreVideoSave => props => { muted, poster, preload, + seekbarColor, + seekbarPlayedColor, + seekbarLoadingColor, } ); return ( diff --git a/projects/plugins/jetpack/extensions/blocks/videopress/seekbar-color-settings.js b/projects/plugins/jetpack/extensions/blocks/videopress/seekbar-color-settings.js new file mode 100644 index 0000000000000..ed48022b8082a --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/videopress/seekbar-color-settings.js @@ -0,0 +1,80 @@ +/** + * External dependencies + */ +import React from 'react'; +import { Component } from '@wordpress/element'; + +/** + * WordPress dependencies + */ +import { Button } from '@wordpress/components'; +import { PanelColorSettings } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import { __ } from '@wordpress/i18n'; + +class SeekbarColorSettings extends Component { + constructor() { + super( ...arguments ); + + const { seekbarColor, seekbarPlayedColor, seekbarLoadingColor } = this.props.attributes; + this.state = { seekbarColor, seekbarPlayedColor, seekbarLoadingColor }; + } + + handleChangeSeekbarColor = newColor => { + this.setState( { seekbarColor: newColor } ); + } + + handleChangeSeekbarLoadingColor = newColor => { + this.setState( { seekbarLoadingColor: newColor } ); + } + + handleChangeSeekbarPlayedColor = newColor => { + this.setState( { seekbarPlayedColor: newColor } ); + } + + saveColors = () => { + const { seekbarColor, seekbarLoadingColor, seekbarPlayedColor } = this.state; + const { setAttributes } = this.props; + setAttributes( { seekbarColor, seekbarLoadingColor, seekbarPlayedColor } ); + }; + + render() { + const { seekbarColor, seekbarPlayedColor, seekbarLoadingColor } = this.state; + + return ( + + + + ); + } +} + +export default SeekbarColorSettings; diff --git a/projects/plugins/jetpack/extensions/blocks/videopress/url.js b/projects/plugins/jetpack/extensions/blocks/videopress/url.js index 27372dd2c9c75..4c314f3033ce2 100644 --- a/projects/plugins/jetpack/extensions/blocks/videopress/url.js +++ b/projects/plugins/jetpack/extensions/blocks/videopress/url.js @@ -3,7 +3,20 @@ */ import { addQueryArgs } from '@wordpress/url'; -export const getVideoPressUrl = ( guid, { autoplay, controls, loop, muted, poster, preload } ) => { +export const getVideoPressUrl = ( + guid, + { + autoplay, + controls, + loop, + muted, + poster, + preload, + seekbarColor, + seekbarPlayedColor, + seekbarLoadingColor, + } +) => { if ( ! guid ) { return null; } @@ -22,6 +35,9 @@ export const getVideoPressUrl = ( guid, { autoplay, controls, loop, muted, poste ...( muted && { muted: true, persistVolume: false } ), ...( poster && { posterUrl: poster } ), ...( preload !== 'none' && { preloadContent: preload } ), + ...( seekbarColor !== '' && { sbc: seekbarColor } ), + ...( seekbarPlayedColor !== '' && { sbpc: seekbarPlayedColor } ), + ...( seekbarLoadingColor !== '' && { sblc: seekbarLoadingColor } ), }; return addQueryArgs( `https://videopress.com/v/${ guid }`, options ); }; From 7d9308deae78510a8c54dfe163875005c455fcb7 Mon Sep 17 00:00:00 2001 From: Kirk Wight Date: Wed, 17 Feb 2021 10:55:22 -0800 Subject: [PATCH 03/34] Podcast Helper: Allow filtering the number of tracks returned by get_track_list. (#18836) --- .../_inc/lib/class-jetpack-podcast-helper.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/projects/plugins/jetpack/_inc/lib/class-jetpack-podcast-helper.php b/projects/plugins/jetpack/_inc/lib/class-jetpack-podcast-helper.php index 941d4fb94a27f..6c7343e51bc74 100644 --- a/projects/plugins/jetpack/_inc/lib/class-jetpack-podcast-helper.php +++ b/projects/plugins/jetpack/_inc/lib/class-jetpack-podcast-helper.php @@ -170,8 +170,19 @@ public function get_track_list() { return $rss; } - // Get first ten items and format them. - $track_list = array_map( array( __CLASS__, 'setup_tracks_callback' ), $rss->get_items( 0, 10 ) ); + /** + * Allow requesting a specific number of tracks from SimplePie's `get_items` call. + * The default number of tracks is ten. + * + * @since 9.5.0 + * + * @param int $number Number of tracks fetched. Default is 10. + * @param object $rss The SimplePie object built from core's `fetch_feed` call. + */ + $tracks_quantity = apply_filters( 'jetpack_podcast_helper_list_quantity', 10, $rss ); + + // Process the requested number of items from our feed. + $track_list = array_map( array( __CLASS__, 'setup_tracks_callback' ), $rss->get_items( 0, $tracks_quantity ) ); // Filter out any tracks that are empty. // Reset the array indicies. From 48f437f5927e6ba872fcdc9977e3d2849ec44530 Mon Sep 17 00:00:00 2001 From: Sergey Mitroshin Date: Wed, 17 Feb 2021 13:05:12 -0600 Subject: [PATCH 04/34] In-Place Connection: reload for paid plans (#18813) If in-place connection is initiated from Jetpack Dashboard URL (`/wp-admin/admin.php?page=jetpack#/dashboard`), the Dashboard does not get reload for customers with paid plans. Those customers end up watching the endless spin of the loader with the "Finishing up" text until they finally reload the page and see Jetpack fully connected. --- projects/plugins/jetpack/_inc/connect-button.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/_inc/connect-button.js b/projects/plugins/jetpack/_inc/connect-button.js index 0067d18b7b9f0..bf47ca3c45f21 100644 --- a/projects/plugins/jetpack/_inc/connect-button.js +++ b/projects/plugins/jetpack/_inc/connect-button.js @@ -163,7 +163,9 @@ jQuery( document ).ready( function ( $ ) { var parser = document.createElement( 'a' ); parser.href = jpConnect.dashboardUrl; var reload = - window.location.pathname === parser.pathname && window.location.hash !== parser.hash; + window.location.pathname === parser.pathname && + window.location.hash.length && + parser.hash.length; window.location.assign( jpConnect.dashboardUrl ); From d0ecc8aa4a0f2b6e6ac458bfe48ac037f0e63aba Mon Sep 17 00:00:00 2001 From: robertf4 <42627630+robertf4@users.noreply.github.com> Date: Thu, 18 Feb 2021 03:51:59 -0600 Subject: [PATCH 05/34] Recommendations: Adjust banner styles (#18849) Co-authored-by: Jeff Golenski --- .../scss/jetpack-recommendations-banner.scss | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/projects/plugins/jetpack/scss/jetpack-recommendations-banner.scss b/projects/plugins/jetpack/scss/jetpack-recommendations-banner.scss index 4169963be1db3..e1d9ea41469c8 100644 --- a/projects/plugins/jetpack/scss/jetpack-recommendations-banner.scss +++ b/projects/plugins/jetpack/scss/jetpack-recommendations-banner.scss @@ -5,11 +5,7 @@ position: relative; display: grid; grid-template-columns: 55% 45%; - - @include breakpoint( '<660px' ) { - grid-template-columns: 100% 0%; - } - + max-width: 1600px; min-height: 480px; background: linear-gradient( 120deg, @@ -22,15 +18,39 @@ margin: 3rem 1.25rem 1.25rem 0; - @include breakpoint( '<960px' ) { + @media (max-width: 1152px) { margin: 4rem 1.25rem 1.25rem 0; + grid-template-columns: 100% 0%; + } + + @media (min-width: 1688px) { + grid-template-columns: 65% 35%; + } + + .notice-dismiss { + @include breakpoint( '<480px' ) { + padding: 16px; + } } } .jp-recommendations-banner__content { display: flex; flex-direction: column; + padding-right: 48px; + + @media (min-width: 1688px) { + max-width: 600px; + } + + @include breakpoint( '<660px' ) { + padding-right: 32px; + } + + @include breakpoint( '<480px' ) { + padding-right: 16px; + } } .jp-recommendations-banner__logo { @@ -55,6 +75,10 @@ font-size: 16px; margin: 32px 0 24px 32px; + @media (min-width: 1152px) { + max-width: 600px; + } + @include breakpoint( '<660px' ) { margin: 32px 32px 24px 32px; } @@ -65,6 +89,7 @@ } .jp-recommendations-banner__answer { + max-width: 600px; text-align: center; margin: 0 0 32px 32px; @@ -163,13 +188,14 @@ right: 0; } - @include breakpoint( '<660px' ) { + @include breakpoint( '<960px' ) { display: none; } } .jp-recommendations-banner__illustration-background { width: 100%; + max-height: 480px; } .jp-recommendations-banner__illustration-foreground { From ff3e7fa6514cb01270cb6181ea94b91859bd1169 Mon Sep 17 00:00:00 2001 From: Rosalind W Date: Thu, 18 Feb 2021 03:54:41 -0600 Subject: [PATCH 06/34] Add Cloudflare Analytics snippet to site header via (WPCOM only) Jetpack plugin (#18771) * Add Cloudflare Analytics snippet to site header via (WPCOM only) Jetpack plugin * Updates based on PR feedback * Ensure the feature is also enabled on wpcom simple sites See https://github.com/Automattic/jetpack/pull/18771#issuecomment-777033437 * Update option name. Co-authored-by: Jeremy Herve --- ....wpcom-json-api-site-settings-endpoint.php | 4 +- ...m-json-api-site-settings-v1-4-endpoint.php | 2 +- .../cloudflare-analytics.php | 37 +++++++++++++++++++ .../plugins/jetpack/modules/module-extras.php | 1 + 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 projects/plugins/jetpack/modules/cloudflare-analytics/cloudflare-analytics.php diff --git a/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php b/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php index 2b4b303bfac12..f0d95470baebc 100644 --- a/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php +++ b/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-endpoint.php @@ -404,7 +404,7 @@ public function get_settings_response() { ? get_lang_id_by_code( wpcom_l10n_get_blog_locale_variant( $blog_id, true ) ) : get_option( 'lang_id' ), 'wga' => $this->get_google_analytics(), - 'cloudflare_analytics' => get_option( 'cloudflare_analytics' ), + 'jetpack_cloudflare_analytics' => get_option( 'jetpack_cloudflare_analytics' ), 'disabled_likes' => (bool) get_option( 'disabled_likes' ), 'disabled_reblogs' => (bool) get_option( 'disabled_reblogs' ), 'jetpack_comment_likes_enabled' => (bool) get_option( 'jetpack_comment_likes_enabled', false ), @@ -663,7 +663,7 @@ public function update_settings() { } break; - case 'cloudflare_analytics': + case 'jetpack_cloudflare_analytics': if ( ! isset( $value['code'] ) || ! preg_match( '/^$|^[a-fA-F0-9]+$/i', $value['code'] ) ) { return new WP_Error( 'invalid_code', __( 'Invalid Cloudflare Analytics ID', 'jetpack' ) ); } diff --git a/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-v1-4-endpoint.php b/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-v1-4-endpoint.php index b0307336e3bca..0c33b341c6002 100644 --- a/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-v1-4-endpoint.php +++ b/projects/plugins/jetpack/json-endpoints/class.wpcom-json-api-site-settings-v1-4-endpoint.php @@ -72,7 +72,7 @@ 'lang_id' => '(int) ID for language blog is written in', 'locale' => '(string) locale code for language blog is written in', 'wga' => '(array) Google Analytics Settings', - 'cloudflare_analytics' => '(array) Cloudflare Analytics Settings', + 'jetpack_cloudflare_analytics' => '(array) Cloudflare Analytics Settings', 'disabled_likes' => '(bool) Are likes globally disabled (they can still be turned on per post)?', 'disabled_reblogs' => '(bool) Are reblogs disabled on posts?', 'jetpack_comment_likes_enabled' => '(bool) Are comment likes enabled for all comments?', diff --git a/projects/plugins/jetpack/modules/cloudflare-analytics/cloudflare-analytics.php b/projects/plugins/jetpack/modules/cloudflare-analytics/cloudflare-analytics.php new file mode 100644 index 0000000000000..c72c1eccd0fb8 --- /dev/null +++ b/projects/plugins/jetpack/modules/cloudflare-analytics/cloudflare-analytics.php @@ -0,0 +1,37 @@ + + +\r\n", + esc_html( $option['code'] ) + ); + } +} +add_action( 'wp_footer', __NAMESPACE__ . '\insert_tracking_id', 999 ); diff --git a/projects/plugins/jetpack/modules/module-extras.php b/projects/plugins/jetpack/modules/module-extras.php index 0f6b82f9ea8c5..73d08ef9ff60e 100644 --- a/projects/plugins/jetpack/modules/module-extras.php +++ b/projects/plugins/jetpack/modules/module-extras.php @@ -46,6 +46,7 @@ // Some features are only available when connected to WordPress.com. $connected_tools = array( 'calypsoify/class-jetpack-calypsoify.php', + 'cloudflare-analytics/cloudflare-analytics.php', 'plugin-search.php', 'scan/scan.php', // Shows Jetpack Scan alerts in the admin bar if threats found. 'simple-payments/simple-payments.php', From 4e3403e002d88aa0d106efffa7f02f0464be7677 Mon Sep 17 00:00:00 2001 From: Eric Binnion Date: Thu, 18 Feb 2021 03:55:52 -0600 Subject: [PATCH 07/34] SSO: Disregard filters to use in-place connection when redirecting after SSO (#18801) --- projects/plugins/jetpack/modules/sso.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/projects/plugins/jetpack/modules/sso.php b/projects/plugins/jetpack/modules/sso.php index 558e77ed06b0e..60cab2a1d760c 100644 --- a/projects/plugins/jetpack/modules/sso.php +++ b/projects/plugins/jetpack/modules/sso.php @@ -1067,7 +1067,11 @@ function maybe_authorize_user_after_sso() { /** * Return the raw connect URL with our redirect and attribute connection to SSO. + * We remove any other filters that may be turning on the in-place connection + * since we will be redirecting the user as opposed to iFraming. */ + remove_all_filters( 'jetpack_use_iframe_authorization_flow' ); + add_filter( 'jetpack_use_iframe_authorization_flow', '__return_false' ); $connect_url = Jetpack::init()->build_connect_url( true, $redirect_after_auth, 'sso' ); add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) ); From 6a09f0111017972baf44dac9d6f2bb425588d47e Mon Sep 17 00:00:00 2001 From: Tom Cafferkey Date: Thu, 18 Feb 2021 11:38:24 +0000 Subject: [PATCH 08/34] Add advanced menu items for General and Writing for WP Admin (#18804) --- .../admin-menu/class-atomic-admin-menu.php | 7 ++++++ .../test-class-atomic-admin-menu.php | 22 ++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/modules/masterbar/admin-menu/class-atomic-admin-menu.php b/projects/plugins/jetpack/modules/masterbar/admin-menu/class-atomic-admin-menu.php index b59c7f00304ff..57032e256463a 100644 --- a/projects/plugins/jetpack/modules/masterbar/admin-menu/class-atomic-admin-menu.php +++ b/projects/plugins/jetpack/modules/masterbar/admin-menu/class-atomic-admin-menu.php @@ -203,6 +203,13 @@ public function add_options_menu( $wp_admin = false ) { add_options_page( esc_attr__( 'Hosting Configuration', 'jetpack' ), __( 'Hosting Configuration', 'jetpack' ), 'manage_options', 'https://wordpress.com/hosting-config/' . $this->domain, null, 6 ); parent::add_options_menu( $wp_admin ); + + // No need to add a menu linking to WP Admin if there is already one. + if ( ! $wp_admin ) { + $parent_menu_slug = 'https://wordpress.com/settings/general/' . $this->domain; + add_submenu_page( $parent_menu_slug, esc_attr__( 'Advanced General', 'jetpack' ), __( 'Advanced General', 'jetpack' ), 'manage_options', 'options-general.php' ); + add_submenu_page( $parent_menu_slug, esc_attr__( 'Advanced Writing', 'jetpack' ), __( 'Advanced Writing', 'jetpack' ), 'manage_options', 'options-writing.php' ); + } } /** diff --git a/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-atomic-admin-menu.php b/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-atomic-admin-menu.php index b630357e5b189..0a8cc6f75fe78 100644 --- a/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-atomic-admin-menu.php +++ b/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-atomic-admin-menu.php @@ -310,10 +310,30 @@ public function test_add_tools_menu() { public function test_add_options_menu() { global $submenu; + $general_submenu_item = array( + 'Advanced General', + 'manage_options', + 'options-general.php', + 'Advanced General', + ); + + $writing_submenu_item = array( + 'Advanced Writing', + 'manage_options', + 'options-writing.php', + 'Advanced Writing', + ); + $slug = 'https://wordpress.com/settings/general/' . static::$domain; - static::$admin_menu->add_options_menu( false ); + static::$admin_menu->add_options_menu( true ); + $this->assertNotContains( $general_submenu_item, $submenu['options-general.php'] ); + $this->assertNotContains( $writing_submenu_item, $submenu['options-general.php'] ); + + static::$admin_menu->add_options_menu( false ); $this->assertContains( 'Hosting Configuration', $submenu[ $slug ][6] ); + $this->assertContains( $general_submenu_item, $submenu[ $slug ] ); + $this->assertContains( $writing_submenu_item, $submenu[ $slug ] ); } /** From 6102e29f7ed30f9f85370008dee3b0fa4c52e0d1 Mon Sep 17 00:00:00 2001 From: Miguel Torres Date: Thu, 18 Feb 2021 13:45:03 +0100 Subject: [PATCH 09/34] Admin Menu: Fix link destination for Widgets and Menus (#18862) --- .../masterbar/admin-menu/class-admin-menu.php | 43 +++++++++---------- .../admin-menu/class-atomic-admin-menu.php | 11 +++++ .../admin-menu/class-jetpack-admin-menu.php | 11 +++++ .../admin-menu/class-wpcom-admin-menu.php | 12 +----- .../masterbar/test-class-admin-menu.php | 2 +- .../test-class-atomic-admin-menu.php | 21 +++++++++ .../test-class-jetpack-admin-menu.php | 21 +++++++++ .../masterbar/test-class-wpcom-admin-menu.php | 21 --------- 8 files changed, 87 insertions(+), 55 deletions(-) diff --git a/projects/plugins/jetpack/modules/masterbar/admin-menu/class-admin-menu.php b/projects/plugins/jetpack/modules/masterbar/admin-menu/class-admin-menu.php index a720a8c49cf47..198828352a08a 100644 --- a/projects/plugins/jetpack/modules/masterbar/admin-menu/class-admin-menu.php +++ b/projects/plugins/jetpack/modules/masterbar/admin-menu/class-admin-menu.php @@ -35,13 +35,6 @@ class Admin_Menu { */ protected $domain; - /** - * The customizer default link. - * - * @var string - */ - protected $customize_slug = 'customize.php'; - /** * Admin_Menu constructor. */ @@ -110,7 +103,11 @@ public function reregister_menu_items() { $this->add_testimonials_menu( $wp_admin ); $this->add_portfolio_menu( $wp_admin ); $this->add_comments_menu( $wp_admin ); - $this->add_appearance_menu( $wp_admin ); + + // Whether Themes/Customize links should point to Calypso (false) or wp-admin (true). + $wp_admin_themes = $wp_admin; + $wp_admin_customize = $wp_admin; + $this->add_appearance_menu( $wp_admin_themes, $wp_admin_customize ); $this->add_plugins_menu( $wp_admin ); $this->add_users_menu( $wp_admin ); @@ -382,63 +379,65 @@ function ( $parent_file ) use ( $menu_slug ) { /** * Adds Appearance menu. * - * @param bool $wp_admin Optional. Whether links should point to Calypso or wp-admin. Default false (Calypso). + * @param bool $wp_admin_themes Optional. Whether Themes link should point to Calypso or wp-admin. Default false (Calypso). + * @param bool $wp_admin_customize Optional. Whether Customize link should point to Calypso or wp-admin. Default false (Calypso). */ - public function add_appearance_menu( $wp_admin = false ) { + public function add_appearance_menu( $wp_admin_themes = false, $wp_admin_customize = false ) { $user_can_customize = current_user_can( 'customize' ); $appearance_cap = current_user_can( 'switch_themes' ) ? 'switch_themes' : 'edit_theme_options'; - $themes_slug = $wp_admin ? 'themes.php' : 'https://wordpress.com/themes/' . $this->domain; - $customize_url = add_query_arg( 'return', urlencode( remove_query_arg( wp_removable_query_args(), wp_unslash( $_SERVER['REQUEST_URI'] ) ) ), 'customize.php' ); // phpcs:ignore + $themes_slug = $wp_admin_themes ? 'themes.php' : 'https://wordpress.com/themes/' . $this->domain; + $customize_slug = $wp_admin_customize ? 'customize.php' : 'https://wordpress.com/customize/' . $this->domain; // phpcs:ignore remove_menu_page( 'themes.php' ); remove_submenu_page( 'themes.php', 'themes.php' ); remove_submenu_page( 'themes.php', 'theme-editor.php' ); - remove_submenu_page( 'themes.php', $customize_url ); + remove_submenu_page( 'themes.php', add_query_arg( 'return', rawurlencode( remove_query_arg( wp_removable_query_args(), wp_unslash( $_SERVER['REQUEST_URI'] ) ) ), 'customize.php' ) ); remove_submenu_page( 'themes.php', 'custom-header' ); remove_submenu_page( 'themes.php', 'custom-background' ); add_menu_page( esc_attr__( 'Appearance', 'jetpack' ), __( 'Appearance', 'jetpack' ), $appearance_cap, $themes_slug, null, 'dashicons-admin-appearance', 60 ); add_submenu_page( $themes_slug, esc_attr__( 'Themes', 'jetpack' ), __( 'Themes', 'jetpack' ), 'switch_themes', $themes_slug, null, 0 ); - add_submenu_page( $themes_slug, esc_attr__( 'Customize', 'jetpack' ), __( 'Customize', 'jetpack' ), 'customize', $this->customize_slug, null, 1 ); + add_submenu_page( $themes_slug, esc_attr__( 'Customize', 'jetpack' ), __( 'Customize', 'jetpack' ), 'customize', $customize_slug, null, 1 ); // Maintain id as JS selector. $GLOBALS['menu'][60][5] = 'menu-appearance'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited if ( current_theme_supports( 'custom-header' ) && $user_can_customize ) { - $customize_header_url = add_query_arg( array( 'autofocus' => array( 'control' => 'header_image' ) ), $customize_url ); + $customize_header_url = add_query_arg( array( 'autofocus' => array( 'control' => 'header_image' ) ), $customize_slug ); remove_submenu_page( 'themes.php', esc_url( $customize_header_url ) ); // TODO: Remove WPCom_Theme_Customizer::modify_header_menu_links() and WPcom_Custom_Header::modify_admin_menu_links(). $customize_header_url = admin_url( 'themes.php?page=custom-header' ); remove_submenu_page( 'themes.php', esc_url( $customize_header_url ) ); - $customize_header_url = add_query_arg( array( 'autofocus' => array( 'control' => 'header_image' ) ), $this->customize_slug ); + $customize_header_url = add_query_arg( array( 'autofocus' => array( 'control' => 'header_image' ) ), $customize_slug ); add_submenu_page( $themes_slug, __( 'Header', 'jetpack' ), __( 'Header', 'jetpack' ), 'customize', esc_url( $customize_header_url ), null, 15 ); } if ( current_theme_supports( 'custom-background' ) && $user_can_customize ) { - $customize_background_url = add_query_arg( array( 'autofocus' => array( 'control' => 'background_image' ) ), $customize_url ); + $customize_background_url = add_query_arg( array( 'autofocus' => array( 'control' => 'background_image' ) ), $customize_slug ); remove_submenu_page( 'themes.php', esc_url( $customize_background_url ) ); // TODO: Remove Colors_Manager::modify_header_menu_links() and Colors_Manager_Common::modify_header_menu_links(). $customize_background_url = add_query_arg( array( 'autofocus' => array( 'section' => 'colors_manager_tool' ) ), admin_url( 'customize.php' ) ); remove_submenu_page( 'themes.php', esc_url( $customize_background_url ) ); - $customize_background_url = add_query_arg( array( 'autofocus' => array( 'section' => 'colors_manager_tool' ) ), $this->customize_slug ); + $customize_background_url = add_query_arg( array( 'autofocus' => array( 'section' => 'colors_manager_tool' ) ), $customize_slug ); add_submenu_page( $themes_slug, esc_attr__( 'Background', 'jetpack' ), __( 'Background', 'jetpack' ), 'customize', esc_url( $customize_background_url ), null, 20 ); } if ( current_theme_supports( 'widgets' ) ) { remove_submenu_page( 'themes.php', 'widgets.php' ); + remove_submenu_page( 'themes.php', 'gutenberg-widgets' ); - $customize_menu_url = add_query_arg( array( 'autofocus' => array( 'panel' => 'widgets' ) ), $this->customize_slug ); - add_submenu_page( $themes_slug, esc_attr__( 'Widgets', 'jetpack' ), __( 'Widgets', 'jetpack' ), 'customize', esc_url( $customize_menu_url ), null, 20 ); + $customize_widgets_url = $wp_admin_customize ? 'widgets.php' : add_query_arg( array( 'autofocus' => array( 'panel' => 'widgets' ) ), $customize_slug ); + add_submenu_page( $themes_slug, esc_attr__( 'Widgets', 'jetpack' ), __( 'Widgets', 'jetpack' ), 'customize', esc_url( $customize_widgets_url ), null, 20 ); } if ( current_theme_supports( 'menus' ) || current_theme_supports( 'widgets' ) ) { remove_submenu_page( 'themes.php', 'nav-menus.php' ); - $customize_menu_url = add_query_arg( array( 'autofocus' => array( 'panel' => 'nav_menus' ) ), $this->customize_slug ); - add_submenu_page( $themes_slug, esc_attr__( 'Menus', 'jetpack' ), __( 'Menus', 'jetpack' ), 'customize', esc_url( $customize_menu_url ), null, 20 ); + $customize_menus_url = $wp_admin_customize ? 'nav-menus.php' : add_query_arg( array( 'autofocus' => array( 'panel' => 'nav_menus' ) ), $customize_slug ); + add_submenu_page( $themes_slug, esc_attr__( 'Menus', 'jetpack' ), __( 'Menus', 'jetpack' ), 'customize', esc_url( $customize_menus_url ), null, 20 ); } // Register menu for the Custom CSS Jetpack module, but don't add it as a menu item. diff --git a/projects/plugins/jetpack/modules/masterbar/admin-menu/class-atomic-admin-menu.php b/projects/plugins/jetpack/modules/masterbar/admin-menu/class-atomic-admin-menu.php index 57032e256463a..a870da179c519 100644 --- a/projects/plugins/jetpack/modules/masterbar/admin-menu/class-atomic-admin-menu.php +++ b/projects/plugins/jetpack/modules/masterbar/admin-menu/class-atomic-admin-menu.php @@ -222,4 +222,15 @@ public function add_theme_install_menu( $wp_admin = false ) { add_submenu_page( $parent_menu_slug, esc_attr__( 'Add New Theme', 'jetpack' ), __( 'Add New Theme', 'jetpack' ), 'install_themes', 'theme-install.php', null, 1 ); } + + /** + * Adds Appearance menu. + * + * @param bool $wp_admin_themes Optional. Whether Themes link should point to Calypso or wp-admin. Default false (Calypso). + * @param bool $wp_admin_customize Optional. Whether Customize link should point to Calypso or wp-admin. Default false (Calypso). + */ + public function add_appearance_menu( $wp_admin_themes = false, $wp_admin_customize = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + // Customize on Atomic sites is always done on WP Admin. + parent::add_appearance_menu( $wp_admin_themes, true ); + } } diff --git a/projects/plugins/jetpack/modules/masterbar/admin-menu/class-jetpack-admin-menu.php b/projects/plugins/jetpack/modules/masterbar/admin-menu/class-jetpack-admin-menu.php index af86d49cf355e..b8c1e748f8f1c 100644 --- a/projects/plugins/jetpack/modules/masterbar/admin-menu/class-jetpack-admin-menu.php +++ b/projects/plugins/jetpack/modules/masterbar/admin-menu/class-jetpack-admin-menu.php @@ -76,4 +76,15 @@ public function add_wp_admin_menu() { add_menu_page( __( 'WP Admin', 'jetpack' ), __( 'WP Admin', 'jetpack' ), 'read', $menu_slug, null, 'dashicons-wordpress-alt', $position ); } + + /** + * Adds Appearance menu. + * + * @param bool $wp_admin_themes Optional. Whether Themes link should point to Calypso or wp-admin. Default false (Calypso). + * @param bool $wp_admin_customize Optional. Whether Customize link should point to Calypso or wp-admin. Default false (Calypso). + */ + public function add_appearance_menu( $wp_admin_themes = false, $wp_admin_customize = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + // Customize on Atomic sites is always done on Jetpack sites. + parent::add_appearance_menu( $wp_admin_themes, true ); + } } diff --git a/projects/plugins/jetpack/modules/masterbar/admin-menu/class-wpcom-admin-menu.php b/projects/plugins/jetpack/modules/masterbar/admin-menu/class-wpcom-admin-menu.php index 2a2522fc34ac8..e509536f06187 100644 --- a/projects/plugins/jetpack/modules/masterbar/admin-menu/class-wpcom-admin-menu.php +++ b/projects/plugins/jetpack/modules/masterbar/admin-menu/class-wpcom-admin-menu.php @@ -16,15 +16,6 @@ */ class WPcom_Admin_Menu extends Admin_Menu { - /** - * WPcom_Admin_Menu constructor. - */ - protected function __construct() { - parent::__construct(); - - $this->customize_slug = 'https://wordpress.com/customize/' . $this->domain; - } - /** * Sets up class properties for REST API requests. * @@ -34,8 +25,7 @@ public function rest_api_init( $response ) { parent::rest_api_init( $response ); // Get domain for requested site. - $this->domain = ( new Status() )->get_site_suffix(); - $this->customize_slug = 'https://wordpress.com/customize/' . $this->domain; + $this->domain = ( new Status() )->get_site_suffix(); return $response; } diff --git a/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-admin-menu.php b/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-admin-menu.php index 89c75c471f05e..1029155b8eaca 100644 --- a/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-admin-menu.php +++ b/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-admin-menu.php @@ -491,7 +491,7 @@ public function test_add_comments_menu() { */ public function test_add_appearance_menu() { global $menu, $submenu; - $customize_slug = 'customize.php'; + $customize_slug = 'https://wordpress.com/customize/' . static::$domain; static::$admin_menu->add_appearance_menu( false ); $slug = 'https://wordpress.com/themes/' . static::$domain; diff --git a/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-atomic-admin-menu.php b/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-atomic-admin-menu.php index 0a8cc6f75fe78..377c3b4d78cd2 100644 --- a/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-atomic-admin-menu.php +++ b/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-atomic-admin-menu.php @@ -377,4 +377,25 @@ public function test_add_theme_install_menu() { $this->assertNotContains( $submenu_item, $submenu[ $slug ] ); } } + + /** + * Tests add_appearance_menu + * + * @covers ::add_appearance_menu + */ + public function test_add_appearance_menu() { + global $submenu; + + $slug = 'https://wordpress.com/themes/' . static::$domain; + static::$admin_menu->add_appearance_menu( false, false ); + + // Check Customize menu always links to WP Admin. + $customize_submenu_item = array( + 'Customize', + 'customize', + 'customize.php', + 'Customize', + ); + $this->assertContains( $customize_submenu_item, $submenu[ $slug ] ); + } } diff --git a/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-jetpack-admin-menu.php b/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-jetpack-admin-menu.php index b879467da02e1..be0ab114ad5b3 100644 --- a/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-jetpack-admin-menu.php +++ b/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-jetpack-admin-menu.php @@ -159,4 +159,25 @@ public function test_add_wp_admin_menu() { ); $this->assertSame( end( $menu ), $wp_admin_menu_item ); } + + /** + * Tests add_appearance_menu + * + * @covers ::add_appearance_menu + */ + public function test_add_appearance_menu() { + global $submenu; + + $slug = 'https://wordpress.com/themes/' . static::$domain; + static::$admin_menu->add_appearance_menu( false, false ); + + // Check Customize menu always links to WP Admin. + $customize_submenu_item = array( + 'Customize', + 'customize', + 'customize.php', + 'Customize', + ); + $this->assertContains( $customize_submenu_item, $submenu[ $slug ] ); + } } diff --git a/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-wpcom-admin-menu.php b/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-wpcom-admin-menu.php index b01e14d93812c..f2b16d85f51b3 100644 --- a/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-wpcom-admin-menu.php +++ b/projects/plugins/jetpack/tests/php/modules/masterbar/test-class-wpcom-admin-menu.php @@ -390,27 +390,6 @@ public function test_add_options_menu() { $this->assertContains( $sharing_submenu_item, $submenu[ $slug ] ); } - /** - * Tests add_appearance_menu - * - * @covers ::add_appearance_menu - */ - public function test_add_appearance_menu() { - global $submenu; - - $slug = 'https://wordpress.com/themes/' . static::$domain; - static::$admin_menu->add_appearance_menu( false ); - - // Check Customize menu always links to WP.com. - $customize_submenu_item = array( - 'Customize', - 'customize', - 'https://wordpress.com/customize/' . static::$domain, - 'Customize', - ); - $this->assertContains( $customize_submenu_item, $submenu[ $slug ] ); - } - /** * Tests add_gutenberg_menus * From ede76747a11517cc64c1d4f1095699e71d61f323 Mon Sep 17 00:00:00 2001 From: robertf4 <42627630+robertf4@users.noreply.github.com> Date: Thu, 18 Feb 2021 09:05:05 -0600 Subject: [PATCH 10/34] Setup Wizard: Remove setup wizard (#18867) Co-authored-by: Jeremy Herve --- projects/packages/jitm/src/js/jetpack-jitm.js | 7 +- .../options/legacy/class-jetpack-options.php | 2 +- .../index.jsx | 51 - projects/plugins/jetpack/_inc/client/main.jsx | 33 +- .../jetpack/_inc/client/scss/style.scss | 1 - .../setup-wizard/checklist-answer/index.jsx | 119 -- .../setup-wizard/checklist-answer/style.scss | 125 -- .../feature-toggle-group/index.jsx | 40 - .../feature-toggle-group/style.scss | 47 - .../setup-wizard/feature-toggle/README.md | 28 - .../setup-wizard/feature-toggle/index.jsx | 262 ---- .../feature-toggle/map-feature-to-props.js | 1171 ----------------- .../setup-wizard/feature-toggle/style.scss | 175 --- .../setup-wizard/income-question/index.jsx | 155 --- .../setup-wizard/income-question/style.scss | 38 - .../_inc/client/setup-wizard/index.jsx | 80 -- .../client/setup-wizard/intro-page/index.jsx | 124 -- .../client/setup-wizard/intro-page/style.scss | 53 - .../recommended-features/index.jsx | 93 -- .../recommended-features/style.scss | 61 - .../recommended-features/variables.scss | 2 - .../_inc/client/setup-wizard/style.scss | 39 - .../setup-wizard/updates-question/index.jsx | 98 -- .../setup-wizard/updates-question/style.scss | 47 - .../jetpack/_inc/client/state/action-types.js | 17 - .../client/state/initial-state/reducer.js | 22 - .../jetpack/_inc/client/state/reducer.js | 2 - .../_inc/client/state/setup-wizard/actions.js | 70 - .../setup-wizard/feature-recommendations.js | 81 -- .../_inc/client/state/setup-wizard/index.js | 2 - .../_inc/client/state/setup-wizard/reducer.js | 142 -- .../jetpack/_inc/jetpack-wizard-banner.js | 50 - .../admin-pages/class.jetpack-react-page.php | 21 - .../jetpack/_inc/lib/class-jetpack-wizard.php | 26 +- .../lib/class.core-rest-api-endpoints.php | 92 -- .../class-jetpack-recommendations-banner.php | 4 +- .../jetpack/class-jetpack-wizard-banner.php | 237 +--- .../plugins/jetpack/class.jetpack-admin.php | 1 - projects/plugins/jetpack/class.jetpack.php | 2 - projects/plugins/jetpack/load-jetpack.php | 2 - .../jetpack/scss/jetpack-wizard-banner.scss | 124 -- .../lib/test_class.rest-api-endpoints.php | 33 +- tools/eslint-excludelist.json | 11 - 43 files changed, 16 insertions(+), 3774 deletions(-) delete mode 100644 projects/plugins/jetpack/_inc/client/components/data/query-setup-wizard-questionnaire/index.jsx delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/checklist-answer/index.jsx delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/checklist-answer/style.scss delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle-group/index.jsx delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle-group/style.scss delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/README.md delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/index.jsx delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/map-feature-to-props.js delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/style.scss delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/income-question/index.jsx delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/income-question/style.scss delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/index.jsx delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/intro-page/index.jsx delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/intro-page/style.scss delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/recommended-features/index.jsx delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/recommended-features/style.scss delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/recommended-features/variables.scss delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/style.scss delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/updates-question/index.jsx delete mode 100644 projects/plugins/jetpack/_inc/client/setup-wizard/updates-question/style.scss delete mode 100644 projects/plugins/jetpack/_inc/client/state/setup-wizard/actions.js delete mode 100644 projects/plugins/jetpack/_inc/client/state/setup-wizard/feature-recommendations.js delete mode 100644 projects/plugins/jetpack/_inc/client/state/setup-wizard/index.js delete mode 100644 projects/plugins/jetpack/_inc/client/state/setup-wizard/reducer.js delete mode 100644 projects/plugins/jetpack/_inc/jetpack-wizard-banner.js delete mode 100644 projects/plugins/jetpack/scss/jetpack-wizard-banner.scss diff --git a/projects/packages/jitm/src/js/jetpack-jitm.js b/projects/packages/jitm/src/js/jetpack-jitm.js index 77543503efb09..e838aabba05d7 100644 --- a/projects/packages/jitm/src/js/jetpack-jitm.js +++ b/projects/packages/jitm/src/js/jetpack-jitm.js @@ -116,7 +116,7 @@ jQuery( document ).ready( function ( $ ) { $.ajax( { url: window.jitm_config.api_root + 'jetpack/v4/jitm', method: 'POST', // using DELETE without permalinks is broken in default nginx configuration - beforeSend: function( xhr ) { + beforeSend: function ( xhr ) { xhr.setRequestHeader( 'X-WP-Nonce', window.jitm_config.nonce ); }, data: { @@ -210,11 +210,6 @@ jQuery( document ).ready( function ( $ ) { }; var reFetch = function () { - // Do not render JITMs if the Wizard Banner is displayed. - if ( $( '#jp-wizard-banner' ).length ) { - return; - } - $( '.jetpack-jitm-message' ).each( function () { var $el = $( this ); diff --git a/projects/packages/options/legacy/class-jetpack-options.php b/projects/packages/options/legacy/class-jetpack-options.php index 8a2c355538483..781f1ce64ba1c 100644 --- a/projects/packages/options/legacy/class-jetpack-options.php +++ b/projects/packages/options/legacy/class-jetpack-options.php @@ -71,7 +71,7 @@ public static function get_option_names( $type = 'compact' ) { 'mapbox_api_key', // (string) Mapbox API Key, for use with Map block. 'mailchimp', // (string) Mailchimp keyring data, for mailchimp block. 'xmlrpc_errors', // (array) Keys are XML-RPC signature error codes. Values are truthy. - 'dismissed_wizard_banner', // (int) True if the Wizard banner has been dismissed. + 'dismissed_wizard_banner', // (int) (DEPRECATED) True if the Wizard banner has been dismissed. ); case 'private': diff --git a/projects/plugins/jetpack/_inc/client/components/data/query-setup-wizard-questionnaire/index.jsx b/projects/plugins/jetpack/_inc/client/components/data/query-setup-wizard-questionnaire/index.jsx deleted file mode 100644 index 5e3220abcbe48..0000000000000 --- a/projects/plugins/jetpack/_inc/client/components/data/query-setup-wizard-questionnaire/index.jsx +++ /dev/null @@ -1,51 +0,0 @@ -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; - -/** - * Internal dependencies - */ -import { - fetchSetupWizardQuestionnaire, - isFetchingSetupWizardQuestionnaire, -} from 'state/setup-wizard'; -import { isOfflineMode } from 'state/connection'; - -class QuerySetupWizardQuestionnaire extends Component { - static propTypes = { - isFetchingSetupWizardQuestionnaire: PropTypes.bool, - isOfflineMode: PropTypes.bool, - }; - - static defaultProps = { - isFetchingScanStatus: false, - isOfflineMode: false, - }; - - componentDidMount() { - if ( ! this.props.isFetchingSetupWizardQuestionnaire && ! this.props.isOfflineMode ) { - this.props.fetchSetupWizardQuestionnaire(); - } - } - - render() { - return null; - } -} - -export default connect( - state => { - return { - isFetchingSetupWizardQuestionnaire: isFetchingSetupWizardQuestionnaire( state ), - isOfflineMode: isOfflineMode( state ), - }; - }, - dispatch => { - return { - fetchSetupWizardQuestionnaire: () => dispatch( fetchSetupWizardQuestionnaire() ), - }; - } -)( QuerySetupWizardQuestionnaire ); diff --git a/projects/plugins/jetpack/_inc/client/main.jsx b/projects/plugins/jetpack/_inc/client/main.jsx index 84fcbfb814354..6e82b120eafeb 100644 --- a/projects/plugins/jetpack/_inc/client/main.jsx +++ b/projects/plugins/jetpack/_inc/client/main.jsx @@ -33,12 +33,10 @@ import { getCurrentVersion, getTracksUserData, showRecommendations, - showSetupWizard, } from 'state/initial-state'; import { areThereUnsavedSettings, clearUnsavedSettingsFlag } from 'state/settings'; import { getSearchTerm } from 'state/search'; import { Recommendations } from 'recommendations'; -import { SetupWizard } from 'setup-wizard'; import AtAGlance from 'at-a-glance/index.jsx'; import MyPlan from 'my-plan/index.jsx'; import Footer from 'components/footer'; @@ -66,14 +64,6 @@ const recommendationsRoutes = [ '/recommendations/summary', ]; -const setupRoutes = [ - '/setup', - '/setup/intro', - '/setup/income', - '/setup/updates', - '/setup/features', -]; - const dashboardRoutes = [ '/', '/dashboard', '/reconnect', '/my-plan', '/plans' ]; const settingsRoutes = [ '/settings', @@ -256,19 +246,6 @@ class Main extends React.Component { /> ); break; - case '/setup': - case '/setup/intro': - case '/setup/income': - case '/setup/updates': - case '/setup/features': - if ( this.props.showSetupWizard ) { - navComponent = null; - pageComponent = ; - } else { - this.props.history.replace( '/dashboard' ); - pageComponent = this.getAtAGlance(); - } - break; case '/recommendations': case '/recommendations/site-type': case '/recommendations/woocommerce': @@ -327,12 +304,9 @@ class Main extends React.Component { shouldShowMasthead() { // Only show on the setup pages, dashboard, and settings page - return [ - ...setupRoutes, - ...dashboardRoutes, - ...recommendationsRoutes, - ...settingsRoutes, - ].includes( this.props.location.pathname ); + return [ ...dashboardRoutes, ...recommendationsRoutes, ...settingsRoutes ].includes( + this.props.location.pathname + ); } shouldShowFooter() { @@ -410,7 +384,6 @@ export default connect( rewindStatus: getRewindStatus( state ), currentVersion: getCurrentVersion( state ), showRecommendations: showRecommendations( state ), - showSetupWizard: showSetupWizard( state ), }; }, dispatch => ( { diff --git a/projects/plugins/jetpack/_inc/client/scss/style.scss b/projects/plugins/jetpack/_inc/client/scss/style.scss index e2feabfc33c81..93b20a1fd3592 100644 --- a/projects/plugins/jetpack/_inc/client/scss/style.scss +++ b/projects/plugins/jetpack/_inc/client/scss/style.scss @@ -58,5 +58,4 @@ @import '../performance/style'; @import '../recommendations/style'; @import '../settings/style'; -@import '../setup-wizard/style'; @import '../traffic/style'; diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/checklist-answer/index.jsx b/projects/plugins/jetpack/_inc/client/setup-wizard/checklist-answer/index.jsx deleted file mode 100644 index d23e8dc919f19..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/checklist-answer/index.jsx +++ /dev/null @@ -1,119 +0,0 @@ -/** - * External dependencies - */ -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React, { useCallback, useState, useEffect } from 'react'; -import { connect } from 'react-redux'; - -/** - * Internal dependencies - */ -import Gridicon from 'components/gridicon'; -import { getSetupWizardAnswer, updateSetupWizardQuestionnaire } from 'state/setup-wizard'; - -import './style.scss'; - -let ChecklistAnswer = props => { - const { checked, title, details, answerKey, updateChecklistAnswerQuestion } = props; - - const [ expanded, setExpanded ] = useState( false ); - const [ windowWidth, setWindowWidth ] = useState( false ); - - const handleResize = useCallback( () => { - setWindowWidth( window.innerWidth <= 660 ? 'small' : 'large' ); - }, [ window.innerWidth ] ); - - useEffect( () => { - handleResize(); // Call this once to make sure windowWidth is initialized - window.addEventListener( 'resize', handleResize ); - return () => { - window.removeEventListener( 'resize', handleResize ); - }; - } ); - - const toggleCheckboxLargeWindow = useCallback( () => { - if ( 'small' === windowWidth ) { - return; - } - - const newCheckedValue = ! checked; - updateChecklistAnswerQuestion( { [ answerKey ]: newCheckedValue } ); - }, [ checked, windowWidth ] ); - - const toggleCheckboxSmallWindow = useCallback( () => { - if ( 'large' === windowWidth ) { - return; - } - - const newCheckedValue = ! checked; - updateChecklistAnswerQuestion( { [ answerKey ]: newCheckedValue } ); - }, [ checked, windowWidth ] ); - - const toggleExpanded = useCallback( () => { - setExpanded( ! expanded ); - }, [ expanded ] ); - - const smallWindow = 'small' === windowWidth; - - const chevronIcon = expanded ? 'chevron-up' : 'chevron-down'; - - return ( -
-
- -
-
-

{ title }

-
-
-

{ details }

-
-
- -
-
- ); -}; - -ChecklistAnswer.propTypes = { - answerKey: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - details: PropTypes.string.isRequired, -}; - -ChecklistAnswer = connect( - ( state, ownProps ) => ( { - checked: getSetupWizardAnswer( state, ownProps.answerKey ), - } ), - dispatch => ( { - updateChecklistAnswerQuestion: answer => dispatch( updateSetupWizardQuestionnaire( answer ) ), - } ) -)( ChecklistAnswer ); - -export { ChecklistAnswer }; diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/checklist-answer/style.scss b/projects/plugins/jetpack/_inc/client/setup-wizard/checklist-answer/style.scss deleted file mode 100644 index 9544092ff2b51..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/checklist-answer/style.scss +++ /dev/null @@ -1,125 +0,0 @@ -@import '../../scss/calypso-colors'; -@import '../../scss/mixin_breakpoint'; - -.jp-checklist-answer-container { - p { - margin: 0; - } - - @include breakpoint( '>660px' ) { - padding-top: 0.5rem; - - display: grid; - grid-template-rows: 2.5rem auto; - grid-template-columns: auto auto; - - box-sizing: border-box; - border: 1px solid $gray-lighten-20; - border-radius: 4px; - - cursor: pointer; - - &.checked { - background: $gray-light; - } - } - - @include breakpoint( '<660px' ) { - display: grid; - grid-template-rows: 30px auto; - grid-template-columns: 30px auto 30px; - - margin-bottom: 1.5rem; - } -} - -.jp-checklist-answer-container:focus { - @include breakpoint( '>660px' ) { - border: 1px solid $blue-medium; - } -} - -.jp-checklist-answer-checkbox-container { - display: flex; - justify-content: center; - align-items: center; -} - -.jp-checklist-answer-container input { - grid-row: 1; - grid-column: 1; - - @include breakpoint( '>660px' ) { - margin-left: 2rem; - margin-right: 1.5rem; - } - @include breakpoint( '<660px' ) { - margin: 0; - } -} - -.jp-checklist-answer-title { - grid-row: 1; - grid-column: 2; - - @include breakpoint( '>660px' ) { - margin: 0.45rem 1rem 1rem 0; - } - @include breakpoint( '<660px' ) { - display: flex; - align-items: center; - - margin: 0.5rem; - } - - font-weight: 600; - text-align: left; -} - -.jp-checklist-answer-details { - @include breakpoint( '>660px' ) { - grid-row: 2; - grid-column: 2; - margin: 0 1rem 1rem 0; - } - - @include breakpoint( '<660px' ) { - grid-column-start: 1; - grid-column-end: 3; - } - - @include breakpoint( '<660px' ) { - display: none; - - &.expanded { - display: block; - - margin-top: 1rem; - } - } - - text-align: left; -} - -.jp-checklist-answer-chevron-container { - @include breakpoint( '>660px' ) { - display: none; - } - - grid-row: 1; - grid-column: 3; - - display: flex; - justify-content: center; - align-items: center; - - @include breakpoint( '<660px' ) { - cursor: pointer; - } -} - -.jp-checklist-answer-chevron-container:focus { - @include breakpoint( '<660px' ) { - border: 1px solid $blue-medium; - } -} diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle-group/index.jsx b/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle-group/index.jsx deleted file mode 100644 index 1de417265b82e..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle-group/index.jsx +++ /dev/null @@ -1,40 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import PropTypes from 'prop-types'; - -/** - * Internal dependencies - */ -import { FeatureToggle } from '../feature-toggle'; - -import './style.scss'; - -const FeatureToggleGroup = props => { - const { title, details, features } = props; - - return ( -
-

{ title }

-

{ details }

-
- { features.map( feature => { - return ( -
- -
- ); - } ) } -
-
- ); -}; - -FeatureToggleGroup.propTypes = { - title: PropTypes.string.isRequired, - details: PropTypes.string.isRequired, - features: PropTypes.array.isRequired, -}; - -export { FeatureToggleGroup }; diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle-group/style.scss b/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle-group/style.scss deleted file mode 100644 index 1a88e64fba49f..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle-group/style.scss +++ /dev/null @@ -1,47 +0,0 @@ -@import '../../scss/mixin_breakpoint'; -@import '../recommended-features/variables'; - -.jp-setup-wizard-feature-toggle-group { - margin-bottom: 75px; - - @include breakpoint( $less-than-vertical-break ) { - margin-bottom: 50px; - } - - &:last-child { - margin-bottom: 0; - } - - h2 { - text-align: left; - margin: 1rem 0 0.25rem 0; - - @include breakpoint( $less-than-vertical-break ) { - margin: 1rem 1rem 0.25rem 1rem; - } - } -} - -.jp-setup-wizard-feature-toggle-group-details { - text-align: left; - margin: 0 0 1rem 0; - - @include breakpoint( $less-than-vertical-break ) { - margin: 0 1rem 1rem 1rem; - } -} - -.jp-setup-wizard-feature-toggle-group-toggles-area-container { - display: flex; - flex-direction: column; -} - -.jp-setup-wizard-feature-toggle-group-toggle-container { - @include breakpoint( $greater-than-vertical-break ) { - margin: 0.25rem 0; - } - - @include breakpoint( $less-than-vertical-break ) { - margin-top: -1px; - } -} diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/README.md b/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/README.md deleted file mode 100644 index 61586a3fc1c32..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# FeatureToggle - -FeatureToggle is used to provide a simple on/off toggle UI with upgrade options for any feature provided by Jetpack. - -If an upgradeLink is provided as a prop then the FeatureToggle will not provide toggle abilities and will instead use the toggle to open the upgrade link. - -#### How to use: - -```js -import { FeatureButton } from 'components/button'; - -const render = function() { - return ; -}; -``` - -#### Props - -- `title` - (string) The title of the feature. -- `details` - (string) The detail sentence to display for the feature. -- `checked` - (bool) True to show the toggle as checked, false to show the toggle as unchecked. -- `info` - _optional_ (string) Optional info about the feature. -- `onToggleChange` - _optional_ (func) Function that will be executed when the toggle is clicked and upgradeLink is not set. It will be passed the current checked value of checked. -- `configureLink` - _optional_ (string) Set as a URL to have the component display a "Configure" button that will open the given URL. -- `upgradeLink` - _optional_ (string) Set as a URL to have the component display a "Upgrade" button that will open the given URL. -- `optionsLink` - _optional_ (string) Set as a URL to have the component display a "View options" hyperlink that will open the given URL. -- `isPaid` - _optional_ (bool) Set to true to visually indicate that the feature is paid. -- `isDisabled` - _optional_ (bool) Set to true to disable the toggle. diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/index.jsx b/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/index.jsx deleted file mode 100644 index 1911fae2742b1..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/index.jsx +++ /dev/null @@ -1,262 +0,0 @@ -/** - * External dependencies - */ -import { Disabled, FormToggle } from '@wordpress/components'; -import classnames from 'classnames'; -import { __ } from '@wordpress/i18n'; -import PropTypes from 'prop-types'; -import React, { useCallback, useEffect, useState } from 'react'; -import { connect } from 'react-redux'; - -/** - * Internal dependencies - */ -import Button from 'components/button'; -import Gridicon from 'components/gridicon'; -import Spinner from 'components/spinner'; -import analytics from 'lib/analytics'; - -import { - mapStateToFeatureToggleProps, - mapDispatchToFeatureToggleProps, -} from './map-feature-to-props'; - -import './style.scss'; - -let FeatureToggle = props => { - const { - feature, - title, - details, - info, - checked, - configureLink, - upgradeLink, - optionsLink, - learnMoreLink, - onInstallClick, - isPaid = false, - isButtonLinkExternal = false, - isOptionsLinkExternal = false, - isLearnMoreLinkExternal = false, - } = props; - - const [ windowWidth, setWindowWidth ] = useState( false ); - const [ isInstalling, setIsInstalling ] = useState( false ); - - const handleResize = useCallback( () => { - setWindowWidth( window.innerWidth <= 660 ? 'small' : 'large' ); - }, [ window.innerWidth ] ); - - useEffect( () => { - handleResize(); // Call this once to make sure windowWidth is initialized - window.addEventListener( 'resize', handleResize ); - return () => { - window.removeEventListener( 'resize', handleResize ); - }; - } ); - - const onToggleChange = useCallback( () => { - if ( 'function' === typeof props.onToggleChange ) { - props.onToggleChange( checked ); - analytics.tracks.recordEvent( 'jetpack_wizard_feature_toggled', { - feature, - new_value: ! checked, - } ); - } - }, [ checked, props.onToggleChange ] ); - - const onUpgradeButtonClick = useCallback( () => { - analytics.tracks.recordEvent( 'jetpack_wizard_feature_upgrade', { - feature, - } ); - }, [ feature ] ); - - const onConfigureButtonClick = useCallback( () => { - analytics.tracks.recordEvent( 'jetpack_wizard_feature_configure', { - feature, - } ); - }, [ feature ] ); - - const onViewOptionsClick = useCallback( () => { - analytics.tracks.recordEvent( 'jetpack_wizard_feature_view_options', { - feature, - } ); - }, [ feature ] ); - - const onLearnMoreClick = useCallback( () => { - analytics.tracks.recordEvent( 'jetpack_wizard_feature_learn_more', { - feature, - } ); - }, [ feature ] ); - - const handleOnInstallClick = useCallback( () => { - setIsInstalling( true ); - onInstallClick().then( () => { - setIsInstalling( false ); - analytics.tracks.recordEvent( 'jetpack_wizard_feature_install', { - feature, - } ); - } ); - }, [ feature ] ); - - let buttonContent; - if ( ! checked && upgradeLink ) { - buttonContent = ( - - ); - } else if ( configureLink ) { - buttonContent = ( - - ); - } else if ( ! checked && onInstallClick ) { - if ( isInstalling ) { - buttonContent = ( - - ); - } else { - buttonContent = ( - - ); - } - } - - const largeWindow = 'large' === windowWidth; - const smallWindow = 'small' === windowWidth; - - let infoContent; - if ( info ) { - infoContent =

{ info }

; - } - - // Note: if any more types of text links get added to this component then refactor these - // into a single set of textLink, isTextLinkExternal, and textLinkDisplayText props. - let textLinkContent; - if ( optionsLink ) { - const externalLinkProps = isOptionsLinkExternal - ? { target: '_blank', rel: 'noopener noreferrer' } - : {}; - - textLinkContent = ( - - { __( 'View options', 'jetpack' ) } - { isOptionsLinkExternal && } - - ); - } else if ( learnMoreLink ) { - const externalLinkProps = isLearnMoreLinkExternal - ? { target: '_blank', rel: 'noopener noreferrer' } - : {}; - - textLinkContent = ( - - { __( 'Learn more', 'jetpack' ) } - { isLearnMoreLinkExternal && } - - ); - } - - const formToggle = ; - - const isDisabled = !! ( props.isDisabled || upgradeLink ); - - return ( -
-
- - { isDisabled ? { formToggle } : formToggle } -
- { smallWindow && ( -
-

{ title }

-
- ) } -
-

- { largeWindow && { title } } - { details } - { textLinkContent && { textLinkContent } } -

-
- { ( buttonContent || infoContent ) && ( -
- { buttonContent } - { infoContent } -
- ) } -
- ); -}; - -FeatureToggle.propTypes = { - feature: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - details: PropTypes.string.isRequired, - info: PropTypes.string, - checked: PropTypes.bool.isRequired, - onToggleChange: PropTypes.func, - onInstallClick: PropTypes.func, - configureLink: PropTypes.string, - upgradeLink: PropTypes.string, - optionsLink: PropTypes.string, - learnMoreLink: PropTypes.string, - isPaid: PropTypes.bool, - isDisabled: PropTypes.bool, - isButtonLinkExternal: PropTypes.bool, - isOptionsLinkExternal: PropTypes.bool, - isLearnMoreLinkExternal: PropTypes.bool, -}; - -FeatureToggle = connect( - ( state, ownProps ) => mapStateToFeatureToggleProps( state, ownProps.feature ), - ( dispatch, ownProps ) => mapDispatchToFeatureToggleProps( dispatch, ownProps.feature ) -)( FeatureToggle ); - -export { FeatureToggle }; diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/map-feature-to-props.js b/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/map-feature-to-props.js deleted file mode 100644 index 906afb9fd8244..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/map-feature-to-props.js +++ /dev/null @@ -1,1171 +0,0 @@ -/** - * External dependencies - */ -import { __, sprintf } from '@wordpress/i18n'; -import { get, rest } from 'lodash'; - -/** - * Internal dependencies - */ -import getRedirectUrl from 'lib/jp-redirect'; -import { getPlanClass } from 'lib/plans/constants'; -import restApi from 'rest-api'; -import { getVaultPressData, isAkismetKeyValid } from 'state/at-a-glance'; -import { getSiteRawUrl, getSiteAdminUrl } from 'state/initial-state'; -import { getRewindStatus } from 'state/rewind'; -import { getSetting, updateSettings } from 'state/settings'; -import { - getActiveBackupPurchase, - getActiveScanPurchase, - getSitePlan, - hasActiveBackupPurchase, - hasActiveScanPurchase, - hasActiveSearchPurchase, -} from 'state/site'; -import { fetchPluginsData, isPluginActive } from 'state/site/plugins'; - -function getInfoString( productName ) { - return sprintf( - /* translators: placeholder is a product name, such as Jetpack Backups. */ - __( 'Included with %s', 'jetpack' ), - productName - ); -} - -const features = { - ads: { - mapStateToProps: state => { - const sitePlan = getSitePlan( state ); - const planClass = getPlanClass( sitePlan.product_slug ); - const siteRawUrl = getSiteRawUrl( state ); - - const inCurrentPlan = [ 'is-premium-plan', 'is-business-plan' ].includes( planClass ); - - let upgradeLink; - if ( ! inCurrentPlan ) { - upgradeLink = getRedirectUrl( 'jetpack-setup-wizard-ads-upgrade', { - site: siteRawUrl, - } ); - } - - let info; - let configureLink; - if ( inCurrentPlan ) { - info = getInfoString( sitePlan.product_name ); - configureLink = '#/settings?term=wordads'; - } - - return { - feature: 'ads', - title: __( 'Ads', 'jetpack' ), - details: __( 'Generate income with high-quality ads.', 'jetpack' ), - checked: getSetting( state, 'wordads' ), - isPaid: true, - configureLink, - upgradeLink, - info, - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { wordads: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'anti-spam': { - mapStateToProps: state => { - const sitePlan = getSitePlan( state ); - const planClass = getPlanClass( sitePlan.product_slug ); - const siteRawUrl = getSiteRawUrl( state ); - - const inCurrentPlan = [ 'is-personal-plan', 'is-premium-plan', 'is-business-plan' ].includes( - planClass - ); - - let optionsLink; - let isOptionsLinkExternal = false; - if ( inCurrentPlan ) { - optionsLink = getRedirectUrl( 'jetpack-setup-wizard-anti-spam-get-started', { - site: siteRawUrl, - } ); - isOptionsLinkExternal = true; - } - - let upgradeLink; - if ( ! inCurrentPlan ) { - upgradeLink = getRedirectUrl( 'jetpack-setup-wizard-antispam-upgrade', { - site: siteRawUrl, - } ); - } - - let info; - if ( inCurrentPlan ) { - info = getInfoString( sitePlan.product_name ); - } - - return { - feature: 'anti-spam', - title: __( 'Anti-spam', 'jetpack' ), - details: __( 'No more approving or vetting.', 'jetpack' ), - checked: true === isAkismetKeyValid( state ), - isDisabled: inCurrentPlan, - isPaid: true, - optionsLink, - isOptionsLinkExternal, - upgradeLink, - info, - }; - }, - }, - - backups: { - mapStateToProps: state => { - const sitePlan = getSitePlan( state ); - const planClass = getPlanClass( sitePlan.product_slug ); - const siteRawUrl = getSiteRawUrl( state ); - const isBackupsPurchased = - hasActiveBackupPurchase( state ) || - [ - 'is-personal-plan', - 'is-premium-plan', - 'is-business-plan', - 'is-daily-backup-plan', - 'is-realtime-backup-plan', - ].includes( planClass ); - - let optionsLink; - if ( isBackupsPurchased ) { - optionsLink = '#/settings?term=backup'; - } - - let upgradeLink; - if ( ! isBackupsPurchased ) { - upgradeLink = getRedirectUrl( 'jetpack-setup-wizard-backups-upgrade', { - site: siteRawUrl, - } ); - } - - let info; - if ( isBackupsPurchased ) { - const backupsPurchase = getActiveBackupPurchase( state ); - const productName = backupsPurchase ? backupsPurchase.product_name : sitePlan.product_name; - info = getInfoString( productName ); - } - - return { - feature: 'backups', - title: __( 'Daily or Real-time backups', 'jetpack' ), - details: __( 'Get time travel for your site with Jetpack Backup.', 'jetpack' ), - checked: isBackupsPurchased, - isDisabled: isBackupsPurchased, - optionsLink, - upgradeLink, - info, - isPaid: true, - }; - }, - }, - - 'beautiful-math': { - mapStateToProps: state => { - return { - feature: 'beautiful-math', - title: __( 'Beautiful math', 'jetpack' ), - details: __( 'Display math and formulas beautifully.', 'jetpack' ), - checked: getSetting( state, 'latex' ), - optionsLink: '#/settings?term=latex', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { latex: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'brute-force-protect': { - mapStateToProps: state => { - return { - feature: 'brute-force-protect', - title: __( 'Brute force protection', 'jetpack' ), - details: __( 'Stop malicious login attempts.', 'jetpack' ), - checked: getSetting( state, 'protect' ), - optionsLink: '#/settings?term=brute%20force', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { protect: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - carousel: { - mapStateToProps: state => { - return { - feature: 'carousel', - title: __( 'Carousel', 'jetpack' ), - details: __( - 'Create full-screen carousel slideshows for the images in your posts and pages.', - 'jetpack' - ), - checked: getSetting( state, 'carousel' ), - optionsLink: '#/settings?term=carousel', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { carousel: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'comment-likes': { - mapStateToProps: state => { - return { - feature: 'comment-likes', - title: __( 'Comment Likes', 'jetpack' ), - details: __( 'Increase engagement with liking on comments.', 'jetpack' ), - checked: getSetting( state, 'comment-likes' ), - optionsLink: '#/settings?term=comment%20likes', - }; - }, - - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'comment-likes': ! currentCheckedValue } ) ); - }, - }; - }, - }, - - comments: { - mapStateToProps: state => { - return { - feature: 'comments', - title: __( 'Comments', 'jetpack' ), - details: __( 'An enhanced comments section with better verfiication.', 'jetpack' ), - checked: getSetting( state, 'comments' ), - optionsLink: '#/settings?term=comments', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { comments: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'contact-form': { - mapStateToProps: state => { - return { - feature: 'contact-form', - title: __( 'Contact Form', 'jetpack' ), - details: __( 'Add contact forms using the block editor.', 'jetpack' ), - checked: getSetting( state, 'contact-form' ), - optionsLink: '#/settings?term=contact%20form', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'contact-form': ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'copy-post': { - mapStateToProps: state => { - return { - feature: 'copy-post', - title: __( 'Copy Post', 'jetpack' ), - details: __( 'Simply duplicate content.', 'jetpack' ), - checked: getSetting( state, 'copy-post' ), - optionsLink: '#/settings?term=copy%20post', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'copy-post': ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'creative-mail': { - mapStateToProps: state => { - const isCreativeMailActive = isPluginActive( - state, - 'creative-mail-by-constant-contact/creative-mail-plugin.php' - ); - const siteAdminUrl = getSiteAdminUrl( state ); - const creativeMailConfigureLink = siteAdminUrl + 'admin.php?page=creativemail'; - - return { - feature: 'creative-mail', - title: __( 'Creative Mail by Constant Contact', 'jetpack' ), - details: __( 'Turn visitors into subscribers with email marketing.', 'jetpack' ), - checked: isCreativeMailActive, - isDisabled: true, - isPaid: true, - configureLink: isCreativeMailActive ? creativeMailConfigureLink : null, - learnMoreLink: - 'https://jetpack.com/support/jetpack-blocks/form-block/newsletter-sign-up-form/', - isLearnMoreLinkExternal: true, - }; - }, - mapDispatchToProps: dispatch => { - const installAndRefreshPluginData = () => { - return restApi - .installPlugin( 'creative-mail-by-constant-contact', 'setup-wizard' ) - .then( () => { - dispatch( fetchPluginsData() ); - } ); - }; - - return { - onInstallClick: () => installAndRefreshPluginData(), - }; - }, - }, - - 'custom-css': { - mapStateToProps: state => { - return { - feature: 'custom-css', - title: __( 'Custom CSS', 'jetpack' ), - details: __( 'Enable an enhanced CSS customization panel.', 'jetpack' ), - checked: getSetting( state, 'custom-css' ), - optionsLink: '#/settings?term=custom%20css', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'custom-css': ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'enhanced-distribution': { - mapStateToProps: state => { - return { - feature: 'enhanced-distribution', - title: __( 'Enhanced Distribution', 'jetpack' ), - details: __( 'Increase reach and traffic.', 'jetpack' ), - checked: getSetting( state, 'enhanced-distribution' ), - optionsLink: '#/traffic?term=enhanced', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'enhanced-distribution': ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'extra-sidebar-widgets': { - mapStateToProps: state => { - return { - feature: 'extra-sidebar-widgets', - title: __( 'Extra Sidebar Widgets', 'jetpack' ), - details: __( 'Add more widgets.', 'jetpack' ), - checked: getSetting( state, 'widgets' ), - optionsLink: '#/traffic?term=extra', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { widgets: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'google-analytics': { - mapStateToProps: state => { - const sitePlan = getSitePlan( state ); - const planClass = getPlanClass( sitePlan.product_slug ); - const siteRawUrl = getSiteRawUrl( state ); - - const inCurrentPlan = [ 'is-premium-plan', 'is-business-plan' ].includes( planClass ); - - let upgradeLink; - if ( ! inCurrentPlan ) { - upgradeLink = getRedirectUrl( 'jetpack-setup-wizard-google-analytics-upgrade', { - site: siteRawUrl, - } ); - } - - let info; - let configureLink; - if ( inCurrentPlan ) { - info = getInfoString( sitePlan.product_name ); - configureLink = '#/settings?term=google%20analytics'; - } - - return { - feature: 'google-analytics', - title: __( 'Google Analytics', 'jetpack' ), - details: __( 'Add your Google Analytics account.', 'jetpack' ), - checked: getSetting( state, 'google-analytics' ), - isPaid: true, - configureLink, - upgradeLink, - info, - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'google-analytics': ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'gravatar-hovercards': { - mapStateToProps: state => { - return { - feature: 'gravatar-hovercards', - title: __( 'Gravatar Hovercards', 'jetpack' ), - details: __( 'Give comments life.', 'jetpack' ), - checked: getSetting( state, 'gravatar-hovercards' ), - optionsLink: '#/traffic?term=hovercards', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'gravatar-hovercards': ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'infinite-scroll': { - mapStateToProps: state => { - return { - feature: 'infinite-scroll', - title: __( 'Infinite Scroll', 'jetpack' ), - details: __( - 'Create a smooth, uninterrupted reading experience by loading more content as visitors scroll to the bottom of your archive pages.', - 'jetpack' - ), - checked: !! getSetting( state, 'infinite-scroll' ), - optionsLink: '#/settings?term=infinite', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - if ( currentCheckedValue ) { - return dispatch( updateSettings( { 'infinite-scroll': false } ) ); - } - return dispatch( updateSettings( { 'infinite-scroll': true, infinite_scroll: true } ) ); - }, - }; - }, - }, - - 'json-api': { - mapStateToProps: state => { - return { - feature: 'json-api', - title: __( 'JSON API', 'jetpack' ), - details: __( 'JSON API access for developers.', 'jetpack' ), - checked: getSetting( state, 'json-api' ), - optionsLink: '/wp-admin/admin.php?page=jetpack#/traffic?term=json', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'json-api': ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'lazy-images': { - mapStateToProps: state => { - return { - feature: 'lazy-images', - title: __( 'Lazy Loading Images', 'jetpack' ), - details: __( 'Further improve site speed and only load images visitors need.', 'jetpack' ), - checked: getSetting( state, 'lazy-images' ), - optionsLink: '#/settings?term=lazy', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'lazy-images': ! currentCheckedValue } ) ); - }, - }; - }, - }, - - likes: { - mapStateToProps: state => { - return { - feature: 'likes', - title: __( 'Likes', 'jetpack' ), - details: __( 'Add a like button to your posts.', 'jetpack' ), - checked: getSetting( state, 'likes' ), - optionsLink: '#/settings?term=likes', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { likes: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - markdown: { - mapStateToProps: state => { - return { - feature: 'markdown', - title: __( 'Markdown', 'jetpack' ), - details: __( 'Write faster rich-text.', 'jetpack' ), - checked: getSetting( state, 'markdown' ), - optionsLink: '#/traffic?term=markdown', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { markdown: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - masterbar: { - mapStateToProps: state => { - return { - feature: 'masterbar', - title: __( 'WordPress.com Toolbar', 'jetpack' ), - details: __( - 'The WordPress.com toolbar replaces the default WordPress admin toolbar.', - 'jetpack' - ), - checked: getSetting( state, 'masterbar' ), - optionsLink: '#/settings?term=toolbar', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { masterbar: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - monitor: { - mapStateToProps: state => { - return { - feature: 'monitor', - title: __( 'Downtime Monitoring', 'jetpack' ), - details: __( 'Get an alert immediately if your site goes down.', 'jetpack' ), - checked: getSetting( state, 'monitor' ), - optionsLink: '#/settings?term=monitor', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { monitor: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - notifications: { - mapStateToProps: state => { - return { - feature: 'notifications', - title: __( 'Notifications', 'jetpack' ), - details: __( 'Stay up-to-date with your site.', 'jetpack' ), - checked: getSetting( state, 'notes' ), - optionsLink: '#/settings?term=push', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { notes: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - portfolio: { - mapStateToProps: state => { - return { - feature: 'portfolio', - title: __( 'Portfolio: Custom content types', 'jetpack' ), - details: __( 'Use portfolios on your site to showcase your best work.', 'jetpack' ), - checked: getSetting( state, 'jetpack_portfolio' ), - optionsLink: '/wp-admin/admin.php?page=jetpack#/settings?term=portfolios', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( ( dispatchProp, getState ) => { - const jetpack_portfolio = ! currentCheckedValue; - const jetpack_testimonial = getSetting( getState(), 'jetpack_testimonial' ); - const customContentTypes = jetpack_portfolio || jetpack_testimonial; - return dispatchProp( - updateSettings( { - jetpack_portfolio, - 'custom-content-types': customContentTypes, - } ) - ); - } ); - }, - }; - }, - }, - - 'post-by-email': { - mapStateToProps: state => { - return { - feature: 'post-by-email', - title: __( 'Post by email', 'jetpack' ), - details: __( - 'Post by email is a quick way to publish new posts without visiting your site.', - 'jetpack' - ), - checked: getSetting( state, 'post-by-email' ), - configureLink: '#/traffic?term=post%20by%20email', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'post-by-email': ! currentCheckedValue } ) ); - }, - }; - }, - }, - - publicize: { - mapStateToProps: state => { - const siteRawUrl = getSiteRawUrl( state ); - - return { - feature: 'publicize', - title: __( 'Publicize', 'jetpack' ), - details: __( - 'Automaticaly share content on your favorite social media accounts.', - 'jetpack' - ), - checked: getSetting( state, 'publicize' ), - configureLink: getRedirectUrl( 'calypso-marketing-connections', { site: siteRawUrl } ), - isButtonLinkExternal: true, - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { publicize: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'related-posts': { - mapStateToProps: state => { - return { - feature: 'related-posts', - title: __( 'Related posts', 'jetpack' ), - details: __( - 'Keep your visitors engaged with related content at the bottom of each post.', - 'jetpack' - ), - checked: getSetting( state, 'related-posts' ), - optionsLink: '#/settings?term=related%20posts', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'related-posts': ! currentCheckedValue } ) ); - }, - }; - }, - }, - - scan: { - mapStateToProps: state => { - const sitePlan = getSitePlan( state ); - const planClass = getPlanClass( sitePlan.product_slug ); - const siteRawUrl = getSiteRawUrl( state ); - const isScanPurchased = - hasActiveScanPurchase( state ) || - [ 'is-premium-plan', 'is-business-plan', 'is-scan-plan' ].includes( planClass ); - - let optionsLink; - if ( isScanPurchased ) { - optionsLink = '#/settings?term=scan'; - } - - let upgradeLink; - if ( ! isScanPurchased ) { - upgradeLink = getRedirectUrl( 'jetpack-setup-wizard-scan-upgrade', { - site: siteRawUrl, - } ); - } - - let info; - if ( isScanPurchased ) { - const scanPurchase = getActiveScanPurchase( state ); - const productName = scanPurchase ? scanPurchase.product_name : sitePlan.product_name; - info = getInfoString( productName ); - } - - return { - feature: 'scan', - title: __( 'Security scanning', 'jetpack' ), - details: __( 'Stop threats to keep your website safe.', 'jetpack' ), - checked: isScanPurchased, - isDisabled: isScanPurchased, - isPaid: true, - optionsLink, - upgradeLink, - info, - }; - }, - }, - - search: { - mapStateToProps: state => { - let upgradeLink; - let optionsLink; - const sitePlan = getSitePlan( state ); - const siteRawUrl = getSiteRawUrl( state ); - if ( - 'is-business-plan' !== getPlanClass( sitePlan.product_slug ) && - ! hasActiveSearchPurchase( state ) - ) { - upgradeLink = getRedirectUrl( 'jetpack-setup-wizard-search-upgrade', { - site: siteRawUrl, - } ); - } else { - optionsLink = '#/settings?term=search'; - } - - return { - feature: 'search', - title: __( 'Search', 'jetpack' ), - details: __( - 'Incredibly powerful and customizable, Jetpack Search helps your visitors instantly find the right content – right when they need it.', - 'jetpack' - ), - checked: getSetting( state, 'search' ), - isPaid: true, - upgradeLink, - optionsLink, - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { search: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - sso: { - mapStateToProps: state => { - return { - feature: 'sso', - title: __( 'Secure Sign On', 'jetpack' ), - details: __( 'Add an extra layer of security.', 'jetpack' ), - checked: getSetting( state, 'sso' ), - optionsLink: '#/settings?term=secure%20sign%20on', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { sso: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - seo: { - mapStateToProps: state => { - return { - feature: 'seo', - title: __( 'SEO', 'jetpack' ), - details: __( 'Take control of the way search engines represent your site.', 'jetpack' ), - checked: getSetting( state, 'seo-tools' ), - configureLink: '#/settings?term=seo', - isPaid: false, - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'seo-tools': ! currentCheckedValue } ) ); - }, - }; - }, - }, - - sitemaps: { - mapStateToProps: state => { - return { - feature: 'sitemaps', - title: __( 'Sitemaps', 'jetpack' ), - details: __( 'Automatically generate sitemaps for all your content.', 'jetpack' ), - checked: getSetting( state, 'sitemaps' ), - optionsLink: '#/settings?term=sitemaps', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { sitemaps: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - sharing: { - mapStateToProps: state => { - return { - feature: 'sharing', - title: __( 'Sharing', 'jetpack' ), - details: __( 'Increase sharing of your posts and pages.', 'jetpack' ), - checked: getSetting( state, 'sharedaddy' ), - optionsLink: '#/settings?term=sharedaddy', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { sharedaddy: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - shortcodes: { - mapStateToProps: state => { - return { - feature: 'shortcodes', - title: __( 'Shortcode Embeds', 'jetpack' ), - details: __( 'Embed YouTube videos, and other content easily.', 'jetpack' ), - checked: getSetting( state, 'shortcodes' ), - optionsLink: '#/traffic?term=embeds', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { shortcodes: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - shortlinks: { - mapStateToProps: state => { - return { - feature: 'shortlinks', - title: __( 'WP.me Shortlinks', 'jetpack' ), - details: __( 'Build quick links for sharing.', 'jetpack' ), - checked: getSetting( state, 'shortlinks' ), - optionsLink: '#/settings?term=shortlink', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { shortlinks: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'simple-payments-block': { - mapStateToProps: state => { - const sitePlan = getSitePlan( state ); - const planClass = getPlanClass( sitePlan.product_slug ); - const siteRawUrl = getSiteRawUrl( state ); - - const inCurrentPlan = [ 'is-premium-plan', 'is-business-plan' ].includes( planClass ); - - let upgradeLink; - if ( ! inCurrentPlan ) { - upgradeLink = getRedirectUrl( 'jetpack-setup-wizard-simple-payments-block-upgrade', { - site: siteRawUrl, - } ); - } - - let info; - let configureLink; - let isButtonLinkExternal = false; - if ( inCurrentPlan ) { - info = getInfoString( sitePlan.product_name ); - configureLink = getRedirectUrl( 'jetpack-setup-wizard-simple-payments-support', { - site: siteRawUrl, - } ); - isButtonLinkExternal = true; - } - - return { - feature: 'simple-payments-block', - title: __( 'Pay with PayPal', 'jetpack' ), - details: __( 'A simple way to accept payments.', 'jetpack' ), - checked: inCurrentPlan, - isDisabled: inCurrentPlan, - isPaid: true, - configureLink, - upgradeLink, - info, - isButtonLinkExternal, - }; - }, - }, - - 'site-accelerator': { - mapStateToProps: state => { - return { - feature: 'site-accelerator', - title: __( 'Site Accelerator', 'jetpack' ), - details: __( 'Enable for faster images and a faster site.', 'jetpack' ), - checked: getSetting( state, 'photon' ) && getSetting( state, 'photon-cdn' ), - optionsLink: '#/settings?term=image%20optimize', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( - updateSettings( { photon: ! currentCheckedValue, 'photon-cdn': ! currentCheckedValue } ) - ); - }, - }; - }, - }, - - 'site-stats': { - mapStateToProps: state => { - return { - feature: 'site-stats', - title: __( 'Site Stats', 'jetpack' ), - details: __( - 'Track your site visitors and learn about your most popular content.', - 'jetpack' - ), - checked: getSetting( state, 'stats' ), - optionsLink: '#/settings?term=site%20stats', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { stats: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'site-verification': { - mapStateToProps: state => { - return { - feature: 'site-verification', - title: __( 'Site verification', 'jetpack' ), - details: __( 'Verify your site with Google, Bing, Yandex, and Pinterest.', 'jetpack' ), - checked: getSetting( state, 'verification-tools' ), - configureLink: '#/settings?term=verify', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'verification-tools': ! currentCheckedValue } ) ); - }, - }; - }, - }, - - subscriptions: { - mapStateToProps: state => { - return { - feature: 'subscriptions', - title: __( 'Subscriptions', 'jetpack' ), - details: __( 'Send post notifications to your visitors.', 'jetpack' ), - checked: getSetting( state, 'subscriptions' ), - optionsLink: '#/settings?term=subscriptions', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { subscriptions: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - testimonials: { - mapStateToProps: state => { - return { - feature: 'testimonials', - title: __( 'Testimonial: Custom content types', 'jetpack' ), - details: __( 'Add testimonials to your website to attract new customers.', 'jetpack' ), - checked: getSetting( state, 'jetpack_testimonial' ), - optionsLink: '#/settings?term=testimonials', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( ( dispatchProp, getState ) => { - const jetpack_portfolio = getSetting( getState(), 'jetpack_portfolio' ); - const jetpack_testimonial = ! currentCheckedValue; - const customContentTypes = jetpack_portfolio || jetpack_testimonial; - - return dispatchProp( - updateSettings( { - jetpack_testimonial, - 'custom-content-types': customContentTypes, - } ) - ); - } ); - }, - }; - }, - }, - - 'tiled-galleries': { - mapStateToProps: state => { - return { - feature: 'tiled-galleries', - title: __( 'Tiled Galleries', 'jetpack' ), - details: __( 'Add beautifully laid out galleries using the block editor.', 'jetpack' ), - checked: getSetting( state, 'tiled-gallery' ), - optionsLink: 'https://jetpack.com/support/jetpack-blocks/tiled-gallery-block/', - isOptionsLinkExternal: true, - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'tiled-gallery': ! currentCheckedValue } ) ); - }, - }; - }, - }, - - videopress: { - mapStateToProps: state => { - const sitePlan = getSitePlan( state ); - const planClass = getPlanClass( sitePlan.product_slug ); - const siteRawUrl = getSiteRawUrl( state ); - - const inCurrentPlan = [ 'is-premium-plan', 'is-business-plan' ].includes( planClass ); - - let upgradeLink; - if ( ! inCurrentPlan ) { - upgradeLink = getRedirectUrl( 'jetpack-setup-wizard-videopress-upgrade', { - site: siteRawUrl, - } ); - } - - let info; - let optionsLink; - if ( inCurrentPlan ) { - info = getInfoString( sitePlan.product_name ); - optionsLink = '#/settings?term=video%20player'; - } - - return { - feature: 'videopress', - title: __( 'VideoPress', 'jetpack' ), - details: __( 'Host fast, high-quality, ad-free video.', 'jetpack' ), - checked: getSetting( state, 'videopress' ), - isPaid: true, - optionsLink, - upgradeLink, - info, - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { videopress: ! currentCheckedValue } ) ); - }, - }; - }, - }, - - 'widget-visibility': { - mapStateToProps: state => { - return { - feature: 'widget-visibility', - title: __( 'Widget Visibility', 'jetpack' ), - details: __( 'Control your widgets at the post or page level.', 'jetpack' ), - checked: getSetting( state, 'widget-visibility' ), - optionsLink: '#/settings?term=visibility', - }; - }, - mapDispatchToProps: dispatch => { - return { - onToggleChange: currentCheckedValue => { - return dispatch( updateSettings( { 'widget-visibility': ! currentCheckedValue } ) ); - }, - }; - }, - }, -}; - -export const mapStateToFeatureToggleProps = ( state, feature ) => { - if ( ! Object.keys( features ).includes( feature ) ) { - throw `Feature not found: ${ feature }`; - } - - const mapStateToProps = features[ feature ].mapStateToProps; - - return 'function' === typeof features[ feature ].mapStateToProps ? mapStateToProps( state ) : {}; -}; - -export const mapDispatchToFeatureToggleProps = ( dispatch, feature ) => { - if ( ! Object.keys( features ).includes( feature ) ) { - throw `Feature not found: ${ feature }`; - } - - const mapDispatchToProps = features[ feature ].mapDispatchToProps; - - return 'function' === typeof mapDispatchToProps ? mapDispatchToProps( dispatch ) : {}; -}; diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/style.scss b/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/style.scss deleted file mode 100644 index f611251512eec..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/style.scss +++ /dev/null @@ -1,175 +0,0 @@ -@import '../../scss/mixin_breakpoint'; -@import '../../scss/calypso-colors'; -@import '../recommended-features/variables'; - -.jp-setup-wizard-feature-toggle { - border: 1px solid #ccd0d4; - - display: grid; - grid-template-rows: 100px; - grid-template-columns: 100px auto auto; - - @include breakpoint( $greater-than-vertical-break ) { - border-radius: 4px; - - &.jp-setup-wizard-fixed-right-column { - grid-template-columns: 100px auto 240px; - } - } - - @include breakpoint( $less-than-vertical-break ) { - grid-template-rows: 65px auto auto; - grid-template-columns: 90px auto; - - border-width: 1px 0; - } - - .dops-button { - width: 150px; - - @include breakpoint( $less-than-vertical-break ) { - width: 100%; - margin-bottom: 1rem; - } - - .gridicon { - margin-left: 0.25rem; - } - } -} - -.jp-setup-wizard-form-toggle-container { - display: flex; - align-items: center; - justify-content: center; - - padding: 0 2rem; - @include breakpoint( $less-than-vertical-break ) { - padding: 0 1rem; - } - - position: relative; - - .gridicons-star { - display: none; - } - - &.is-paid-feature { - &::before { - content: ''; - width: 0px; - height: 0px; - border-top: 35px solid #4ab866; - border-right: 35px solid transparent; - position: absolute; - top: 0; - left: 0; - } - - .gridicons-star { - display: block; - color: white; - position: absolute; - top: 0; - left: 0; - width: 18px; - height: 18px; - } - } -} - -.jp-setup-wizard-feature-toggle-content-container { - display: flex; - align-items: center; - justify-content: left; - padding-right: 1rem; - - @include breakpoint( $less-than-vertical-break ) { - grid-row: 2; - grid-column-start: 1; - grid-column-end: 3; - - padding-left: 30px; - padding-right: 30px; - } -} - -.jp-setup-wizard-feature-toggle-content { - text-align: left; - - @include breakpoint( $greater-than-vertical-break ) { - font-size: 15px; - - span { - font-weight: bold; - margin-right: 0.5rem; - } - } - - @include breakpoint( $less-than-vertical-break ) { - margin: 0 0 1rem 0; - } -} - -.jp-setup-wizard-feature-toggle-button-container { - display: flex; - flex-direction: column; - align-items: flex-end; - justify-content: center; - - padding-right: 1rem; - padding-top: 1rem; - padding-bottom: 1rem; - - p { - font-style: italic; - color: lighten( $gray, 10% ); - - @include breakpoint( $greater-than-vertical-break ) { - text-align: right; - } - } - @include breakpoint( $less-than-vertical-break ) { - padding: 0 30px; - - grid-row: 3; - grid-column-start: 1; - grid-column-end: 3; - } -} - -.jp-setup-wizard-form-toggle-title-small { - @include breakpoint( $less-than-vertical-break ) { - grid-row: 1; - grid-column: 2; - - display: flex; - justify-content: left; - align-items: center; - - p { - font-weight: bold; - } - } -} - -.jp-setup-wizard-feature-toggle-info { - margin: 0.5rem 0; - - @include breakpoint( $less-than-vertical-break ) { - align-self: flex-start; - margin: 0 0 1rem 0; - } -} - -.jp-setup-wizard-view-options-link { - margin-left: 0.5rem; - color: black; - font-weight: bold; - text-decoration: underline !important; -} - -.jp-setup-wizard-install-spinner-container { - display: flex; - justify-content: center; -} diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/income-question/index.jsx b/projects/plugins/jetpack/_inc/client/setup-wizard/income-question/index.jsx deleted file mode 100644 index d39f65be8d3d6..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/income-question/index.jsx +++ /dev/null @@ -1,155 +0,0 @@ -/** - * External dependencies - */ -import React, { useCallback, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { useLocation } from 'react-router-dom'; -import { __, sprintf } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { ChecklistAnswer } from '../checklist-answer'; -import Button from 'components/button'; -import { imagePath } from 'constants/urls'; -import analytics from 'lib/analytics'; -import { - getSetupWizardAnswer, - saveSetupWizardQuestionnnaire, - updateSetupWizardQuestionnaire, - updateSetupWizardStatus, -} from 'state/setup-wizard'; - -import './style.scss'; - -let IncomeQuestion = props => { - const location = useLocation(); - - useEffect( () => { - props.updateStatus( 'income-page' ); - analytics.tracks.recordEvent( 'jetpack_wizard_page_view', { step: 'income-page' } ); - - const queryParams = new URLSearchParams( location.search ); - const useAnswer = queryParams.get( 'use' ); - - if ( [ 'personal', 'business' ].includes( useAnswer ) ) { - props.updateSiteUseQuestion( { use: useAnswer } ); - props.saveQuestionnaire(); - analytics.tracks.recordEvent( 'jetpack_wizard_question_answered', { - question: 'use', - answer: useAnswer, - } ); - } - }, [ location ] ); - - const onContinueClick = useCallback( () => { - props.saveQuestionnaire(); - for ( const answer in props.answers ) { - if ( props.answers[ answer ] ) { - analytics.tracks.recordEvent( 'jetpack_wizard_question_answered', { - question: 'income', - answer: answer, - } ); - } - } - }, [ props.answers ] ); - - const onNoneApplyClick = useCallback( () => { - analytics.tracks.recordEvent( 'jetpack_setup_wizard_question_skipped', { - question: 'income', - } ); - } ); - - return ( -
- { -

- { sprintf( - /* translators: placeholder is the site title. */ - __( 'Do you intend to make money directly from %s?', 'jetpack' ), - props.siteTitle - ) } -

-

{ __( 'Check all that apply', 'jetpack' ) }

-
- - - - -
-
- - - { __( 'None of these apply', 'jetpack' ) } - -
-
- ); -}; - -IncomeQuestion.propTypes = { - siteTitle: PropTypes.string.isRequired, -}; - -IncomeQuestion = connect( - state => ( { - answers: [ - 'advertising-revenue', - 'store-revenue', - 'appointments-revenue', - 'location-revenue', - ].reduce( ( acc, curr ) => ( { ...acc, [ curr ]: getSetupWizardAnswer( state, curr ) } ), {} ), - } ), - dispatch => ( { - saveQuestionnaire: () => dispatch( saveSetupWizardQuestionnnaire() ), - updateSiteUseQuestion: answer => dispatch( updateSetupWizardQuestionnaire( answer ) ), - updateStatus: status => dispatch( updateSetupWizardStatus( status ) ), - } ) -)( IncomeQuestion ); - -export { IncomeQuestion }; diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/income-question/style.scss b/projects/plugins/jetpack/_inc/client/setup-wizard/income-question/style.scss deleted file mode 100644 index 54c57174d438a..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/income-question/style.scss +++ /dev/null @@ -1,38 +0,0 @@ -@import '../../scss/mixin_breakpoint'; - -.jp-setup-wizard-subtitle { - font-weight: 600; - font-size: 1.33rem; - margin: 0 1rem 2rem 1rem; - color: black; -} - -.jp-setup-wizard-income-answer-container { - @include breakpoint( '>660px' ) { - display: grid; - grid-template-rows: auto auto; - grid-template-columns: 50% 50%; - grid-gap: 1rem; - - max-width: 650px; - margin: 0 auto; - } - - @include breakpoint( '<660px' ) { - display: flex; - flex-direction: column; - - margin: 0 0 0 1rem; - } -} - -.jp-setup-wizard-advance-container { - display: flex; - flex-direction: column; - align-items: center; - margin: 2rem; - - @include breakpoint( '<660px' ) { - margin: 0 2rem 2rem 2rem; - } -} diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/index.jsx b/projects/plugins/jetpack/_inc/client/setup-wizard/index.jsx deleted file mode 100644 index 3563b301947b1..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/index.jsx +++ /dev/null @@ -1,80 +0,0 @@ -/** - * External dependencies - */ -import React from 'react'; -import { Redirect, Route, Switch } from 'react-router-dom'; -import { connect } from 'react-redux'; - -/** - * Internal dependencies - */ -import QuerySite from 'components/data/query-site'; -import QuerySitePlugins from 'components/data/query-site-plugins'; -import QueryRewindStatus from 'components/data/query-rewind-status'; -import QueryVaultPressData from 'components/data/query-vaultpress-data'; -import QueryAkismetKeyCheck from 'components/data/query-akismet-key-check'; -import QuerySetupWizardQuestionnaire from 'components/data/query-setup-wizard-questionnaire'; -import { getSiteTitle } from 'state/initial-state'; -import { getSetupWizardStatus } from 'state/setup-wizard'; - -import { IntroPage } from './intro-page'; -import { IncomeQuestion } from './income-question'; -import { UpdatesQuestion } from './updates-question'; -import { RecommendedFeatures } from './recommended-features'; - -const SetupWizardComponent = props => { - const { siteTitle, status } = props; - - let redirectPath; - switch ( status ) { - case 'not-started': - case 'intro-page': - redirectPath = '/intro'; - break; - case 'income-page': - redirectPath = '/income'; - break; - case 'updates-page': - redirectPath = '/updates'; - break; - case 'features-page': - case 'completed': - redirectPath = '/features'; - break; - default: - throw `Unknown status ${ status } in SetupWizardComponent`; - } - - return ( - <> - - - - - - - - - - - - - - - - - - - - - - - ); -}; - -export const SetupWizard = connect( state => { - return { - siteTitle: getSiteTitle( state ), - status: getSetupWizardStatus( state ), - }; -} )( SetupWizardComponent ); diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/intro-page/index.jsx b/projects/plugins/jetpack/_inc/client/setup-wizard/intro-page/index.jsx deleted file mode 100644 index 9fee80376b55d..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/intro-page/index.jsx +++ /dev/null @@ -1,124 +0,0 @@ -/** - * External dependencies - */ -import React, { useCallback, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { __, sprintf } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import Button from 'components/button'; -import { imagePath } from 'constants/urls'; -import analytics from 'lib/analytics'; -import { - saveSetupWizardQuestionnnaire, - updateSetupWizardQuestionnaire, - updateSetupWizardStatus, -} from 'state/setup-wizard'; - -import './style.scss'; - -let IntroPage = props => { - useEffect( () => { - props.updateStatus( 'intro-page' ); - analytics.tracks.recordEvent( 'jetpack_wizard_page_view', { step: 'intro-page' } ); - }, [] ); - - const onPersonalButtonClick = useCallback( () => { - props.updateSiteUseQuestion( { use: 'personal' } ); - props.saveQuestionnaire(); - analytics.tracks.recordEvent( 'jetpack_wizard_question_answered', { - question: 'use', - answer: 'personal', - } ); - }, [] ); - - const onBusinessButtonClick = useCallback( () => { - props.updateSiteUseQuestion( { use: 'business' } ); - props.saveQuestionnaire(); - analytics.tracks.recordEvent( 'jetpack_wizard_question_answered', { - question: 'use', - answer: 'business', - } ); - }, [] ); - - const onSkipLinkClick = useCallback( () => { - analytics.tracks.recordEvent( 'jetpack_setup_wizard_question_skipped', { - question: 'use', - } ); - }, [] ); - - return ( -
- { -

- { __( 'Set up Jetpack for better site security, performance, and more.', 'jetpack' ) } -

-

- { __( 'Jetpack is a cloud-powered tool built by Automattic.', 'jetpack' ) } -

-

- { __( - 'Answer a few questions and we’ll help you secure, speed up, customize, and grow your WordPress website.', - 'jetpack' - ) } -

-
-

- { sprintf( - /* translators: placeholder is the site title. */ - __( 'What will %s be used for?', 'jetpack' ), - props.siteTitle - ) } -

-
- - -
- - { __( 'Skip to recommended features', 'jetpack' ) } - -
-
- ); -}; - -IntroPage.propTypes = { - siteTitle: PropTypes.string.isRequired, -}; - -IntroPage = connect( - state => ( {} ), - dispatch => ( { - updateSiteUseQuestion: answer => dispatch( updateSetupWizardQuestionnaire( answer ) ), - saveQuestionnaire: () => dispatch( saveSetupWizardQuestionnnaire() ), - updateStatus: status => dispatch( updateSetupWizardStatus( status ) ), - } ) -)( IntroPage ); - -export { IntroPage }; diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/intro-page/style.scss b/projects/plugins/jetpack/_inc/client/setup-wizard/intro-page/style.scss deleted file mode 100644 index d3b70bbdc9537..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/intro-page/style.scss +++ /dev/null @@ -1,53 +0,0 @@ -@import '../../scss/calypso-colors'; -@import '../../scss/mixin_breakpoint'; - -.jp-setup-wizard-header { - @include breakpoint( '<660px' ) { - text-align: left; - } -} - -.jp-setup-wizard-paragraph { - font-size: 1rem; - - @include breakpoint( '>660px' ) { - margin: 0; - } - - @include breakpoint( '<660px' ) { - text-align: left; - } -} - -.jp-setup-wizard-intro-question { - box-sizing: border-box; - - @include breakpoint( '>480px' ) { - border: 1px solid $gray-lighten-20; - border-radius: 4px; - } - - max-width: 720px; - margin: 2rem auto; - padding: 1rem 0; - - h2 { - margin-top: 0px; - - @include breakpoint( '<480px' ) { - text-align: left; - } - } -} - -.jp-setup-wizard-answer-buttons { - display: flex; - - flex-direction: row; - justify-content: center; - - @include breakpoint( '<480px' ) { - flex-direction: column; - align-items: center; - } -} diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/recommended-features/index.jsx b/projects/plugins/jetpack/_inc/client/setup-wizard/recommended-features/index.jsx deleted file mode 100644 index bd6e8bdb22e20..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/recommended-features/index.jsx +++ /dev/null @@ -1,93 +0,0 @@ -/** - * External dependencies - */ -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { FeatureToggleGroup } from '../feature-toggle-group'; -import Button from 'components/button'; -import { imagePath } from 'constants/urls'; -import analytics from 'lib/analytics'; -import { fetchSettings, isFetchingSettingsList } from 'state/settings'; -import { getRecommendedFeatureGroups, updateSetupWizardStatus } from 'state/setup-wizard'; - -import './style.scss'; - -class RecommendedFeatures extends Component { - componentDidMount = () => { - if ( ! this.props.isFetchingSettingsList ) { - this.props.fetchSettings(); - } - this.props.updateStatus( 'features-page' ); - analytics.tracks.recordEvent( 'jetpack_wizard_page_view', { step: 'features-page' } ); - }; - - onDoneButtonClick = () => { - this.props.updateStatus( 'completed' ); - analytics.tracks.recordEvent( 'jetpack_wizard_features_done' ); - window.setTimeout( () => { - window.location.reload(); - }, 200 ); - }; - - onExploreMoreButtonClick = () => { - analytics.tracks.recordEvent( 'jetpack_wizard_features_explore_more' ); - }; - - render() { - return ( -
- { -

{ __( 'Get started with Jetpack’s powerful features', 'jetpack' ) }

-

- { __( - 'Jetpack has a lot of features so we’ve made a few recommendations for you below.', - 'jetpack' - ) } -

-

- { __( 'You can change your feature settings at any time.', 'jetpack' ) } -

-
- { this.props.recommendedFeatureGroups.map( featureGroup => { - return ( - - ); - } ) } -
-
- - -
-
- ); - } -} - -RecommendedFeatures = connect( - state => ( { - isFetchingSettingsList: isFetchingSettingsList( state ), - recommendedFeatureGroups: getRecommendedFeatureGroups( state ), - } ), - dispatch => ( { - fetchSettings: () => dispatch( fetchSettings() ), - updateStatus: status => dispatch( updateSetupWizardStatus( status ) ), - } ) -)( RecommendedFeatures ); - -export { RecommendedFeatures }; diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/recommended-features/style.scss b/projects/plugins/jetpack/_inc/client/setup-wizard/recommended-features/style.scss deleted file mode 100644 index dee6f18b8afc4..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/recommended-features/style.scss +++ /dev/null @@ -1,61 +0,0 @@ -@import '../../scss/mixin_breakpoint'; -@import './variables'; - -.jp-setup-wizard-main.jp-setup-wizard-recommended-features-main { - img { - width: auto; - height: 150px; - } - - @include breakpoint( $less-than-vertical-break ) { - padding: 1rem 0; - - h1 { - margin: 0.67rem 1rem 0 1rem; - } - } -} - -.jp-setup-wizard-recommended-features-subtitle { - font-size: 1rem; - margin: 1rem 4rem; -} - -.jp-setup-wizard-recommended-features-p1 { - font-size: 1rem; - margin: 1rem 4rem 0 4rem; - - @include breakpoint( $less-than-vertical-break ) { - margin: 1rem 4rem 0 1rem; - } -} - -.jp-setup-wizard-recommended-features-p2 { - font-size: 1rem; - margin: 0 4rem 4rem 4rem; - - @include breakpoint( $less-than-vertical-break ) { - margin: 1rem 4rem 0 1rem; - } -} - -.jp-setup-wizard-recommended-features-buttons-container { - display: flex; - flex-direction: row; - justify-content: center; - - @include breakpoint( $less-than-vertical-break ) { - flex-direction: column; - margin-top: 1rem; - } - - .dops-button { - width: 175px; - margin: 2rem 0.5rem 1rem 0.5rem; - - @include breakpoint( $less-than-vertical-break ) { - width: auto; - margin: 0.25rem 1rem; - } - } -} diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/recommended-features/variables.scss b/projects/plugins/jetpack/_inc/client/setup-wizard/recommended-features/variables.scss deleted file mode 100644 index e77b8cd43be97..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/recommended-features/variables.scss +++ /dev/null @@ -1,2 +0,0 @@ -$greater-than-vertical-break: '>660px'; -$less-than-vertical-break: '<660px'; diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/style.scss b/projects/plugins/jetpack/_inc/client/setup-wizard/style.scss deleted file mode 100644 index 069bef53b4785..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/style.scss +++ /dev/null @@ -1,39 +0,0 @@ -.jp-setup-wizard-main { - text-align: center; - background: $white; - padding: 1rem; - box-shadow: 0px 2px 6px rgba( 0, 0, 0, 0.03 ), 0px 1px 2px rgba( 0, 0, 0, 0.03 ); - border: 1px solid $gray-lighten-20; -} - -.jp-setup-wizard-main h1 { - font-size: 1.5rem; - font-weight: normal; - margin: 0.67rem 1rem 0 1rem; - color: black; - - @include breakpoint( '<660px' ) { - text-align: left; - } -} - -.jp-setup-wizard-main p { - @include breakpoint( '<660px' ) { - text-align: left; - } -} - -.jp-setup-wizard-button { - margin: 1rem; - width: 160px; - - @include breakpoint( '<480px' ) { - width: 100%; - margin: 0.5rem 0; - } -} - -.jp-setup-wizard-skip-link { - text-decoration: underline !important; - color: black; -} diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/updates-question/index.jsx b/projects/plugins/jetpack/_inc/client/setup-wizard/updates-question/index.jsx deleted file mode 100644 index 5c69b70662489..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/updates-question/index.jsx +++ /dev/null @@ -1,98 +0,0 @@ -/** - * External dependencies - */ -import React, { useCallback, useEffect } from 'react'; -import { connect } from 'react-redux'; -import { __, sprintf } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import Button from 'components/button'; -import { imagePath } from 'constants/urls'; -import analytics from 'lib/analytics'; -import { - saveSetupWizardQuestionnnaire, - updateSetupWizardQuestionnaire, - updateSetupWizardStatus, -} from 'state/setup-wizard'; - -import './style.scss'; - -let UpdatesQuestion = props => { - useEffect( () => { - props.updateStatus( 'updates-page' ); - analytics.tracks.recordEvent( 'jetpack_wizard_page_view', { step: 'updates-page' } ); - }, [] ); - - const onYesButtonClick = useCallback( () => { - props.updateUpdatesQuestion( { 'site-updates': true } ); - props.saveQuestionnaire(); - analytics.tracks.recordEvent( 'jetpack_wizard_question_answered', { - question: 'updates', - answer: 'yes', - } ); - }, [] ); - - const onNoButtonClick = useCallback( () => { - props.updateUpdatesQuestion( { 'site-updates': false } ); - props.saveQuestionnaire(); - analytics.tracks.recordEvent( 'jetpack_wizard_question_answered', { - question: 'updates', - answer: 'no', - } ); - }, [] ); - - const onSkipLinkClick = useCallback( () => { - analytics.tracks.recordEvent( 'jetpack_setup_wizard_question_skipped', { - question: 'updates', - } ); - }, [] ); - - return ( -
- { -

- { sprintf( - /* Translators: placeholder is the name of the site. */ - __( 'Will %s have blog posts, news, or regular updates?', 'jetpack' ), - props.siteTitle - ) } -

-
- - -
- - { __( 'Skip', 'jetpack' ) } - -
- ); -}; - -UpdatesQuestion = connect( - state => ( {} ), - dispatch => ( { - updateUpdatesQuestion: answer => dispatch( updateSetupWizardQuestionnaire( answer ) ), - saveQuestionnaire: () => dispatch( saveSetupWizardQuestionnnaire() ), - updateStatus: status => dispatch( updateSetupWizardStatus( status ) ), - } ) -)( UpdatesQuestion ); - -export { UpdatesQuestion }; diff --git a/projects/plugins/jetpack/_inc/client/setup-wizard/updates-question/style.scss b/projects/plugins/jetpack/_inc/client/setup-wizard/updates-question/style.scss deleted file mode 100644 index 7db64b10f024d..0000000000000 --- a/projects/plugins/jetpack/_inc/client/setup-wizard/updates-question/style.scss +++ /dev/null @@ -1,47 +0,0 @@ -@import '../../scss/mixin_breakpoint'; - -.jp-setup-wizard-main.jp-setup-wizard-updates-main { - padding-bottom: 20rem; - - @include breakpoint( '<480px' ) { - padding-bottom: 1rem; - } - - img { - width: 100%; - height: 200px; - - @include breakpoint( '<480px' ) { - height: auto; - } - } - - h1 { - margin: 2rem 1rem; - - @include breakpoint( '<480px' ) { - margin: 2rem 0; - } - } -} - -.jp-setup-wizard-updates-button { - margin: 0.5rem; - width: 100px; - - @include breakpoint( '<480px' ) { - width: 100%; - margin: 0.5rem 0; - } -} - -.jp-setup-wizard-updates-answer-buttons-container { - display: flex; - flex-direction: row; - justify-content: center; - - @include breakpoint( '<480px' ) { - flex-direction: column; - align-items: center; - } -} diff --git a/projects/plugins/jetpack/_inc/client/state/action-types.js b/projects/plugins/jetpack/_inc/client/state/action-types.js index 66a42865e59c1..84d8f5f1fcbfb 100644 --- a/projects/plugins/jetpack/_inc/client/state/action-types.js +++ b/projects/plugins/jetpack/_inc/client/state/action-types.js @@ -121,23 +121,6 @@ export const JETPACK_SITE_PURCHASES_FETCH = 'JETPACK_SITE_PURCHASES_FETCH'; export const JETPACK_SITE_PURCHASES_FETCH_RECEIVE = 'JETPACK_SITE_PURCHASES_FETCH_RECEIVE'; export const JETPACK_SITE_PURCHASES_FETCH_FAIL = 'JETPACK_SITE_PURCHASES_FETCH_FAIL'; -export const JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH = 'JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH'; -export const JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH_RECEIVE = - 'JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH_RECEIVE'; -export const JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH_FAIL = - 'JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH_FAIL'; -export const JETPACK_SETUP_WIZARD_QUESTIONNAIRE_UPDATE = - 'JETPACK_SETUP_WIZARD_QUESTIONNAIRE_UPDATE'; -export const JETPACK_SETUP_WIZARD_QUESTIONNAIRE_SAVE = 'JETPACK_SETUP_WIZARD_QUESTIONNAIRE_SAVE'; -export const JETPACK_SETUP_WIZARD_QUESTIONNAIRE_SAVE_SUCCESS = - 'JETPACK_SETUP_WIZARD_QUESTIONNAIRE_SAVE_SUCCESS'; -export const JETPACK_SETUP_WIZARD_QUESTIONNAIRE_SAVE_FAIL = - 'JETPACK_SETUP_WIZARD_QUESTIONNAIRE_SAVE_FAIL'; -export const JETPACK_SETUP_WIZARD_STATUS_UPDATE = 'JETPACK_SETUP_WIZARD_STATUS_UPDATE'; -export const JETPACK_SETUP_WIZARD_STATUS_UPDATE_SUCCESS = - 'JETPACK_SETUP_WIZARD_STATUS_UPDATE_SUCCESS'; -export const JETPACK_SETUP_WIZARD_STATUS_UPDATE_FAIL = 'JETPACK_SETUP_WIZARD_STATUS_UPDATE_FAIL'; - export const JETPACK_RECOMMENDATIONS_DATA_ADD_SELECTED_RECOMMENDATION = 'JETPACK_RECOMMENDATIONS_DATA_ADD_SELECTED_RECOMMENDATION'; export const JETPACK_RECOMMENDATIONS_DATA_ADD_SKIPPED_RECOMMENDATION = diff --git a/projects/plugins/jetpack/_inc/client/state/initial-state/reducer.js b/projects/plugins/jetpack/_inc/client/state/initial-state/reducer.js index 1ef27ae46d95b..356b652d76557 100644 --- a/projects/plugins/jetpack/_inc/client/state/initial-state/reducer.js +++ b/projects/plugins/jetpack/_inc/client/state/initial-state/reducer.js @@ -297,17 +297,6 @@ export function showRecommendations( state ) { return get( state.jetpack.initialState.siteData, 'showRecommendations', false ); } -/** - * Check if the Setup Wizard should be displayed - * - * @param {object} state Global state tree - * - * @return {boolean} True if the Setup Wizard should be displayed. - */ -export function showSetupWizard( state ) { - return get( state.jetpack.initialState.siteData, 'showSetupWizard', false ); -} - /** * Check if the site is part of a Multisite network. * @@ -433,17 +422,6 @@ function getProductOptions( state, product, siteProducts ) { } ); } -/** - * The status of the Setup Wizard when the application loaded. - * - * @param {*} state Global state tree - * - * @return {string} The Setup Wizard status. - */ -export function getInitialSetupWizardStatus( state ) { - return get( state.jetpack.initialState, 'setupWizardStatus', '' ); -} - /** * The current step of the Recommendations. * diff --git a/projects/plugins/jetpack/_inc/client/state/reducer.js b/projects/plugins/jetpack/_inc/client/state/reducer.js index 16f1c466200e0..652871568712f 100644 --- a/projects/plugins/jetpack/_inc/client/state/reducer.js +++ b/projects/plugins/jetpack/_inc/client/state/reducer.js @@ -23,7 +23,6 @@ import { reducer as rewind } from 'state/rewind/reducer'; import { reducer as scan } from 'state/scan/reducer'; import { reducer as search } from 'state/search/reducer'; import { reducer as settings } from 'state/settings/reducer'; -import { reducer as setupWizard } from 'state/setup-wizard/reducer'; import { reducer as siteData } from 'state/site/reducer'; import { reducer as siteProducts } from 'state/site-products/reducer'; import { reducer as siteVerify } from 'state/site-verify/reducer'; @@ -49,7 +48,6 @@ const jetpackReducer = combineReducers( { scan, search, settings, - setupWizard, siteData, siteProducts, siteVerify, diff --git a/projects/plugins/jetpack/_inc/client/state/setup-wizard/actions.js b/projects/plugins/jetpack/_inc/client/state/setup-wizard/actions.js deleted file mode 100644 index 0b377302c7620..0000000000000 --- a/projects/plugins/jetpack/_inc/client/state/setup-wizard/actions.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Internal dependencies - */ -import restApi from 'rest-api'; -import { - JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH, - JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH_RECEIVE, - JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH_FAIL, - JETPACK_SETUP_WIZARD_QUESTIONNAIRE_SAVE_SUCCESS, - JETPACK_SETUP_WIZARD_QUESTIONNAIRE_SAVE_FAIL, - JETPACK_SETUP_WIZARD_QUESTIONNAIRE_UPDATE, - JETPACK_SETUP_WIZARD_QUESTIONNAIRE_UPDATE_SUCCESS, - JETPACK_SETUP_WIZARD_QUESTIONNAIRE_UPDATE_FAIL, - JETPACK_SETUP_WIZARD_STATUS_UPDATE, - JETPACK_SETUP_WIZARD_STATUS_UPDATE_SUCCESS, - JETPACK_SETUP_WIZARD_STATUS_UPDATE_FAIL, -} from 'state/action-types'; - -export const fetchSetupWizardQuestionnaire = () => { - return dispatch => { - dispatch( { type: JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH } ); - return restApi - .fetchSetupQuestionnaire() - .then( questionnaire => { - dispatch( { type: JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH_RECEIVE, questionnaire } ); - } ) - .catch( error => { - dispatch( { type: JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH_FAIL, error } ); - } ); - }; -}; - -export const updateSetupWizardQuestionnaire = answer => { - return dispatch => { - dispatch( { type: JETPACK_SETUP_WIZARD_QUESTIONNAIRE_UPDATE, answer } ); - }; -}; - -export const saveSetupWizardQuestionnnaire = () => { - return ( dispatch, getState ) => { - dispatch( { type: JETPACK_SETUP_WIZARD_QUESTIONNAIRE_UPDATE } ); - - const setupWizard = getState().jetpack.setupWizard; - - return restApi - .saveSetupQuestionnaire( { - questionnaire: setupWizard.questionnaire, - } ) - .then( () => { - dispatch( { type: JETPACK_SETUP_WIZARD_QUESTIONNAIRE_SAVE_SUCCESS } ); - } ) - .catch( error => dispatch( { type: JETPACK_SETUP_WIZARD_QUESTIONNAIRE_SAVE_FAIL, error } ) ); - }; -}; - -export const updateSetupWizardStatus = status => { - return ( dispatch, getState ) => { - dispatch( { type: JETPACK_SETUP_WIZARD_STATUS_UPDATE, status } ); - - const setupWizard = getState().jetpack.setupWizard; - return restApi - .saveSetupQuestionnaire( { - status: setupWizard.status, - } ) - .then( () => { - dispatch( { type: JETPACK_SETUP_WIZARD_STATUS_UPDATE_SUCCESS } ); - } ) - .catch( error => dispatch( { type: JETPACK_SETUP_WIZARD_STATUS_UPDATE_FAIL, error } ) ); - }; -}; diff --git a/projects/plugins/jetpack/_inc/client/state/setup-wizard/feature-recommendations.js b/projects/plugins/jetpack/_inc/client/state/setup-wizard/feature-recommendations.js deleted file mode 100644 index 2c3f6e579cf8c..0000000000000 --- a/projects/plugins/jetpack/_inc/client/state/setup-wizard/feature-recommendations.js +++ /dev/null @@ -1,81 +0,0 @@ -// The order of feature declaration in featureGroups here determines the order they display in. -// Feaures declared earlier in the array will display earlier in the UI. -export const featureGroups = { - security: [ 'anti-spam', 'backups', 'brute-force-protect', 'monitor', 'scan', 'sso' ], - performance: [ - 'site-accelerator', - 'lazy-images', - 'search', - 'videopress', - 'site-stats', - 'infinite-scroll', - ], - marketing: [ - 'creative-mail', - 'ads', - 'comment-likes', - 'contact-form', - 'google-analytics', - 'likes', - 'notifications', - 'publicize', - 'related-posts', - 'sharing', - 'shortlinks', - 'seo', - 'simple-payments-block', - 'site-verification', - 'sitemaps', - 'subscriptions', - ], - publishing: [ - 'beautiful-math', - 'carousel', - 'comments', - 'copy-post', - 'custom-css', - 'enhanced-distribution', - 'extra-sidebar-widgets', - 'gravatar-hovercards', - 'json-api', - 'testimonials', - 'tiled-galleries', - 'markdown', - 'masterbar', - 'portfolio', - 'post-by-email', - 'shortcodes', - 'widget-visibility', - ], -}; - -export const featureRecommendations = { - all: [ - 'backups', - 'scan', - 'anti-spam', - 'brute-force-protect', - 'site-accelerator', - 'lazy-images', - 'site-stats', - 'subscriptions', - 'carousel', - 'extra-sidebar-widgets', - 'tiled-galleries', - 'creative-mail', - ], - 'business-use': [ 'monitor', 'contact-form', 'notifications' ], - 'advertising-revenue': [ 'ads' ], - 'store-revenue': [ 'search', 'videopress', 'simple-payments-block' ], - 'blog-posts': [ - 'search', - 'infinite-scroll', - 'videopress', - 'publicize', - 'related-posts', - 'sharing', - 'site-verification', - 'copy-post', - 'enhanced-distribution', - ], -}; diff --git a/projects/plugins/jetpack/_inc/client/state/setup-wizard/index.js b/projects/plugins/jetpack/_inc/client/state/setup-wizard/index.js deleted file mode 100644 index 5e3164b4c9f72..0000000000000 --- a/projects/plugins/jetpack/_inc/client/state/setup-wizard/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export * from './reducer'; -export * from './actions'; diff --git a/projects/plugins/jetpack/_inc/client/state/setup-wizard/reducer.js b/projects/plugins/jetpack/_inc/client/state/setup-wizard/reducer.js deleted file mode 100644 index e49b7f162d5f5..0000000000000 --- a/projects/plugins/jetpack/_inc/client/state/setup-wizard/reducer.js +++ /dev/null @@ -1,142 +0,0 @@ -/** - * External dependencies - */ -import { combineReducers } from 'redux'; -import { assign, get, intersection, reduce, union } from 'lodash'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { featureGroups, featureRecommendations } from './feature-recommendations'; -import { - JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH, - JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH_RECEIVE, - JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH_FAIL, - JETPACK_SETUP_WIZARD_QUESTIONNAIRE_UPDATE, - JETPACK_SETUP_WIZARD_STATUS_UPDATE, -} from 'state/action-types'; -import { getInitialSetupWizardStatus } from 'state/initial-state'; - -const questionnaire = ( state = {}, action ) => { - switch ( action.type ) { - case JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH_RECEIVE: - return assign( {}, state, action.questionnaire ); - case JETPACK_SETUP_WIZARD_QUESTIONNAIRE_UPDATE: - return assign( {}, state, action.answer ); - default: - return state; - } -}; - -const requests = ( state = {}, action ) => { - switch ( action.type ) { - case JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH: - return assign( {}, state, { isFetchingSetupQuestionnaire: true } ); - case JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH_RECEIVE: - case JETPACK_SETUP_WIZARD_QUESTIONNAIRE_FETCH_FAIL: - return assign( {}, state, { isFetchingSetupQuestionnaire: false } ); - default: - return state; - } -}; - -const status = ( state = '', action ) => { - switch ( action.type ) { - case JETPACK_SETUP_WIZARD_STATUS_UPDATE: - return action.status; - default: - return state; - } -}; - -export const reducer = combineReducers( { questionnaire, requests, status } ); - -export const isFetchingSetupWizardQuestionnaire = state => { - return !! state.jetpack.setupWizard.requests.isFetchingSetupQuestionnaire; -}; - -export const getSetupWizardAnswer = ( state, question ) => { - return get( state.jetpack.setupWizard.questionnaire, question ); -}; - -export const getRecommendedFeatureGroups = state => { - const answers = state.jetpack.setupWizard.questionnaire; - - if ( ! answers ) { - return []; - } - - const listOfRecommendedFeatures = [ featureRecommendations.all ]; - - if ( 'business' === answers.use ) { - listOfRecommendedFeatures.push( featureRecommendations[ 'business-use' ] ); - } - - if ( answers[ 'advertising-revenue' ] ) { - listOfRecommendedFeatures.push( featureRecommendations[ 'advertising-revenue' ] ); - } - - if ( answers[ 'store-revenue' ] ) { - listOfRecommendedFeatures.push( featureRecommendations[ 'store-revenue' ] ); - } - - if ( answers[ 'site-updates' ] ) { - listOfRecommendedFeatures.push( featureRecommendations[ 'blog-posts' ] ); - } - - const recommendedFeatures = reduce( - listOfRecommendedFeatures, - ( acc, curr ) => union( acc, curr ), - [] - ); - - // Note these are in a list here to guarantee order - return [ 'security', 'marketing', 'performance', 'publishing' ].map( featureGroupKey => ( { - ...getFeatureGroupContent( featureGroupKey ), - features: intersection( featureGroups[ featureGroupKey ], recommendedFeatures ), - } ) ); -}; - -function getFeatureGroupContent( featureGroupKey ) { - switch ( featureGroupKey ) { - case 'security': - return { - title: __( 'Security', 'jetpack' ), - details: __( - 'Keep your site backed up, prevent unwanted intrusions, find issues with malware scanning, and stop spammers in their tracks.', - 'jetpack' - ), - }; - case 'performance': - return { - title: __( 'Performance', 'jetpack' ), - details: __( - 'Load pages faster! Shorter load times can lead to happier readers, more page views, and — if you’re running a store — improved sales.', - 'jetpack' - ), - }; - case 'marketing': - return { - title: __( 'Marketing', 'jetpack' ), - details: __( - 'Increase visitors with social integrations, keep them engaged with related content, and so much more.', - 'jetpack' - ), - }; - case 'publishing': - return { - title: __( 'Design & Publishing', 'jetpack' ), - details: __( - 'Customize your homepage, blog posts, sidebars, and widgets — all without touching any code.', - 'jetpack' - ), - }; - } -} - -export const getSetupWizardStatus = state => { - return '' === state.jetpack.setupWizard.status - ? getInitialSetupWizardStatus( state ) - : state.jetpack.setupWizard.status; -}; diff --git a/projects/plugins/jetpack/_inc/jetpack-wizard-banner.js b/projects/plugins/jetpack/_inc/jetpack-wizard-banner.js deleted file mode 100644 index e7e149fe3391d..0000000000000 --- a/projects/plugins/jetpack/_inc/jetpack-wizard-banner.js +++ /dev/null @@ -1,50 +0,0 @@ -/* global jQuery, jp_banner */ - -( function ( $ ) { - var wizardBanner = $( '#jp-wizard-banner' ); - var wizardBannerDismiss = $( '.wizard-banner-dismiss' ); - var personalButton = $( '#jp-wizard-banner-personal-button' ); - var businessButton = $( '#jp-wizard-banner-business-button' ); - var skipLink = $( '.jp-wizard-banner-wizard-skip-link' ); - - // Dismiss the wizard banner via AJAX - wizardBannerDismiss.on( 'click', function () { - $( wizardBanner ).hide(); - - var data = { - dismissBanner: true, - action: 'jetpack_wizard_banner', - nonce: jp_banner.wizardBannerNonce, - }; - - $.post( jp_banner.ajax_url, data, function ( response ) { - if ( true !== response.success ) { - $( wizardBanner ).show(); - } - } ); - } ); - - personalButton.on( 'click', function () { - $.post( jp_banner.ajax_url, { - personal: true, - action: 'jetpack_wizard_banner', - nonce: jp_banner.wizardBannerNonce, - } ); - } ); - - businessButton.on( 'click', function () { - $.post( jp_banner.ajax_url, { - business: true, - action: 'jetpack_wizard_banner', - nonce: jp_banner.wizardBannerNonce, - } ); - } ); - - skipLink.on( 'click', function () { - $.post( jp_banner.ajax_url, { - skip: true, - action: 'jetpack_wizard_banner', - nonce: jp_banner.wizardBannerNonce, - } ); - } ); -} )( jQuery ); diff --git a/projects/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php b/projects/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php index 61849c27c8b21..892eefae9a44f 100644 --- a/projects/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php +++ b/projects/plugins/jetpack/_inc/lib/admin-pages/class.jetpack-react-page.php @@ -46,15 +46,6 @@ function add_page_actions( $hook ) { } } - /** - * Add Jetpack Setup sub-link for eligible users - */ - function jetpack_add_set_up_sub_nav_item() { - if ( $this->show_setup_wizard() ) { - add_submenu_page( 'jetpack', __( 'Set up', 'jetpack' ), __( 'Set up', 'jetpack' ), 'jetpack_admin_page', 'jetpack#/setup', '__return_null' ); - } - } - /** * Add Jetpack Dashboard sub-link and point it to AAG if the user can view stats, manage modules or if Protect is active. * @@ -304,7 +295,6 @@ function get_initial_state() { 'plan' => Jetpack_Plan::get(), 'showBackups' => Jetpack::show_backups_ui(), 'showRecommendations' => Jetpack_Recommendations::is_enabled(), - 'showSetupWizard' => $this->show_setup_wizard(), 'isMultisite' => is_multisite(), 'dateFormat' => get_option( 'date_format' ), ), @@ -327,7 +317,6 @@ function get_initial_state() { 'externalServicesConnectUrls' => $this->get_external_services_connect_urls(), 'calypsoEnv' => Jetpack::get_calypso_env(), 'products' => Jetpack::get_products_for_purchase(), - 'setupWizardStatus' => Jetpack_Options::get_option( 'setup_wizard_status', 'not-started' ), // TODO: delete. 'recommendationsStep' => Jetpack_Core_Json_Api_Endpoints::get_recommendations_step()['step'], 'isSafari' => $is_safari, 'doNotUseConnectionIframe' => Constants::is_true( 'JETPACK_SHOULD_NOT_USE_CONNECTION_IFRAME' ), @@ -359,16 +348,6 @@ function get_flattened_settings( $modules ) { return $settings->data; } - - /** - * Returns a boolean for whether the Setup Wizard should be displayed or not. - * - * @return bool True if the Setup Wizard should be displayed, false otherwise. - */ - public function show_setup_wizard() { - return Jetpack_Wizard::can_be_displayed(); - } - /** * Returns the release post content and image data as an associative array. * This data is used to create the update modal. diff --git a/projects/plugins/jetpack/_inc/lib/class-jetpack-wizard.php b/projects/plugins/jetpack/_inc/lib/class-jetpack-wizard.php index 65bcdc0e50136..41b3e9ec298c2 100644 --- a/projects/plugins/jetpack/_inc/lib/class-jetpack-wizard.php +++ b/projects/plugins/jetpack/_inc/lib/class-jetpack-wizard.php @@ -1,28 +1,10 @@ WP_REST_Server::READABLE, - 'callback' => __CLASS__ . '::get_setup_wizard_questionnaire', - 'permission_callback' => __CLASS__ . '::update_settings_permission_check', - ), - array( - 'methods' => WP_REST_Server::EDITABLE, - 'callback' => __CLASS__ . '::update_setup_wizard_questionnaire', - 'permission_callback' => __CLASS__ . '::update_settings_permission_check', - 'args' => array( - 'questionnaire' => array( - 'required' => false, - 'type' => 'object', - 'validate_callback' => __CLASS__ . '::validate_setup_wizard_questionnaire', - ), - 'status' => array( - 'required' => false, - 'type' => 'string', - 'validate_callback' => __CLASS__ . '::validate_string', - ), - ), - ), - ) - ); - register_rest_route( 'jetpack/v4', '/recommendations/data', @@ -734,36 +702,6 @@ public static function register_endpoints() { ); } - /** - * Get the settings for the wizard questionnaire - * - * @return array Questionnaire settings. - */ - public static function get_setup_wizard_questionnaire() { - return Jetpack_Options::get_option( 'setup_wizard_questionnaire', (object) array() ); - } - - /** - * Update the settings selected on the wizard questionnaire - * - * @param WP_REST_Request $request The request. - * - * @return bool true. - */ - public static function update_setup_wizard_questionnaire( $request ) { - $questionnaire = $request['questionnaire']; - if ( ! empty( $questionnaire ) ) { - Jetpack_Options::update_option( 'setup_wizard_questionnaire', $questionnaire ); - } - - $status = $request['status']; - if ( ! empty( $status ) ) { - Jetpack_Options::update_option( 'setup_wizard_status', $status ); - } - - return true; - } - /** * Get the data for the recommendations * @@ -845,36 +783,6 @@ public static function get_recommendations_upsell() { } } - /** - * Validate the answers on the setup wizard questionnaire - * - * @param array $value Value to check received by request. - * @param WP_REST_Request $request The request sent to the WP REST API. - * @param string $param Name of the parameter passed to endpoint holding $value. - * - * @return bool|WP_Error - */ - public static function validate_setup_wizard_questionnaire( $value, $request, $param ) { - if ( ! is_array( $value ) ) { - /* translators: Name of a parameter that must be an object */ - return new WP_Error( 'invalid_param', sprintf( esc_html__( '%s must be an object.', 'jetpack' ), $param ) ); - } - - foreach ( $value as $answer_key => $answer ) { - if ( is_string( $answer ) ) { - $validate = self::validate_string( $answer, $request, $param ); - } else { - $validate = self::validate_boolean( $answer, $request, $param ); - } - - if ( is_wp_error( $validate ) ) { - return $validate; - } - } - - return true; - } - /** * Validate the recommendations data * diff --git a/projects/plugins/jetpack/class-jetpack-recommendations-banner.php b/projects/plugins/jetpack/class-jetpack-recommendations-banner.php index 147e3ce616c96..d9d8fae384261 100644 --- a/projects/plugins/jetpack/class-jetpack-recommendations-banner.php +++ b/projects/plugins/jetpack/class-jetpack-recommendations-banner.php @@ -14,9 +14,9 @@ **/ class Jetpack_Recommendations_Banner { /** - * Jetpack_Wizard_Banner + * Jetpack_Recommendations_Banner * - * @var Jetpack_Wizard_Banner + * @var Jetpack_Recommendations_Banner **/ private static $instance = null; diff --git a/projects/plugins/jetpack/class-jetpack-wizard-banner.php b/projects/plugins/jetpack/class-jetpack-wizard-banner.php index fa6868f79e469..41b3e9ec298c2 100644 --- a/projects/plugins/jetpack/class-jetpack-wizard-banner.php +++ b/projects/plugins/jetpack/class-jetpack-wizard-banner.php @@ -1,239 +1,10 @@ can_be_displayed() ) { - return; - } - - add_action( 'admin_print_styles', array( $this, 'admin_banner_styles' ) ); - add_action( 'admin_notices', array( $this, 'render_banner' ) ); - add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_banner_scripts' ) ); - } - - /** - * Can we display the banner? - */ - private function can_be_displayed() { - if ( ! Jetpack_Wizard::can_be_displayed() ) { - return false; - } - - // Only the dashboard and plugins pages should see the banner. - if ( ! in_array( get_current_screen()->id, array( 'dashboard', 'plugins' ), true ) ) { - return false; - } - - if ( ! current_user_can( 'jetpack_manage_modules' ) ) { - return false; - } - - // Kill if banner has been dismissed. - if ( Jetpack_Options::get_option( 'dismissed_wizard_banner' ) ) { - return false; - } - - if ( ! in_array( Jetpack_Options::get_option( 'setup_wizard_status', 'not-started' ), array( 'not-started', 'intro-page' ), true ) ) { - return false; - } - - return true; - } - - /** - * Enqueue JavaScript files. - */ - public function enqueue_banner_scripts() { - wp_enqueue_script( - 'jetpack-wizard-banner-js', - Assets::get_file_url_for_environment( - '_inc/build/jetpack-wizard-banner.min.js', - '_inc/jetpack-wizard-banner.js' - ), - array( 'jquery' ), - JETPACK__VERSION, - true - ); - - wp_localize_script( - 'jetpack-wizard-banner-js', - 'jp_banner', - array( - 'ajax_url' => admin_url( 'admin-ajax.php' ), - 'wizardBannerNonce' => wp_create_nonce( 'jp-wizard-banner-nonce' ), - ) - ); - } - - /** - * Include the needed styles - */ - public function admin_banner_styles() { - wp_enqueue_style( - 'jetpack-wizard-banner', - Assets::get_file_url_for_environment( - 'css/jetpack-wizard-banner.min.css', - 'css/jetpack-wizard-banner.css' - ), - array(), - JETPACK__VERSION - ); - } - - /** - * AJAX callback - */ - public static function ajax_callback() { - check_ajax_referer( 'jp-wizard-banner-nonce', 'nonce' ); - - $tracking = new Tracking(); - - if ( isset( $_REQUEST['personal'] ) ) { - $tracking->record_user_event( 'setup_wizard_banner_click', array( 'button' => 'personal' ) ); - } - - if ( isset( $_REQUEST['business'] ) ) { - $tracking->record_user_event( 'setup_wizard_banner_click', array( 'button' => 'business' ) ); - } - - if ( isset( $_REQUEST['skip'] ) ) { - $tracking->record_user_event( 'setup_wizard_banner_click', array( 'button' => 'skip' ) ); - } - - if ( - current_user_can( 'jetpack_manage_modules' ) - && isset( $_REQUEST['dismissBanner'] ) - ) { - Jetpack_Options::update_option( 'dismissed_wizard_banner', 1 ); - $tracking->record_user_event( 'setup_wizard_banner_dismiss' ); - wp_send_json_success(); - } - - wp_die(); - } - - /** - * Renders the Wizard Banner - * - * Since this HTML replicates the contents of _inc/client/setup-wizard/intro-page/index.jsx, - * every time one is changed, the other should also be. - */ - public function render_banner() { - $jetpack_logo = new Jetpack_Logo(); - $powering_up_logo = plugins_url( 'images/jetpack-powering-up.svg', JETPACK__PLUGIN_FILE ); - - ?> - - jetpack_react, 'add_actions' ), 998 ); add_action( 'admin_menu', array( $this->jetpack_react, 'add_actions' ), 998 ); add_action( 'jetpack_admin_menu', array( $this->jetpack_react, 'jetpack_add_dashboard_sub_nav_item' ) ); - add_action( 'jetpack_admin_menu', array( $this->jetpack_react, 'jetpack_add_set_up_sub_nav_item' ) ); add_action( 'jetpack_admin_menu', array( $this->jetpack_react, 'jetpack_add_settings_sub_nav_item' ) ); add_action( 'jetpack_admin_menu', array( $this, 'admin_menu_debugger' ) ); add_action( 'jetpack_admin_menu', array( $this->fallback_page, 'add_actions' ) ); diff --git a/projects/plugins/jetpack/class.jetpack.php b/projects/plugins/jetpack/class.jetpack.php index d2fe152600cab..952ae10c42db3 100644 --- a/projects/plugins/jetpack/class.jetpack.php +++ b/projects/plugins/jetpack/class.jetpack.php @@ -699,7 +699,6 @@ private function __construct() { add_action( 'wp_ajax_jetpack_connection_banner', array( $this, 'jetpack_connection_banner_callback' ) ); add_action( 'wp_ajax_jetpack_recommendations_banner', array( 'Jetpack_Recommendations_Banner', 'ajax_callback' ) ); - add_action( 'wp_ajax_jetpack_wizard_banner', array( 'Jetpack_Wizard_Banner', 'ajax_callback' ) ); add_action( 'wp_loaded', array( $this, 'register_assets' ) ); @@ -3611,7 +3610,6 @@ function admin_init() { } Jetpack_Recommendations_Banner::init(); - Jetpack_Wizard_Banner::init(); if ( current_user_can( 'manage_options' ) && ! self::permit_ssl() ) { add_action( 'jetpack_notices', array( $this, 'alert_auto_ssl_fail' ) ); diff --git a/projects/plugins/jetpack/load-jetpack.php b/projects/plugins/jetpack/load-jetpack.php index 190b7026def66..87e795158fa33 100644 --- a/projects/plugins/jetpack/load-jetpack.php +++ b/projects/plugins/jetpack/load-jetpack.php @@ -60,9 +60,7 @@ function jetpack_should_use_minified_assets() { require_once JETPACK__PLUGIN_DIR . 'class.jetpack-plan.php'; jetpack_require_lib( 'class-jetpack-recommendations' ); -jetpack_require_lib( 'class-jetpack-wizard' ); require_once JETPACK__PLUGIN_DIR . 'class-jetpack-recommendations-banner.php'; -require_once JETPACK__PLUGIN_DIR . 'class-jetpack-wizard-banner.php'; if ( is_admin() ) { require_once JETPACK__PLUGIN_DIR . 'class.jetpack-admin.php'; diff --git a/projects/plugins/jetpack/scss/jetpack-wizard-banner.scss b/projects/plugins/jetpack/scss/jetpack-wizard-banner.scss deleted file mode 100644 index e11b51ecf2a07..0000000000000 --- a/projects/plugins/jetpack/scss/jetpack-wizard-banner.scss +++ /dev/null @@ -1,124 +0,0 @@ -@import 'node_modules/@automattic/color-studio/dist/color-variables.scss'; -@import '../_inc/client/scss/mixin_breakpoint'; - -.jp-wizard-banner { - position: relative; - text-align: left; - background: $studio-white; - padding: 2rem; - box-shadow: 0px 2px 6px rgba( 0, 0, 0, 0.03 ), 0px 1px 2px rgba( 0, 0, 0, 0.03 ); - border: 1px solid $studio-gray-5; - margin: 40px 20px 0 2px; - @include breakpoint( '<660px' ) { - margin: 45px 20px 0 2px; - } -} - -.jp-wizard-banner-wizard-header { - margin-bottom: 1rem !important; - margin-top: 1rem !important; - font-weight: 500; - font-size: 1.5em; - line-height: 150%; - @include breakpoint( '<660px' ) { - text-align: left; - font-weight: 400; - } -} - -.jp-wizard-banner-grid { - display: flex; - max-width: 1000px; -} - -.jp-wizard-banner-grid-a { - width: 95%; - - @include breakpoint( '<960px' ) { - width: 100%; - } -} - -.jp-wizard-banner-grid-b { - width: 20%; - display: block; - min-width: 200px; - - @include breakpoint( '<960px' ) { - display: none; - } -} - -.powering-up-img { - border: none; - margin-right: 1rem; - margin-top: 3rem; - margin-left: 1rem; - - @include breakpoint( '<960px' ) { - display: none; - } -} - -.jp-wizard-banner-wizard-paragraph { - font-size: 1rem; - - @include breakpoint( '>660px' ) { - margin: 0; - } - - @include breakpoint( '<660px' ) { - text-align: left; - } -} - -.jp-wizard-banner-wizard-intro-question { - box-sizing: border-box; - - @include breakpoint( '>480px' ) { - border: 1px solid #dddddd; - border-radius: 4px; - } - - max-width: 720px; - padding: 1.5rem 0 1.5rem; - text-align: center; - margin-top: 2rem; - - h2 { - @include breakpoint( '<480px' ) { - text-align: left; - } - } -} - -.jp-wizard-banner-wizard-answer-buttons { - display: flex; - - flex-direction: row; - justify-content: center; - - @include breakpoint( '<480px' ) { - flex-direction: column; - align-items: center; - width: 100%; - } -} - -.jp-wizard-banner-wizard-button { - margin: 1rem; - width: 160px; - margin-right: 10px !important; - - @include breakpoint( '<480px' ) { - width: 100%; - margin: 0.5rem 0; - } -} - -.jp-wizard-banner-wizard-skip-link { - text-decoration: underline !important; - color: $studio-black; - margin-top: 1rem; - display: block; -} diff --git a/projects/plugins/jetpack/tests/php/_inc/lib/test_class.rest-api-endpoints.php b/projects/plugins/jetpack/tests/php/_inc/lib/test_class.rest-api-endpoints.php index ddc59d12fa9be..84820eb83e24b 100644 --- a/projects/plugins/jetpack/tests/php/_inc/lib/test_class.rest-api-endpoints.php +++ b/projects/plugins/jetpack/tests/php/_inc/lib/test_class.rest-api-endpoints.php @@ -1015,37 +1015,6 @@ public function test_change_owner() { remove_filter( 'pre_http_request', array( $this, 'mock_xmlrpc_success' ), 10 ); } - /** - * Test saving and retrieving the Setup Wizard questionnaire responses. - * - * @since 4.4.0 - */ - public function test_setup_wizard() { - // Create a user and set it up as current. - $user = $this->create_and_get_user( 'administrator' ); - $user->add_cap( 'jetpack_configure_modules' ); - wp_set_current_user( $user->ID ); - - $test_data = array( - 'param1' => 'val1', - 'param2' => 'val2', - ); - - $response = $this->create_and_get_request( - 'setup/questionnaire', - array( - 'questionnaire' => $test_data, - ), - 'POST' - ); - $this->assertResponseStatus( 200, $response ); - $this->assertEquals( true, $response->get_data() ); - - $response = $this->create_and_get_request( 'setup/questionnaire', array(), 'GET' ); - $this->assertResponseStatus( 200, $response ); - $this->assertResponseData( $test_data, $response ); - } - /** * Test saving and retrieving the recommendations data. * @@ -1106,7 +1075,7 @@ public function test_recommendations_step() { } /** - * Test saving and retrieving the Setup Wizard questionnaire responses. + * Test saving and retrieving licensing errors. * * @since 9.0.0 */ diff --git a/tools/eslint-excludelist.json b/tools/eslint-excludelist.json index 5cb1453af1b66..d4c650ae60fd3 100644 --- a/tools/eslint-excludelist.json +++ b/tools/eslint-excludelist.json @@ -8,7 +8,6 @@ "projects/plugins/jetpack/_inc/client/components/chart/legend.jsx", "projects/plugins/jetpack/_inc/client/components/count/docs/example.jsx", "projects/plugins/jetpack/_inc/client/components/count/index.jsx", - "projects/plugins/jetpack/_inc/client/components/data/query-setup-wizard-questionnaire/index.jsx", "projects/plugins/jetpack/_inc/client/components/data/query-site-products/index.js", "projects/plugins/jetpack/_inc/client/components/external-link/index.jsx", "projects/plugins/jetpack/_inc/client/components/form/counter.js", @@ -71,13 +70,6 @@ "projects/plugins/jetpack/_inc/client/rest-api/index.js", "projects/plugins/jetpack/_inc/client/security/sso.jsx", "projects/plugins/jetpack/_inc/client/settings/index.jsx", - "projects/plugins/jetpack/_inc/client/setup-wizard/checklist-answer/index.jsx", - "projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/index.jsx", - "projects/plugins/jetpack/_inc/client/setup-wizard/feature-toggle/map-feature-to-props.js", - "projects/plugins/jetpack/_inc/client/setup-wizard/income-question/index.jsx", - "projects/plugins/jetpack/_inc/client/setup-wizard/intro-page/index.jsx", - "projects/plugins/jetpack/_inc/client/setup-wizard/recommended-features/index.jsx", - "projects/plugins/jetpack/_inc/client/setup-wizard/updates-question/index.jsx", "projects/plugins/jetpack/_inc/client/state/at-a-glance/reducer.js", "projects/plugins/jetpack/_inc/client/state/checklist/actions.js", "projects/plugins/jetpack/_inc/client/state/checklist/reducer.js", @@ -99,8 +91,6 @@ "projects/plugins/jetpack/_inc/client/state/search/reducer.js", "projects/plugins/jetpack/_inc/client/state/settings/actions.js", "projects/plugins/jetpack/_inc/client/state/settings/reducer.js", - "projects/plugins/jetpack/_inc/client/state/setup-wizard/actions.js", - "projects/plugins/jetpack/_inc/client/state/setup-wizard/reducer.js", "projects/plugins/jetpack/_inc/client/state/site-products/reducer.js", "projects/plugins/jetpack/_inc/client/state/site-verify/reducer.js", "projects/plugins/jetpack/_inc/client/state/site/reducer.js", @@ -116,7 +106,6 @@ "projects/plugins/jetpack/_inc/jetpack-connection-banner.js", "projects/plugins/jetpack/_inc/jetpack-deactivate-dialog.js", "projects/plugins/jetpack/_inc/jetpack-modules.js", - "projects/plugins/jetpack/_inc/jetpack-wizard-banner.js", "projects/plugins/jetpack/_inc/jquery.jetpack-resize.js", "projects/plugins/jetpack/_inc/lib/debugger/jetpack-debugger-site-health.js", "projects/plugins/jetpack/_inc/polldaddy-shortcode.js", From 7efd55c89aedb330a7368bb7c47e2c6cb81a4e06 Mon Sep 17 00:00:00 2001 From: Jeremy Herve Date: Thu, 18 Feb 2021 17:04:15 +0100 Subject: [PATCH 11/34] Actions: switch from app to action for Stale issue/PR labeling (#18859) Co-authored-by: Brad Jorsch --- .github/stale.yml | 63 ------------------------------------- .github/workflows/stale.yml | 54 +++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 63 deletions(-) delete mode 100644 .github/stale.yml create mode 100644 .github/workflows/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 1616317f350fe..0000000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,63 +0,0 @@ -# Configuration for probot-stale - https://github.com/probot/stale - -# Number of days of inactivity before a stale Issue or Pull Request is closed. -# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: false - -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 180 # 6 months - -# Issues with these labels will never be considered stale -exemptLabels: - - "[Pri] High" - - "[Pri] BLOCKER" - - "[Type] Good For Community" - - "[Type] Good First Bug" - - "FixTheFlows" - -# Set to true to ignore issues in a project (defaults to false) -exemptProjects: true - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: false - -# Label to use when marking an issue as stale -staleLabel: "[Status] Stale" - -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false - -# Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 2 - -# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': -issues: - # Comment to post when marking an issue as stale. Set to `false` to disable - markComment: > -

This issue has been marked as stale. This happened because:

- -
    -
  • It has been inactive in the past 6 months.
  • -
  • It hasn’t been labeled `[Pri] Blocker`, `[Pri] High`.
  • -
- -

No further action is needed. But it's worth checking if this ticket has clear - reproduction steps and it is still reproducible. Feel free to close this issue - if you think it's not valid anymore — if you do, please add a brief - explanation.

- -pulls: - # Number of days of inactivity before an Issue or Pull Request becomes stale - daysUntilStale: 90 # 3 months - markComment: > -

This PR has been marked as stale. This happened because:

- -
    -
  • It has been inactive in the past 3 months.
  • -
  • It hasn’t been labeled `[Pri] Blocker`, `[Pri] High`.
  • -
- -

No further action is needed. But it's worth checking if this PR has clear - testing instructions, is it up to date with master, and it is still valid. - Feel free to close this issue if you think it's not valid anymore — if you - do, please add a brief explanation.

diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000000..423e291979843 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,54 @@ + +name: 'Mark stale issues' +on: + schedule: + - cron: '30 0 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + timeout-minutes: 1 # 2021-01-18: Successful runs seem to take a few seconds + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + # Get issues in ascending (oldest first) order. + ascending: true + # Do not auto-close issues marked as stale. + days-before-close: -1 + # After 6 months, mark issue as stale. + days-before-issue-stale: 180 + # After 3 months, mark PR as stale. + days-before-pr-stale: 90 + # Issues and PRs with these labels will never be considered stale. + exempt-issue-labels: '[Pri] High,[Pri] BLOCKER,[Type] Good For Community,[Type] Good First Bug,FixTheFlows' + exempt-pr-labels: '[Pri] High,[Pri] BLOCKER,[Type] Good For Community,[Type] Good First Bug,FixTheFlows' + # Label to use when marking an issue / PR as stale + stale-pr-label: '[Status] Stale' + stale-issue-label: '[Status] Stale' + # Messages to display. + stale-issue-message: | +

This issue has been marked as stale. This happened because:

+ +
    +
  • It has been inactive in the past 6 months.
  • +
  • It hasn’t been labeled `[Pri] Blocker`, `[Pri] High`, etc.
  • +
+ +

No further action is needed. But it's worth checking if this ticket has clear + reproduction steps and it is still reproducible. Feel free to close this issue + if you think it's not valid anymore — if you do, please add a brief + explanation.

+ stale-pr-message: | +

This PR has been marked as stale. This happened because:

+ +
    +
  • It has been inactive in the past 3 months.
  • +
  • It hasn’t been labeled `[Pri] Blocker`, `[Pri] High`, etc.
  • +
+ +

No further action is needed. But it's worth checking if this PR has clear + testing instructions, is it up to date with master, and it is still valid. + Feel free to close this PR if you think it's not valid anymore — if you + do, please add a brief explanation.

+ operations-per-run: 2 From 04d8306760ec2d1a271dad15d0000b6c25b94059 Mon Sep 17 00:00:00 2001 From: Jeremy Herve Date: Thu, 18 Feb 2021 18:27:22 +0100 Subject: [PATCH 12/34] Instagram: use arguments provided by shortcode attributes (#18768) --- .../jetpack/modules/shortcodes/instagram.php | 7 +- .../shortcodes/test-class.instagram.php | 123 +++++------------- 2 files changed, 36 insertions(+), 94 deletions(-) diff --git a/projects/plugins/jetpack/modules/shortcodes/instagram.php b/projects/plugins/jetpack/modules/shortcodes/instagram.php index 40c69c3cad797..3f6c757eded61 100644 --- a/projects/plugins/jetpack/modules/shortcodes/instagram.php +++ b/projects/plugins/jetpack/modules/shortcodes/instagram.php @@ -57,7 +57,7 @@ function jetpack_instagram_enable_embeds() { /** * Add auth token required by Instagram's oEmbed REST API, or proxy through WP.com. */ - add_filter( 'oembed_fetch_url', 'jetpack_instagram_oembed_fetch_url', 10, 2 ); + add_filter( 'oembed_fetch_url', 'jetpack_instagram_oembed_fetch_url', 10, 3 ); /** * Add JP auth headers if we're proxying through WP.com. @@ -214,16 +214,17 @@ function jetpack_instagram_get_allowed_parameters( $url, $atts = array() ) { * * @param string $provider URL of the oEmbed provider. * @param string $url URL of the content to be embedded. + * @param array $args Additional arguments for retrieving embed HTML. * * @return string */ -function jetpack_instagram_oembed_fetch_url( $provider, $url ) { +function jetpack_instagram_oembed_fetch_url( $provider, $url, $args ) { if ( ! wp_startswith( $provider, 'https://graph.facebook.com/v5.0/instagram_oembed/' ) ) { return $provider; } // Get a set of URL and parameters supported by Facebook. - $clean_parameters = jetpack_instagram_get_allowed_parameters( $url ); + $clean_parameters = jetpack_instagram_get_allowed_parameters( $url, $args ); // Replace existing URL by our clean version. if ( ! empty( $clean_parameters['url'] ) ) { diff --git a/projects/plugins/jetpack/tests/php/modules/shortcodes/test-class.instagram.php b/projects/plugins/jetpack/tests/php/modules/shortcodes/test-class.instagram.php index 4ed63a837cad0..fbd4755dc3b1f 100644 --- a/projects/plugins/jetpack/tests/php/modules/shortcodes/test-class.instagram.php +++ b/projects/plugins/jetpack/tests/php/modules/shortcodes/test-class.instagram.php @@ -179,81 +179,18 @@ public function test_shortcode_instagram() { } /** + * Test different oEmbed URLs and their output. + * * @covers ::jetpack_instagram_oembed_fetch_url + * @dataProvider get_instagram_urls + * + * @param string $original Instagram URL provided by user. + * @param string $expected Instagram URL embedded in the final post content. */ - public function test_instagram_replace_image_url_with_embed() { - global $post; - - $instagram_url = 'https://www.instagram.com/p/BnMO9vRleEx/'; - $post = $this->factory->post->create_and_get( array( 'post_content' => $instagram_url ) ); - - setup_postdata( $post ); - ob_start(); - the_content(); - $actual = ob_get_clean(); - wp_reset_postdata(); - - $this->assertContains( - '
factory->post->create_and_get( array( 'post_content' => $instagram_url ) ); - - setup_postdata( $post ); - ob_start(); - the_content(); - $actual = ob_get_clean(); - wp_reset_postdata(); - - $this->assertContains( - '
.widget { + border: none; padding: 14px 0 0 0; margin: 0; background: none; @@ -16,5 +17,11 @@ h2.widgettitle { font-size: 1.3em; margin: 1em 0 0.5em; + border: none; + + &::before, + &::after { + display: none !important; // hide theme-specific horizontal rules added via psudo-elements. + } } } From 03ef8656bb3129b02b33b0c46a151fee05d61ccf Mon Sep 17 00:00:00 2001 From: Sergey Mitroshin Date: Thu, 18 Feb 2021 15:36:15 -0600 Subject: [PATCH 14/34] Connection: updating the deprecated `is_user_connected()` method (#18876) The method Jetpack::is_user_connected() is deprecated by PR #18548. Replacing it with its counterpart from the jetpack-connection package. --- projects/plugins/jetpack/class.jetpack.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/class.jetpack.php b/projects/plugins/jetpack/class.jetpack.php index 952ae10c42db3..073add3e18dc7 100644 --- a/projects/plugins/jetpack/class.jetpack.php +++ b/projects/plugins/jetpack/class.jetpack.php @@ -4238,7 +4238,7 @@ function ( $domains ) { exit; } - if ( self::is_active() && self::is_user_connected() ) { + if ( static::is_active() && static::connection()->is_user_connected() ) { // The user is either already connected, or finished the connection process. wp_safe_redirect( $dest_url ); exit; From c11c422c6751974a9451511957b9066c90d8b2b1 Mon Sep 17 00:00:00 2001 From: Paul Bunkham Date: Thu, 18 Feb 2021 22:27:08 +0000 Subject: [PATCH 15/34] Correct the strokeWidth attribute. (#18845) --- projects/plugins/jetpack/extensions/shared/icons.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/extensions/shared/icons.js b/projects/plugins/jetpack/extensions/shared/icons.js index e83a073501651..a1f868eef627d 100644 --- a/projects/plugins/jetpack/extensions/shared/icons.js +++ b/projects/plugins/jetpack/extensions/shared/icons.js @@ -201,7 +201,7 @@ export const TranscriptIcon = { From 347fb3b7726ed9741dc30e1cd5a8ababcc083e88 Mon Sep 17 00:00:00 2001 From: Jeremy Herve Date: Fri, 19 Feb 2021 08:00:07 +0100 Subject: [PATCH 16/34] VideoPress: avoid warning when no rating is set (#18857) In some scenarios, the following notice happens: PHP Notice: Undefined property: stdClass::$rating in /home/holden/apps/jeherve_com/w/wp-content/plugins/jetpack-dev/_inc/lib/core-api/wpcom-fields/class-wpcom-rest-api-v2-attachment-videopress-data.php on line 97 Let's make sure we always have at least a default rating. --- .../plugins/jetpack/modules/videopress/utility-functions.php | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/plugins/jetpack/modules/videopress/utility-functions.php b/projects/plugins/jetpack/modules/videopress/utility-functions.php index e6f498f9d82b3..5624ffaf39806 100644 --- a/projects/plugins/jetpack/modules/videopress/utility-functions.php +++ b/projects/plugins/jetpack/modules/videopress/utility-functions.php @@ -529,6 +529,7 @@ function video_get_info_by_blogpostid( $blog_id, $post_id ) { $video_info->blog_id = $blog_id; $video_info->guid = null; $video_info->finish_date_gmt = '0000-00-00 00:00:00'; + $video_info->rating = null; if ( is_wp_error( $post ) ) { return $video_info; From a6781ef3b9f5837a0eb7b634da54ae4fb855272e Mon Sep 17 00:00:00 2001 From: Jeremy Herve Date: Fri, 19 Feb 2021 08:08:33 +0100 Subject: [PATCH 17/34] Actions: make milestone detection available for all tasks (#18558) Co-authored-by: Brad Jorsch --- .github/actions/repo-gardening/readme.md | 1 + .../actions/repo-gardening/src/get-labels.js | 29 + .../src/get-next-valid-milestone.js | 42 + .../repo-gardening/src/get-plugin-names.js | 31 + .github/actions/repo-gardening/src/index.js | 6 + .../src/tasks/add-milestone/index.js | 51 +- .../src/tasks/check-description/index.js | 304 ++++ .../src/tasks/check-description/readme.md | 7 + .github/workflows/dangerci.yml | 27 - dangerfile.js | 121 -- package.json | 1 - projects/plugins/jetpack/babel.config.js | 7 +- tools/eslint-excludelist.json | 1 - yarn.lock | 1381 +---------------- 14 files changed, 440 insertions(+), 1569 deletions(-) create mode 100644 .github/actions/repo-gardening/src/get-labels.js create mode 100644 .github/actions/repo-gardening/src/get-next-valid-milestone.js create mode 100644 .github/actions/repo-gardening/src/get-plugin-names.js create mode 100644 .github/actions/repo-gardening/src/tasks/check-description/index.js create mode 100644 .github/actions/repo-gardening/src/tasks/check-description/readme.md delete mode 100644 .github/workflows/dangerci.yml delete mode 100644 dangerfile.js diff --git a/.github/actions/repo-gardening/readme.md b/.github/actions/repo-gardening/readme.md index 7f20fe32c05ce..55c62582bf7a6 100644 --- a/.github/actions/repo-gardening/readme.md +++ b/.github/actions/repo-gardening/readme.md @@ -6,6 +6,7 @@ Here is the current list of tasks handled by this action: - Assign Issues: Adds assignee for issues which are being worked on, and adds the "In Progress" label. - Add Milestone: Adds a valid milestone to all PRs that get merged and don't already include a milestone. +- Check Description: Checks the contents of a PR description, and ensure it matches our recommendations. ## Build your own diff --git a/.github/actions/repo-gardening/src/get-labels.js b/.github/actions/repo-gardening/src/get-labels.js new file mode 100644 index 0000000000000..4494e29c03ad9 --- /dev/null +++ b/.github/actions/repo-gardening/src/get-labels.js @@ -0,0 +1,29 @@ +/* global GitHub */ + +/** + * Get labels on a PR. + * + * @param {GitHub} octokit - Initialized Octokit REST client. + * @param {string} owner - Repository owner. + * @param {string} repo - Repository name. + * @param {string} number - PR number. + * + * @returns {Promise} Promise resolving to an array of all labels for that PR. + */ +async function getLabels( octokit, owner, repo, number ) { + const labelList = []; + + for await ( const response of octokit.paginate.iterator( octokit.issues.listLabelsOnIssue, { + owner: owner.login, + repo, + issue_number: +number, + } ) ) { + response.data.map( label => { + labelList.push( label.name ); + } ); + } + + return labelList; +} + +module.exports = getLabels; diff --git a/.github/actions/repo-gardening/src/get-next-valid-milestone.js b/.github/actions/repo-gardening/src/get-next-valid-milestone.js new file mode 100644 index 0000000000000..4b5f69a01d7da --- /dev/null +++ b/.github/actions/repo-gardening/src/get-next-valid-milestone.js @@ -0,0 +1,42 @@ +/** + * External dependencies + */ +const moment = require( 'moment' ); + +/* global GitHub, OktokitIssuesListMilestonesForRepoResponseItem */ + +/** + * Returns a promise resolving to the next valid milestone, if exists. + * + * @param {GitHub} octokit - Initialized Octokit REST client. + * @param {string} owner - Repository owner. + * @param {string} repo - Repository name. + * @param {string} plugin - Plugin slug. + * + * @returns {Promise} Promise resolving to milestone, if exists. + */ +async function getNextValidMilestone( octokit, owner, repo, plugin = 'jetpack' ) { + const options = octokit.issues.listMilestones.endpoint.merge( { + owner, + repo, + state: 'open', + sort: 'due_on', + direction: 'asc', + } ); + + const responses = octokit.paginate.iterator( options ); + + for await ( const response of responses ) { + // Find a milestone which name is a version number + // and it's due dates is earliest in a future + const reg = new RegExp( plugin + '/d.d' ); + const nextMilestone = response.data + .filter( m => m.title.match( reg ) ) + .sort( ( m1, m2 ) => parseFloat( m1.title ) - parseFloat( m2.title ) ) + .find( milestone => milestone.due_on && moment( milestone.due_on ) > moment() ); + + return nextMilestone; + } +} + +module.exports = getNextValidMilestone; diff --git a/.github/actions/repo-gardening/src/get-plugin-names.js b/.github/actions/repo-gardening/src/get-plugin-names.js new file mode 100644 index 0000000000000..5d08b092c9eaf --- /dev/null +++ b/.github/actions/repo-gardening/src/get-plugin-names.js @@ -0,0 +1,31 @@ +/* global GitHub */ + +/** + * Internal dependencies + */ +const getLabels = require( './get-labels' ); + +/** + * Get the name of the plugin concerned by this PR. + * + * @param {GitHub} octokit - Initialized Octokit REST client. + * @param {string} owner - Repository owner. + * @param {string} repo - Repository name. + * @param {string} number - PR / Issue number. + * + * @returns {Promise} Promise resolving to an array of all the plugins touched by that PR. + */ +async function getPluginNames( octokit, owner, repo, number ) { + const plugins = []; + const labels = await getLabels( octokit, owner, repo, number ); + labels.map( label => { + const plugin = label.match( /^\[Plugin\]\s(?[^/]*)$/ ); + if ( plugin && plugin.groups.pluginName ) { + plugins.push( plugin.groups.pluginName.toLowerCase() ); + } + } ); + + return plugins; +} + +module.exports = getPluginNames; diff --git a/.github/actions/repo-gardening/src/index.js b/.github/actions/repo-gardening/src/index.js index 0acb3a2ea5f8c..378ab60579a92 100644 --- a/.github/actions/repo-gardening/src/index.js +++ b/.github/actions/repo-gardening/src/index.js @@ -10,6 +10,7 @@ const { context, getOctokit } = require( '@actions/github' ); const assignIssues = require( './tasks/assign-issues' ); const addMilestone = require( './tasks/add-milestone' ); const addLabels = require( './tasks/add-labels' ); +const checkDescription = require( './tasks/check-description' ); const debug = require( './debug' ); const ifNotFork = require( './if-not-fork' ); @@ -28,6 +29,11 @@ const automations = [ action: [ 'opened', 'reopened', 'synchronize', 'edited', 'labeled' ], task: addLabels, }, + { + event: 'pull_request', + action: [ 'opened', 'reopened', 'synchronize', 'edited', 'labeled' ], + task: checkDescription, + }, ]; ( async function main() { diff --git a/.github/actions/repo-gardening/src/tasks/add-milestone/index.js b/.github/actions/repo-gardening/src/tasks/add-milestone/index.js index d62f2f4f9d52c..47b7ba01ffb0d 100644 --- a/.github/actions/repo-gardening/src/tasks/add-milestone/index.js +++ b/.github/actions/repo-gardening/src/tasks/add-milestone/index.js @@ -1,51 +1,12 @@ -/** - * External dependencies - */ -const moment = require( 'moment' ); - /** * Internal dependencies */ const debug = require( '../../debug' ); const getAssociatedPullRequest = require( '../../get-associated-pull-request' ); +const getNextValidMilestone = require( '../../get-next-valid-milestone' ); +const getPluginNames = require( '../../get-plugin-names' ); -/* global GitHub, OktokitIssuesListMilestonesForRepoResponseItem, WebhookPayloadPullRequest */ - -/** - * Returns a promise resolving to the next valid milestone, if exists. - * - * @param {GitHub} octokit - Initialized Octokit REST client. - * @param {string} owner - Repository owner. - * @param {string} repo - Repository name. - * - * @returns {Promise} Promise resolving to milestone, if exists. - */ -async function getNextValidMilestone( octokit, owner, repo ) { - const params = { - state: 'open', - sort: 'due_on', - direction: 'asc', - }; - - const options = octokit.issues.listMilestones.endpoint.merge( { - owner, - repo, - ...params, - } ); - - const responses = octokit.paginate.iterator( options ); - - for await ( const response of responses ) { - // Find a milestone which name is a version number - // and it's due dates is earliest in a future - const nextMilestone = response.data - .filter( m => m.title.match( /\d\.\d/ ) ) - .sort( ( m1, m2 ) => parseFloat( m1.title ) - parseFloat( m2.title ) ) - .find( milestone => milestone.due_on && moment( milestone.due_on ) > moment() ); - - return nextMilestone; - } -} +/* global GitHub, WebhookPayloadPullRequest */ /** * Assigns any issues that are being worked to the author of the matching PR. @@ -78,8 +39,10 @@ async function addMilestone( payload, octokit ) { return; } - // Get next valid milestone. - const nextMilestone = await getNextValidMilestone( octokit, owner, repo ); + const plugins = await getPluginNames( octokit, owner, repo, prNumber ); + + // Get next valid milestone (we can only add one). + const nextMilestone = await getNextValidMilestone( octokit, owner, repo, plugins[ 0 ] ); if ( ! nextMilestone ) { throw new Error( 'Could not find a valid milestone' ); diff --git a/.github/actions/repo-gardening/src/tasks/check-description/index.js b/.github/actions/repo-gardening/src/tasks/check-description/index.js new file mode 100644 index 0000000000000..f29b7bfea0cb4 --- /dev/null +++ b/.github/actions/repo-gardening/src/tasks/check-description/index.js @@ -0,0 +1,304 @@ +/** + * External dependencies + */ +const moment = require( 'moment' ); + +/** + * Internal dependencies + */ +const debug = require( '../../debug' ); +const getLabels = require( '../../get-labels' ); +const getNextValidMilestone = require( '../../get-next-valid-milestone' ); +const getPluginNames = require( '../../get-plugin-names' ); + +/* global GitHub, WebhookPayloadPullRequest */ + +/** + * Check if a PR has unverified commits. + * + * @param {GitHub} octokit - Initialized Octokit REST client. + * @param {string} owner - Repository owner. + * @param {string} repo - Repository name. + * @param {string} number - PR number. + * + * @returns {Promise} Promise resolving to boolean. + */ +async function hasUnverifiedCommit( octokit, owner, repo, number ) { + for await ( const response of octokit.paginate.iterator( octokit.pulls.listCommits, { + owner: owner.login, + repo, + pull_number: +number, + } ) ) { + if ( response.data.find( commit => commit.commit.message.includes( '[not verified]' ) ) ) { + return true; + } + } + + return false; +} + +/** + * Check for status labels on a PR. + * + * @param {GitHub} octokit - Initialized Octokit REST client. + * @param {string} owner - Repository owner. + * @param {string} repo - Repository name. + * @param {string} number - PR number. + * + * @returns {Promise} Promise resolving to boolean. + */ +async function hasStatusLabels( octokit, owner, repo, number ) { + const labels = await getLabels( octokit, owner, repo, number ); + // We're only interested in status labels, but not the "Needs Reply" label since it can be added by the action. + return !! labels.find( label => label.match( /^\[Status\].*(?} Promise resolving to boolean. + */ +async function hasNeedsReviewLabel( octokit, owner, repo, number ) { + const labels = await getLabels( octokit, owner, repo, number ); + // We're really only interested in the Needs review label. + return !! labels.find( label => label.includes( '[Status] Needs Review' ) ); +} + +/** + * Build some info about a specific plugin's release dates. + * + * @param {string} plugin - Plugin name. + * @param {object} nextMilestone - Information about next milestone as returnde by GitHub. + * + * @returns {Promise} Promise resolving to info about the release (code freeze, release date). + */ +async function getMilestoneDates( plugin, nextMilestone ) { + let releaseDate; + let codeFreezeDate; + if ( nextMilestone ) { + releaseDate = moment( nextMilestone.due_on ).format( 'LL' ); + + // Look for a code freeze date in the milestone description. + const dateRegex = /^Code Freeze: (\d{4}-\d{2}-\d{2})\s*$/m; + const freezeDateDescription = nextMilestone.description.match( dateRegex ); + + // If we have a date and it is valid, use it, otherwise set code freeze to a week before the release. + if ( freezeDateDescription && moment( freezeDateDescription[ 1 ] ).isValid() ) { + codeFreezeDate = moment( freezeDateDescription[ 1 ] ).format( 'LL' ); + } else { + codeFreezeDate = moment( nextMilestone.due_on ).subtract( 7, 'd' ).format( 'LL' ); + } + } else { + // Fallback to raw math calculation + // Calculate next release date + const firstTuesdayOfMonth = moment().add( 1, 'months' ).startOf( 'month' ); + while ( firstTuesdayOfMonth.day() !== 2 ) { + firstTuesdayOfMonth.add( 1, 'day' ); + } + releaseDate = firstTuesdayOfMonth.format( 'LL' ); + // Calculate next code freeze date + codeFreezeDate = firstTuesdayOfMonth.subtract( 8, 'd' ).format( 'LL' ); + } + + return ` +****** + +**${ plugin } plugin:** +- Next scheduled release: _${ releaseDate }_. +- Scheduled code freeze: _${ codeFreezeDate }_ +`; +} + +/** + * Build a string with info about the next milestone. + * + * @param {GitHub} octokit - Initialized Octokit REST client. + * @param {string} owner - Repository owner. + * @param {string} repo - Repository name. + * @param {string} number - PR number. + * + * @returns {Promise} Promise resolving to info about the next release for that plugin. + */ +async function buildMilestoneInfo( octokit, owner, repo, number ) { + const plugins = await getPluginNames( octokit, owner, repo, number ); + const ownerLogin = owner.login; + let pluginInfo; + + // Get next valid milestone for each plugin. + for await ( const plugin of plugins ) { + const nextMilestone = await getNextValidMilestone( octokit, ownerLogin, repo, plugin ); + debug( `check-description: Milestone found: ${ nextMilestone }` ); + + debug( `check-description: getting milestone info for ${ plugin }` ); + const info = await getMilestoneDates( plugin, nextMilestone ); + + pluginInfo += info; + } + + return pluginInfo; +} + +/** + * Search for a previous comment from this task in our PR. + * + * @param {GitHub} octokit - Initialized Octokit REST client. + * @param {string} owner - Repository owner. + * @param {string} repo - Repository name. + * @param {string} number - PR number. + * + * @returns {Promise} Promise resolving to boolean. + */ +async function getCheckComment( octokit, owner, repo, number ) { + let commentID = 0; + + debug( `check-description: Looking for a previous comment from this task in our PR.` ); + + for await ( const response of octokit.paginate.iterator( octokit.issues.listComments, { + owner: owner.login, + repo, + issue_number: +number, + } ) ) { + response.data.map( comment => { + if ( + comment.user.login === 'github-actions[bot]' && + comment.body.includes( '**Thank you for your PR!**' ) + ) { + commentID = comment.id; + } + } ); + } + + return commentID; +} + +/** + * Checks the contents of a PR description. + * + * @param {WebhookPayloadPullRequest} payload - Pull request event payload. + * @param {GitHub} octokit - Initialized Octokit REST client. + */ +async function checkDescription( payload, octokit ) { + const { base, body, head, number } = payload.pull_request; + const { name: repo, owner } = payload.repository; + + debug( `check-description: start building our comment` ); + + // We'll add any remarks we may have about the PR to that comment body. + let comment = `**Thank you for your PR!** + +When contributing to Jetpack, we have [a few suggestions](https://github.com/Automattic/jetpack/blob/master/.github/PULL_REQUEST_TEMPLATE.md) that can help us test and review your patch:
`; + + // No PR is too small to include a description of why you made a change + comment += ` +- ${ + body < 10 ? `:red_circle:` : `:white_check_mark:` + } Include a description of your PR changes.
`; + + // Check all commits in PR. + // In this case, we use a different failure icon, as we do not consider this a blocker, it should not trigger label changes. + const isDirty = await hasUnverifiedCommit( octokit, owner, repo, number ); + comment += ` +- ${ isDirty ? `:x:` : `:white_check_mark:` } All commits were linted before commit.
`; + + // Use labels please! + // Only check this for PRs created by a12s. External contributors cannot add labels. + if ( head.repo.full_name === base.repo.full_name ) { + const isLabeled = await hasStatusLabels( octokit, owner, repo, number ); + debug( `check-description: this PR is correctly labeled: ${ isLabeled }` ); + comment += ` +- ${ + ! isLabeled ? `:red_circle:` : `:white_check_mark:` + } Add a "[Status]" label (In Progress, Needs Team Review, ...).
`; + } + + // Check for testing instructions. + comment += ` +- ${ + ! body.includes( 'Testing instructions' ) ? `:red_circle:` : `:white_check_mark:` + } Add testing instructions.
`; + + // Check for a proposed changelog entry. + comment += ` +- ${ + ! body.includes( 'Proposed changelog entry' ) ? `:red_circle:` : `:white_check_mark:` + } Include a changelog entry for any meaningful change.
`; + + // Check if the Privacy section is filled in. + comment += ` +- ${ + ! body.includes( 'data or activity we track or use' ) ? `:red_circle:` : `:white_check_mark:` + } Specify whether this PR includes any changes to data or privacy.
`; + + debug( `check-description: privacy checked. our comment so far is ${ comment }` ); + + comment += ` + + +This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation :robot: + +****** + +If you are an automattician, once your PR is ready for review add the "[Status] Needs Team review" label and ask someone from your team review the code. +Once you’ve done so, switch to the "[Status] Needs Review" label; someone from Jetpack Crew will then review this PR and merge it to be included in the next Jetpack release.`; + + // Gather info about the next release for that plugin. + const milestoneInfo = await buildMilestoneInfo( octokit, owner, repo, number ); + comment += milestoneInfo; + + // Look for an existing check-description task comment. + const existingComment = await getCheckComment( octokit, owner, repo, number ); + + const ownerLogin = owner.login; + + // If there is a comment already, update it. + if ( existingComment !== 0 ) { + debug( `check-description: update comment ID ${ existingComment } with our new remarks` ); + await octokit.issues.updateComment( { + owner: ownerLogin, + repo, + comment_id: +existingComment, + body: comment, + } ); + } else { + // If no comment was published before, publish one now. + debug( `check-description: Posting comment to PR #${ number }` ); + + await octokit.issues.createComment( { + owner: ownerLogin, + repo, + issue_number: +number, + body: comment, + } ); + } + + // If some of our checks are failing, remove any "Needs Review" labels and add an Needs Author Reply label. + if ( comment.includes( ':red_circle:' ) ) { + debug( `check-description: some of the checks are failing. Update labels accordingly.` ); + + const hasNeedsReview = await hasNeedsReviewLabel( octokit, owner, repo, number ); + if ( hasNeedsReview ) { + debug( `check-description: remove existing Needs review label.` ); + await octokit.issues.removeLabel( { + owner: ownerLogin, + repo, + issue_number: +number, + name: '[Status] Needs Review', + } ); + } + + debug( `check-description: add Needs Author Reply label.` ); + await octokit.issues.addLabels( { + owner: ownerLogin, + repo, + issue_number: +number, + labels: [ '[Status] Needs Author Reply' ], + } ); + } +} + +module.exports = checkDescription; diff --git a/.github/actions/repo-gardening/src/tasks/check-description/readme.md b/.github/actions/repo-gardening/src/tasks/check-description/readme.md new file mode 100644 index 0000000000000..4514f9c2297ba --- /dev/null +++ b/.github/actions/repo-gardening/src/tasks/check-description/readme.md @@ -0,0 +1,7 @@ +# Better description + +Checks the contents of a PR description + +## Rationale + +When one creates a PR, it's best if it contains enough details and information for the PR reviewers to be able to test and review the changes. This is why we created a PR template that can help one add all the necessary information. This action ensures that the template is respected. diff --git a/.github/workflows/dangerci.yml b/.github/workflows/dangerci.yml deleted file mode 100644 index a222f7dac31e0..0000000000000 --- a/.github/workflows/dangerci.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Danger CI - -on: pull_request_target -env: - DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_TOKEN }} -jobs: - dangerci: - name: "Danger CI" - runs-on: ubuntu-latest - timeout-minutes: 4 # 2021-01-18: Successful runs seem to take 1-2 minutes - steps: - - uses: actions/checkout@v2 - - - uses: actions/setup-node@v2 - with: - node-version: '12' - - - uses: actions/cache@v2 - with: - path: /home/runner/.cache/yarn/v6 - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - - run: | - yarn install - yarn danger ci diff --git a/dangerfile.js b/dangerfile.js deleted file mode 100644 index 1023f82358801..0000000000000 --- a/dangerfile.js +++ /dev/null @@ -1,121 +0,0 @@ -/** - * External dependencies - */ -import { danger, warn, markdown, results, schedule, fail } from 'danger'; -const moment = require( 'moment' ); - -const github = danger.github; -const pr = github.pr; -const newFiles = danger.git.created_files; - -// No PR is too small to include a description of why you made a change -if ( pr.body.length < 10 ) { - warn( 'Please include a description of your PR changes.' ); -} - -// Keep track of commits which skipped pre-commit hook -const notVerifiedCommits = danger.git.commits.filter( commit => - commit.message.includes( '[not verified]' ) -); -if ( notVerifiedCommits.length > 0 ) { - warn( '`pre-commit` hook was skipped for one or more commits' ); -} - -// Use labels please! -const ghLabels = github.issue.labels; -if ( ! ghLabels.find( l => l.name.toLowerCase().includes( '[status]' ) ) ) { - warn( - 'The PR is missing at least one `[Status]` label. Suggestions: `[Status] In Progress`, `[Status] Needs Review`' - ); -} - -// Test instructions -if ( ! pr.body.includes( 'Testing instructions' ) ) { - warn( '"Testing instructions" are missing for this PR. Please add some' ); -} - -// Proposed changelog entry -if ( ! pr.body.includes( 'Proposed changelog entry' ) ) { - warn( - '"Proposed changelog entry" is missing for this PR. Please include any meaningful changes' - ); -} - -// Privacy section filled in -if ( ! pr.body.includes( 'data or activity we track or use' ) ) { - warn( - 'The Privacy section is missing for this PR. Please specify whether this PR includes any changes to data or privacy.' - ); -} - -// skip if there are no warnings. -if ( results.warnings.length > 0 || results.fails.length > 0 ) { - markdown( - "This is an automated check which relies on [`PULL_REQUEST_TEMPLATE`](https://github.com/Automattic/jetpack/blob/master/.github/PULL_REQUEST_TEMPLATE.md). We encourage you to follow that template as it helps Jetpack maintainers do their job. If you think 'Testing instructions' or 'Proposed changelog entry' are not needed for your PR - please explain why you think so. Thanks for cooperation :robot:" - ); -} else { - markdown( - `**Thank you for the great PR description!** - -When this PR is ready for review, please apply the \`[Status] Needs Review\` label. If you are an a11n, please have someone from your team review the code if possible. The Jetpack team will also review this PR and merge it to be included in the next Jetpack release.` - ); - setReleaseDates(); -} - -// Add note about E2E dashboard -if ( process.env.TRAVIS_PULL_REQUEST ) { - const dashboardUrl = `https://jetpack-e2e-dashboard.herokuapp.com/pr-${ process.env.TRAVIS_PULL_REQUEST }`; - const msg = `E2E results is available here (for debugging purposes): [${ dashboardUrl }](${ dashboardUrl })`; - markdown( '\n\n' + msg ); -} - -/** - * Adds release and code freeze dates according to x.x milestone due date - */ -function setReleaseDates() { - schedule( async () => { - let jetpackReleaseDate; - let codeFreezeDate; - const milestones = await github.api.issues.listMilestonesForRepo( { - owner: 'Automattic', - repo: 'jetpack', - } ); - - // Find a milestone which name is a version number - // and it's due dates is earliest in a future - const nextMilestone = milestones.data - .filter( m => m.title.match( /\d\.\d/ ) ) - .sort( ( m1, m2 ) => parseFloat( m1.title ) - parseFloat( m2.title ) ) - .find( milestone => milestone.due_on && moment( milestone.due_on ) > moment() ); - - if ( nextMilestone ) { - jetpackReleaseDate = moment( nextMilestone.due_on ).format( 'LL' ); - - // Look for a code freeze date in the milestone description. - const dateRegex = /^Code Freeze: (\d{4}-\d{2}-\d{2})\s*$/m; - const freezeDateDescription = nextMilestone.description.match( dateRegex ); - - // If we have a date and it is valid, use it, otherwise set code freeze to a week before the release. - if ( freezeDateDescription && moment( freezeDateDescription[ 1 ] ).isValid() ) { - codeFreezeDate = moment( freezeDateDescription[ 1 ] ).format( 'LL' ); - } else { - codeFreezeDate = moment( nextMilestone.due_on ).subtract( 7, 'd' ).format( 'LL' ); - } - } else { - // Fallback to raw math calculation - // Calculate next release date - const firstTuesdayOfMonth = moment().add( 1, 'months' ).startOf( 'month' ); - while ( firstTuesdayOfMonth.day() !== 2 ) { - firstTuesdayOfMonth.add( 1, 'day' ); - } - jetpackReleaseDate = firstTuesdayOfMonth.format( 'LL' ); - // Calculate next code freeze date - codeFreezeDate = firstTuesdayOfMonth.subtract( 8, 'd' ).format( 'LL' ); - } - - markdown( ` - -Scheduled Jetpack release: _${ jetpackReleaseDate }_. -Scheduled code freeze: _${ codeFreezeDate }_` ); - } ); -} diff --git a/package.json b/package.json index a18775cda6d24..44903e94cbf71 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,6 @@ "babel-eslint": "10.1.0", "commander": "6.2.1", "concurrently": "5.3.0", - "danger": "10.6.1", "eslint": "7.18.0", "eslint-config-prettier": "7.2.0", "eslint-config-wpcalypso": "5.0.0", diff --git a/projects/plugins/jetpack/babel.config.js b/projects/plugins/jetpack/babel.config.js index 20c97c075acbf..c67265bf51bd7 100644 --- a/projects/plugins/jetpack/babel.config.js +++ b/projects/plugins/jetpack/babel.config.js @@ -7,12 +7,7 @@ const config = { }, { // Transpile ES Modules syntax (`import`) in config files (but not elsewhere) - test: [ - './dangerfile.js', - './gulpfile.babel.js', - './tools/webpack.config.js', - './tools/builder/', - ], + test: [ './gulpfile.babel.js', './tools/webpack.config.js', './tools/builder/' ], presets: [ [ require.resolve( '@automattic/calypso-build/babel/default' ), { modules: 'commonjs' } ], ], diff --git a/tools/eslint-excludelist.json b/tools/eslint-excludelist.json index d4c650ae60fd3..57980e4dc2c46 100644 --- a/tools/eslint-excludelist.json +++ b/tools/eslint-excludelist.json @@ -1,5 +1,4 @@ [ - "dangerfile.js", "projects/plugins/jetpack/_inc/.eslintrc.js", "projects/plugins/jetpack/_inc/client/admin.js", "projects/plugins/jetpack/_inc/client/at-a-glance/backups.jsx", diff --git a/yarn.lock b/yarn.lock index 92cee8a6ce5e2..96c1e96eca6d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -60,14 +60,6 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79" integrity sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg== -"@babel/polyfill@^7.2.5": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.12.1.tgz#1f2d6371d1261bbd961f3c5d5909150e12d0bd96" - integrity sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g== - dependencies: - core-js "^2.6.5" - regenerator-runtime "^0.13.4" - "@babel/runtime-corejs3@^7.10.2": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz#ffee91da0eb4c6dae080774e94ba606368e414f4" @@ -153,116 +145,6 @@ "@nodelib/fs.scandir" "2.1.4" fastq "^1.6.0" -"@octokit/auth-token@^2.4.0": - version "2.4.5" - resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3" - integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA== - dependencies: - "@octokit/types" "^6.0.3" - -"@octokit/endpoint@^6.0.1": - version "6.0.11" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.11.tgz#082adc2aebca6dcefa1fb383f5efb3ed081949d1" - integrity sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ== - dependencies: - "@octokit/types" "^6.0.3" - is-plain-object "^5.0.0" - universal-user-agent "^6.0.0" - -"@octokit/openapi-types@^3.3.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-3.3.0.tgz#8797af01feb524e3f4978e1c9bf7bc7c18ef9d25" - integrity sha512-s3dd32gagPmKaSLNJ9aPNok7U+tl69YLESf6DgQz5Ml/iipPZtif3GLvWpNXoA6qspFm1LFUZX+C3SqWX/Y/TQ== - -"@octokit/plugin-paginate-rest@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz#004170acf8c2be535aba26727867d692f7b488fc" - integrity sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q== - dependencies: - "@octokit/types" "^2.0.1" - -"@octokit/plugin-request-log@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.3.tgz#70a62be213e1edc04bb8897ee48c311482f9700d" - integrity sha512-4RFU4li238jMJAzLgAwkBAw+4Loile5haQMQr+uhFq27BmyJXcXSKvoQKqh0agsZEiUlW6iSv3FAgvmGkur7OQ== - -"@octokit/plugin-rest-endpoint-methods@2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.4.0.tgz#3288ecf5481f68c494dd0602fc15407a59faf61e" - integrity sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ== - dependencies: - "@octokit/types" "^2.0.1" - deprecation "^2.3.1" - -"@octokit/request-error@^1.0.2": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-1.2.1.tgz#ede0714c773f32347576c25649dc013ae6b31801" - integrity sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA== - dependencies: - "@octokit/types" "^2.0.0" - deprecation "^2.0.0" - once "^1.4.0" - -"@octokit/request-error@^2.0.0": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.5.tgz#72cc91edc870281ad583a42619256b380c600143" - integrity sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg== - dependencies: - "@octokit/types" "^6.0.3" - deprecation "^2.0.0" - once "^1.4.0" - -"@octokit/request@^5.2.0": - version "5.4.13" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.13.tgz#eec5987b3e96f984fc5f41967e001170c6d23a18" - integrity sha512-WcNRH5XPPtg7i1g9Da5U9dvZ6YbTffw9BN2rVezYiE7couoSyaRsw0e+Tl8uk1fArHE7Dn14U7YqUDy59WaqEw== - dependencies: - "@octokit/endpoint" "^6.0.1" - "@octokit/request-error" "^2.0.0" - "@octokit/types" "^6.0.3" - deprecation "^2.0.0" - is-plain-object "^5.0.0" - node-fetch "^2.6.1" - once "^1.4.0" - universal-user-agent "^6.0.0" - -"@octokit/rest@^16.43.1": - version "16.43.2" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.43.2.tgz#c53426f1e1d1044dee967023e3279c50993dd91b" - integrity sha512-ngDBevLbBTFfrHZeiS7SAMAZ6ssuVmXuya+F/7RaVvlysgGa1JKJkKWY+jV6TCJYcW0OALfJ7nTIGXcBXzycfQ== - dependencies: - "@octokit/auth-token" "^2.4.0" - "@octokit/plugin-paginate-rest" "^1.1.1" - "@octokit/plugin-request-log" "^1.0.0" - "@octokit/plugin-rest-endpoint-methods" "2.4.0" - "@octokit/request" "^5.2.0" - "@octokit/request-error" "^1.0.2" - atob-lite "^2.0.0" - before-after-hook "^2.0.0" - btoa-lite "^1.0.0" - deprecation "^2.0.0" - lodash.get "^4.4.2" - lodash.set "^4.3.2" - lodash.uniq "^4.5.0" - octokit-pagination-methods "^1.1.0" - once "^1.4.0" - universal-user-agent "^4.0.0" - -"@octokit/types@^2.0.0", "@octokit/types@^2.0.1": - version "2.16.2" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-2.16.2.tgz#4c5f8da3c6fecf3da1811aef678fda03edac35d2" - integrity sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q== - dependencies: - "@types/node" ">= 8" - -"@octokit/types@^6.0.3": - version "6.6.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.6.0.tgz#165d0e7940d5f910b3373f02336c161dcdf097fe" - integrity sha512-nmFoU3HCbw1AmnZU/eto2VvzV06+N7oAqXwMmAHGlNDF+KFykksh/VlAl85xc1P5T7Mw8fKYvXNaImNHCCH/rg== - dependencies: - "@octokit/openapi-types" "^3.3.0" - "@types/node" ">= 8" - "@samverschueren/stream-to-observable@^0.3.0": version "0.3.1" resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz#a21117b19ee9be70c379ec1877537ef2e1c63301" @@ -275,11 +157,6 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== -"@types/node@>= 8": - version "14.14.22" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.22.tgz#0d29f382472c4ccf3bd96ff0ce47daf5b7b84b18" - integrity sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw== - "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -384,13 +261,6 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - acorn-jsx@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" @@ -401,13 +271,6 @@ acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -agent-base@4, agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== - dependencies: - es6-promisify "^5.0.0" - ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" @@ -527,21 +390,6 @@ aria-query@^4.2.2: "@babel/runtime" "^7.10.2" "@babel/runtime-corejs3" "^7.10.2" -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" @@ -563,11 +411,6 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - array.prototype.flatmap@^1.2.3: version "1.2.4" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9" @@ -590,11 +433,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - ast-types-flow@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" @@ -610,28 +448,11 @@ async-foreach@^0.1.3: resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= -async-retry@1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.2.3.tgz#a6521f338358d322b1a0012b79030c6f411d1ce0" - integrity sha512-tfDb02Th6CE6pJUF2gjW5ZVjsgwlucVXOEQMvEX9JgSJMs9gAX+Nz3xRuJBKuUYjTSYORqvDBORdAQ3LU59g7Q== - dependencies: - retry "0.12.0" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -atob-lite@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696" - integrity sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY= - -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -674,19 +495,6 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -694,11 +502,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -before-after-hook@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" - integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A== - big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -728,22 +531,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - braces@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -751,16 +538,6 @@ braces@^3.0.1: dependencies: fill-range "^7.0.1" -btoa-lite@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" - integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= - -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= - buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -769,21 +546,6 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -839,7 +601,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -858,16 +620,6 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - cli-cursor@^2.0.0, cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -918,14 +670,6 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -950,11 +694,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colors@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" - integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== - combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -967,11 +706,6 @@ commander@6.2.1: resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== -commander@^2.18.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - comment-parser@^0.7.6: version "0.7.6" resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-0.7.6.tgz#0e743a53c8e646c899a1323db31f6cd337b10f12" @@ -987,11 +721,6 @@ compare-versions@^3.6.0: resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1017,21 +746,11 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - core-js-pure@^3.0.0: version "3.8.3" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.8.3.tgz#10e9e3b2592ecaede4283e8f3ad7020811587c02" integrity sha512-V5qQZVAr9K0xu7jXg1M7qTEwuxUgqr7dUOezGaNa7i+Xn9oXAU/d1fzqD9ObuwpVQOaorO5s70ckyi1woP9lVA== -core-js@^2.6.5: - version "2.6.12" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" - integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== - core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -1056,17 +775,6 @@ cross-spawn@^3.0.0: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^6.0.0: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -1088,48 +796,6 @@ damerau-levenshtein@^1.0.6: resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791" integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug== -danger@10.6.1: - version "10.6.1" - resolved "https://registry.yarnpkg.com/danger/-/danger-10.6.1.tgz#b19bd8f15f764b13a4bc3c1cb5b3a74e199f93c4" - integrity sha512-uoAhEziWafPq9oCHFFr4d3gMcAmiLaybUT73c1ZEWnATGlwPthGKDOE9DE0TNpr2yMeV9GgpruOD5URivP8LBw== - dependencies: - "@babel/polyfill" "^7.2.5" - "@octokit/rest" "^16.43.1" - async-retry "1.2.3" - chalk "^2.3.0" - commander "^2.18.0" - debug "^4.1.1" - fast-json-patch "^3.0.0-1" - get-stdin "^6.0.0" - gitlab "^10.0.1" - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.1" - hyperlinker "^1.0.0" - json5 "^2.1.0" - jsonpointer "^4.0.1" - jsonwebtoken "^8.4.0" - lodash.find "^4.6.0" - lodash.includes "^4.3.0" - lodash.isobject "^3.0.2" - lodash.keys "^4.0.8" - lodash.mapvalues "^4.6.0" - lodash.memoize "^4.1.2" - memfs-or-file-map-to-github-branch "^1.1.0" - micromatch "^3.1.10" - node-cleanup "^2.1.2" - node-fetch "2.6.1" - override-require "^1.1.1" - p-limit "^2.1.0" - parse-diff "^0.7.0" - parse-git-config "^2.0.3" - parse-github-url "^1.0.2" - parse-link-header "^1.0.1" - pinpoint "^1.1.0" - prettyjson "^1.2.1" - readline-sync "^1.4.9" - require-from-string "^2.0.2" - supports-hyperlinks "^1.0.1" - dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -1147,27 +813,6 @@ date-fns@^2.0.1: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b" integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ== -debug@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - -debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" @@ -1180,11 +825,6 @@ decamelize@^1.1.2, decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - deep-is@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -1197,28 +837,6 @@ define-properties@^1.1.3: dependencies: object-keys "^1.0.12" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -1229,11 +847,6 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= -deprecation@^2.0.0, deprecation@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" - integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -1263,13 +876,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ecdsa-sig-formatter@1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" @@ -1295,13 +901,6 @@ emojis-list@^3.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - enquirer@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -1345,18 +944,6 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es6-promise@^4.0.3: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -1606,11 +1193,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - execa@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" @@ -1626,54 +1208,6 @@ execa@5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= - dependencies: - homedir-polyfill "^1.0.1" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -1688,20 +1222,6 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -1734,11 +1254,6 @@ fast-glob@^3.1.1: micromatch "^4.0.2" picomatch "^2.2.1" -fast-json-patch@^3.0.0-1: - version "3.0.0-1" - resolved "https://registry.yarnpkg.com/fast-json-patch/-/fast-json-patch-3.0.0-1.tgz#4c68f2e7acfbab6d29d1719c44be51899c93dabb" - integrity sha512-6pdFb07cknxvPzCeLsFHStEy+MysPJPgZQ9LbQ/2O67unQF93SNqfdSqnPPl71YMHX+AD8gbl7iuoGFzHEdDuw== - fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -1785,16 +1300,6 @@ file-entry-cache@^6.0.0: dependencies: flat-cache "^3.0.4" -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -1862,25 +1367,11 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -form-data@^2.5.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" - integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.6" - mime-types "^2.1.12" - form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -1890,18 +1381,6 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fs-exists-sync@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" - integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= - fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -1981,23 +1460,11 @@ get-stdin@^6.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - get-stream@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.0.tgz#3e0012cb6827319da2706e601a1583e8629a6718" integrity sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg== -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -2005,28 +1472,6 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -git-config-path@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/git-config-path/-/git-config-path-1.0.1.tgz#6d33f7ed63db0d0e118131503bab3aca47d54664" - integrity sha1-bTP37WPbDQ4RgTFQO6s6ykfVRmQ= - dependencies: - extend-shallow "^2.0.1" - fs-exists-sync "^0.1.0" - homedir-polyfill "^1.0.0" - -gitlab@^10.0.1: - version "10.2.1" - resolved "https://registry.yarnpkg.com/gitlab/-/gitlab-10.2.1.tgz#1f5fb2c2bad08f95b7c7d91dd41805ab5eea3960" - integrity sha512-z+DxRF1C9uayVbocs9aJkJz+kGy14TSm1noB/rAIEBbXOkOYbjKxyuqJzt+0zeFpXFdgA0yq6DVVbvM7HIfGwg== - dependencies: - form-data "^2.5.0" - humps "^2.0.1" - ky "^0.12.0" - ky-universal "^0.3.0" - li "^1.3.0" - query-string "^6.8.2" - universal-url "^2.0.0" - glob-parent@^5.0.0, glob-parent@^5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" @@ -2109,11 +1554,6 @@ has-ansi@^2.0.0: dependencies: ansi-regex "^2.0.0" -has-flag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" - integrity sha1-6CB68cx7MNRGzHC3NLXovhj4jVE= - has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -2134,37 +1574,6 @@ has-unicode@^2.0.0: resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -2172,31 +1581,11 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hasurl@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hasurl/-/hasurl-1.0.0.tgz#e4c619097ae1e8fc906bee904ce47e94f5e1ea37" - integrity sha512-43ypUd3DbwyCT01UYpA99AEZxZ4aKtRxWGBHEIbjcOsUghd9YUON0C+JF6isNjaiwC/UF5neaUudy6JS9jZPZQ== - -homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - hosted-git-info@^2.1.4: version "2.8.8" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== - dependencies: - agent-base "4" - debug "3.1.0" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -2206,24 +1595,11 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -https-proxy-agent@^2.2.1: - version "2.2.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" - integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== - dependencies: - agent-base "^4.3.0" - debug "^3.1.0" - human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -humps@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/humps/-/humps-2.0.1.tgz#dd02ea6081bd0568dc5d073184463957ba9ef9aa" - integrity sha1-3QLqYIG9BWjcXQcxhEY5V7qe+ao= - husky@4.3.8: version "4.3.8" resolved "https://registry.yarnpkg.com/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d" @@ -2240,11 +1616,6 @@ husky@4.3.8: slash "^3.0.0" which-pm-runs "^1.0.0" -hyperlinker@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e" - integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ== - iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -2310,7 +1681,7 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@^1.3.5, ini@^1.3.7: +ini@^1.3.7: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -2343,30 +1714,11 @@ internal-slot@^1.0.2: has "^1.0.3" side-channel "^1.0.4" -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - is-callable@^1.1.4, is-callable@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" @@ -2379,60 +1731,16 @@ is-core-module@^2.1.0: dependencies: has "^1.0.3" -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - is-date-object@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - is-docker@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156" integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw== -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -2472,13 +1780,6 @@ is-negative-zero@^2.0.1: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -2491,18 +1792,6 @@ is-observable@^1.1.0: dependencies: symbol-observable "^1.1.0" -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-plain-object@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" - integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== - is-promise@^2.1.0: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" @@ -2547,11 +1836,6 @@ is-utf8@^0.2.0: resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" @@ -2559,7 +1843,7 @@ is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -isarray@1.0.0, isarray@~1.0.0: +isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -2569,18 +1853,6 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -2667,7 +1939,7 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json5@^2.1.0, json5@^2.1.2: +json5@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== @@ -2681,27 +1953,6 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" -jsonpointer@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.1.0.tgz#501fb89986a2389765ba09e6053299ceb4f2c2cc" - integrity sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg== - -jsonwebtoken@^8.4.0: - version "8.5.1" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" - integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== - dependencies: - jws "^3.2.2" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - semver "^5.6.0" - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -2720,60 +1971,6 @@ jsprim@^1.2.2: array-includes "^3.1.2" object.assign "^4.1.2" -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -ky-universal@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/ky-universal/-/ky-universal-0.3.0.tgz#3fcbb0dd03da39b5f05100d9362a630d5e1d402e" - integrity sha512-CM4Bgb2zZZpsprcjI6DNYTaH3oGHXL2u7BU4DK+lfCuC4snkt9/WRpMYeKbBbXscvKkeqBwzzjFX2WwmKY5K/A== - dependencies: - abort-controller "^3.0.0" - node-fetch "^2.6.0" - -ky@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/ky/-/ky-0.12.0.tgz#c05be95e6745ba422a6d2cc8ae964164962279f9" - integrity sha512-t9b7v3V2fGwAcQnnDDQwKQGF55eWrf4pwi1RN08Fy8b/9GEwV7Ea0xQiaSW6ZbeghBHIwl8kgnla4vVo9seepQ== - language-subtag-registry@~0.3.2: version "0.3.21" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" @@ -2794,11 +1991,6 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -li@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/li/-/li-1.3.0.tgz#22c59bcaefaa9a8ef359cf759784e4bf106aea1b" - integrity sha1-IsWbyu+qmo7zWc91l4TkvxBq6hs= - lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -2900,86 +2092,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.find@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" - integrity sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E= - -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= - -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= - -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= - -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= - -lodash.isobject@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" - integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0= - -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= - -lodash.keys@^4.0.8: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" - integrity sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU= - -lodash.mapvalues@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c" - integrity sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw= - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= - -lodash.once@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" - integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= - -lodash.set@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" - integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= - -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= - -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= - lodash@4.17.20, lodash@^4.0.0, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@~4.17.10: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" @@ -3031,11 +2143,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -macos-release@^2.2.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.4.1.tgz#64033d0ec6a5e6375155a74b1a1eba8e509820ac" - integrity sha512-H/QHeBIN1fIGJX517pvK8IEK53yQOW7YcEI55oYtgjDdoCQQz7eJS94qt5kNrscReEyuD/JcdFCm2XBEcGOITg== - make-dir@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" @@ -3043,30 +2150,11 @@ make-dir@^3.0.2: dependencies: semver "^6.0.0" -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - -memfs-or-file-map-to-github-branch@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/memfs-or-file-map-to-github-branch/-/memfs-or-file-map-to-github-branch-1.2.0.tgz#a56cd13443144a8c7fbe2a4b90b5f570fb39c845" - integrity sha512-PloI9AkRXrLQuBU1s7eYQpl+4hkL0U0h23lddMaJ3ZGUufn8pdNRxd1kCfBqL5gISCFQs78ttXS15e4/f5vcTA== - dependencies: - "@octokit/rest" "^16.43.1" - meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -3093,25 +2181,6 @@ merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -micromatch@^3.1.10: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" @@ -3149,19 +2218,11 @@ minimatch@^3.0.4, minimatch@~3.0.2: dependencies: brace-expansion "^1.1.7" -minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.1.3, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -3174,21 +2235,11 @@ moment@2.29.1: resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - mute-stream@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -3199,39 +2250,12 @@ nan@^2.13.2: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - -node-cleanup@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/node-cleanup/-/node-cleanup-2.1.2.tgz#7ac19abd297e09a7f72a71545d951b517e4dde2c" - integrity sha1-esGavSl+Caf3KnFUXZUbUX5N3iw= - -node-fetch@2.6.1, node-fetch@^2.6.0, node-fetch@^2.6.1: +node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== @@ -3306,13 +2330,6 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -3345,15 +2362,6 @@ object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - object-hash@^2.0.3: version "2.1.1" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.1.1.tgz#9447d0279b4fcf80cff3259bf66a1dc73afabe09" @@ -3369,13 +2377,6 @@ object-keys@^1.0.12, object-keys@^1.1.1: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -3406,13 +2407,6 @@ object.fromentries@^2.0.2: es-abstract "^1.18.0-next.1" has "^1.0.3" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - object.values@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.2.tgz#7a2015e06fcb0f546bd652486ce8583a4731c731" @@ -3423,12 +2417,7 @@ object.values@^1.1.1: es-abstract "^1.18.0-next.1" has "^1.0.3" -octokit-pagination-methods@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4" - integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ== - -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -3471,14 +2460,6 @@ os-homedir@^1.0.0: resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= -os-name@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" - integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== - dependencies: - macos-release "^2.2.0" - windows-release "^3.1.0" - os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -3492,17 +2473,7 @@ osenv@0: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -override-require@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/override-require/-/override-require-1.1.1.tgz#6ae22fadeb1f850ffb0cf4c20ff7b87e5eb650df" - integrity sha1-auIvresfhQ/7DPTCD/e4fl62UN8= - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -p-limit@^2.0.0, p-limit@^2.1.0, p-limit@^2.2.0: +p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== @@ -3554,25 +2525,11 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-diff@0.7.1, parse-diff@^0.7.0: +parse-diff@0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/parse-diff/-/parse-diff-0.7.1.tgz#9b7a2451c3725baf2c87c831ba192d40ee2237d4" integrity sha512-1j3l8IKcy4yRK2W4o9EYvJLSzpAVwz4DXqCewYyx2vEwk2gcf3DBPqc8Fj4XV3K33OYJ08A8fWwyu/ykD/HUSg== -parse-git-config@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/parse-git-config/-/parse-git-config-2.0.3.tgz#6fb840d4a956e28b971c97b33a5deb73a6d5b6bb" - integrity sha512-Js7ueMZOVSZ3tP8C7E3KZiHv6QQl7lnJ+OkbxoaFazzSa2KyEHqApfGbU3XboUgUnq4ZuUmskUpYKTNx01fm5A== - dependencies: - expand-tilde "^2.0.2" - git-config-path "^1.0.1" - ini "^1.3.5" - -parse-github-url@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/parse-github-url/-/parse-github-url-1.0.2.tgz#242d3b65cbcdda14bb50439e3242acf6971db395" - integrity sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw== - parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" @@ -3598,23 +2555,6 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" -parse-link-header@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parse-link-header/-/parse-link-header-1.0.1.tgz#bedfe0d2118aeb84be75e7b025419ec8a61140a7" - integrity sha1-vt/g0hGK64S+deewJUGeyKYRQKc= - dependencies: - xtend "~4.0.1" - -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - path-exists@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" @@ -3637,11 +2577,6 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" @@ -3698,11 +2633,6 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= -pinpoint@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pinpoint/-/pinpoint-1.1.0.tgz#0cf7757a6977f1bf7f6a32207b709e377388e874" - integrity sha1-DPd1eml38b9/ajIge3CeN3OI6HQ= - pkg-dir@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -3724,11 +2654,6 @@ please-upgrade-node@^3.2.0: dependencies: semver-compare "^1.0.0" -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -3751,14 +2676,6 @@ prettier-linter-helpers@^1.0.0: resolved "https://registry.yarnpkg.com/wp-prettier/-/wp-prettier-2.2.1-beta-1.tgz#8afb761f83426bde870f692edc49adbd3e265118" integrity sha512-+JHkqs9LC/JPp51yy1hzs3lQ7qeuWCwOcSzpQNeeY/G7oSpnF61vxt7hRh87zNRTr6ob2ndy0W8rVzhgrcA+Gw== -prettyjson@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prettyjson/-/prettyjson-1.2.1.tgz#fcffab41d19cab4dfae5e575e64246619b12d289" - integrity sha1-/P+rQdGcq0365eV15kJGYZsS0ok= - dependencies: - colors "^1.1.2" - minimist "^1.2.0" - process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -3793,14 +2710,6 @@ psl@^1.1.28: resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -3811,15 +2720,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@^6.8.2: - version "6.13.8" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.8.tgz#8cf231759c85484da3cf05a851810d8e825c1159" - integrity sha512-jxJzQI2edQPE/NPUOusNjO/ZOGqr1o2OBa/3M00fU76FsLXDVbJDv/p7ng5OdQyorKrkRz1oqfwmbe5MAMePQg== - dependencies: - decode-uri-component "^0.2.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -3899,11 +2799,6 @@ readable-stream@^3.4.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readline-sync@^1.4.9: - version "1.4.10" - resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" - integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== - redent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" @@ -3917,14 +2812,6 @@ regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - regexp.prototype.flags@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" @@ -3943,16 +2830,6 @@ regextras@^0.7.1: resolved "https://registry.yarnpkg.com/regextras/-/regextras-0.7.1.tgz#be95719d5f43f9ef0b9fa07ad89b7c606995a3b2" integrity sha512-9YXf6xtW+qzQ+hcMQXx95MOvfqXFgsKDZodX3qZB0x2n5Z94ioetIITsBtvJbiOyxa/6s9AtyweBLCdPmPko/w== -repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - repeating@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" @@ -4011,11 +2888,6 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - resolve@^1.10.0, resolve@^1.12.0, resolve@^1.18.1: version "1.19.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" @@ -4040,16 +2912,6 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -retry@0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -4096,13 +2958,6 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -4153,7 +3008,7 @@ semver-regex@^3.1.2: resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.2.tgz#34b4c0d361eef262e07199dbef316d0f2ab11807" integrity sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA== -"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: +"semver@2 || 3 || 4 || 5": version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -4187,23 +3042,6 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -4211,11 +3049,6 @@ shebang-command@^2.0.0: dependencies: shebang-regex "^3.0.0" -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - shebang-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" @@ -4259,52 +3092,6 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - source-map@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" @@ -4312,7 +3099,7 @@ source-map@^0.4.2: dependencies: amdefine ">=0.0.4" -source-map@^0.5.0, source-map@^0.5.6: +source-map@^0.5.0: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -4348,18 +3135,6 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== -split-on-first@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" - integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -4380,14 +3155,6 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - stdout-stream@^1.4.0: version "1.4.1" resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" @@ -4395,11 +3162,6 @@ stdout-stream@^1.4.0: dependencies: readable-stream "^2.0.1" -strict-uri-encode@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" - integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= - string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -4513,11 +3275,6 @@ strip-bom@^2.0.0: dependencies: is-utf8 "^0.2.0" -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" @@ -4540,7 +3297,7 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^5.0.0, supports-color@^5.3.0: +supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -4561,14 +3318,6 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-hyperlinks@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz#71daedf36cc1060ac5100c351bb3da48c29c0ef7" - integrity sha512-HHi5kVSefKaJkGYXbDuKbUGRVxqnWGn3J2e39CYcNJEfWciGq2zYtOhXLTlvrOZW1QU7VX67w7fMmWafHX9Pfw== - dependencies: - has-flag "^2.0.0" - supports-color "^5.0.0" - symbol-observable@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -4615,21 +3364,6 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -4637,16 +3371,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -4655,13 +3379,6 @@ tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -tr46@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" - integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk= - dependencies: - punycode "^2.1.0" - tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -4720,49 +3437,11 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -universal-url@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universal-url/-/universal-url-2.0.0.tgz#35e7fc2c3374804905cee67ea289ed3a47669809" - integrity sha512-3DLtXdm/G1LQMCnPj+Aw7uDoleQttNHp2g5FnNQKR6cP6taNWS1b/Ehjjx4PVyvejKi3TJyu8iBraKM4q3JQPg== - dependencies: - hasurl "^1.0.0" - whatwg-url "^7.0.0" - -universal-user-agent@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-4.0.1.tgz#fd8d6cb773a679a709e967ef8288a31fcc03e557" - integrity sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg== - dependencies: - os-name "^3.1.0" - -universal-user-agent@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" - integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -4770,16 +3449,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -4817,20 +3486,6 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -webidl-conversions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" - integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== - -whatwg-url@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06" - integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg== - dependencies: - lodash.sortby "^4.7.0" - tr46 "^1.0.1" - webidl-conversions "^4.0.2" - which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -4862,13 +3517,6 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" -windows-release@^3.1.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.3.3.tgz#1c10027c7225743eec6b89df160d64c2e0293999" - integrity sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg== - dependencies: - execa "^1.0.0" - word-wrap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" @@ -4905,11 +3553,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - y18n@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" From 9af55f16abe35caaff2e967023f2e61765804e4e Mon Sep 17 00:00:00 2001 From: Jeremy Herve Date: Fri, 19 Feb 2021 09:02:47 +0100 Subject: [PATCH 18/34] Actions: do not add milestone info to comment when undefined (#18881) Follow-up from #18558. When we do not have any plugin info, let's not try to add anything to the comment. --- .../repo-gardening/src/tasks/check-description/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/actions/repo-gardening/src/tasks/check-description/index.js b/.github/actions/repo-gardening/src/tasks/check-description/index.js index f29b7bfea0cb4..e3a415c997810 100644 --- a/.github/actions/repo-gardening/src/tasks/check-description/index.js +++ b/.github/actions/repo-gardening/src/tasks/check-description/index.js @@ -248,7 +248,9 @@ Once you’ve done so, switch to the "[Status] Needs Review" label; someone from // Gather info about the next release for that plugin. const milestoneInfo = await buildMilestoneInfo( octokit, owner, repo, number ); - comment += milestoneInfo; + if ( milestoneInfo ) { + comment += milestoneInfo; + } // Look for an existing check-description task comment. const existingComment = await getCheckComment( octokit, owner, repo, number ); From 12a6d0d94eed577940707653ecd3b74f898c028c Mon Sep 17 00:00:00 2001 From: Jeremy Herve Date: Fri, 19 Feb 2021 10:43:01 +0100 Subject: [PATCH 19/34] Actions: do not run label and comment automations on closed PRs (#18882) Follow-up to #18558. We do not have to keep editing labels or comments on PRs that are closed. --- .../repo-gardening/src/if-not-closed.js | 27 +++++++++++++++++++ .github/actions/repo-gardening/src/index.js | 5 ++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 .github/actions/repo-gardening/src/if-not-closed.js diff --git a/.github/actions/repo-gardening/src/if-not-closed.js b/.github/actions/repo-gardening/src/if-not-closed.js new file mode 100644 index 0000000000000..cd2f7330519cd --- /dev/null +++ b/.github/actions/repo-gardening/src/if-not-closed.js @@ -0,0 +1,27 @@ +/** + * Internal dependencies + */ +const debug = require( './debug' ); + +/* global WPAutomationTask */ + +/** + * Higher-order function which executes and returns the result of the given + * handler only if the PR is not currently closed. + * + * @param {WPAutomationTask} handler - Original task. + * + * @returns {WPAutomationTask} Enhanced task. + */ +function ifNotClosed( handler ) { + const newHandler = ( payload, octokit ) => { + if ( payload.pull_request.state !== 'closed' ) { + return handler( payload, octokit ); + } + debug( `main: Skipping ${ handler.name } because the PR is closed.` ); + }; + Object.defineProperty( newHandler, 'name', { value: handler.name } ); + return newHandler; +} + +module.exports = ifNotClosed; diff --git a/.github/actions/repo-gardening/src/index.js b/.github/actions/repo-gardening/src/index.js index 378ab60579a92..5b560875cfe28 100644 --- a/.github/actions/repo-gardening/src/index.js +++ b/.github/actions/repo-gardening/src/index.js @@ -13,6 +13,7 @@ const addLabels = require( './tasks/add-labels' ); const checkDescription = require( './tasks/check-description' ); const debug = require( './debug' ); const ifNotFork = require( './if-not-fork' ); +const ifNotClosed = require( './if-not-closed' ); const automations = [ { @@ -27,12 +28,12 @@ const automations = [ { event: 'pull_request', action: [ 'opened', 'reopened', 'synchronize', 'edited', 'labeled' ], - task: addLabels, + task: ifNotClosed( addLabels ), }, { event: 'pull_request', action: [ 'opened', 'reopened', 'synchronize', 'edited', 'labeled' ], - task: checkDescription, + task: ifNotClosed( checkDescription ), }, ]; From b92031de5e0cc0c2a058a61810c28e0f0a79f7a0 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 19 Feb 2021 22:35:42 +0800 Subject: [PATCH 20/34] Avoid unnecessary converting of booleans to strings in the WordAds module (#18823) --- .../jetpack/modules/wordads/php/class-wordads-api.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/plugins/jetpack/modules/wordads/php/class-wordads-api.php b/projects/plugins/jetpack/modules/wordads/php/class-wordads-api.php index f25bbe0651358..a5e565e8933ad 100644 --- a/projects/plugins/jetpack/modules/wordads/php/class-wordads-api.php +++ b/projects/plugins/jetpack/modules/wordads/php/class-wordads-api.php @@ -94,7 +94,7 @@ public static function is_wordads_approved() { self::get_wordads_status(); } - return self::$wordads_status['approved'] ? '1' : '0'; + return (bool) self::$wordads_status['approved']; } /** @@ -109,7 +109,7 @@ public static function is_wordads_active() { self::get_wordads_status(); } - return self::$wordads_status['active'] ? '1' : '0'; + return (bool) self::$wordads_status['active']; } /** @@ -124,7 +124,7 @@ public static function is_wordads_house() { self::get_wordads_status(); } - return self::$wordads_status['house'] ? '1' : '0'; + return (bool) self::$wordads_status['house']; } /** @@ -139,7 +139,7 @@ public static function is_wordads_unsafe() { self::get_wordads_status(); } - return self::$wordads_status['unsafe'] ? '1' : '0'; + return (bool) self::$wordads_status['unsafe']; } /** From 26efa77fa46b5d2fe647b6ef8d2d9e52ba373eb7 Mon Sep 17 00:00:00 2001 From: Miguel Torres Date: Fri, 19 Feb 2021 16:17:57 +0100 Subject: [PATCH 21/34] Keyring: Register Sharing page without menu (#18861) --- .../class.jetpack-keyring-service-helper.php | 93 +++++++++++-------- .../wpcom-endpoints/publicize-services.php | 11 +-- 2 files changed, 57 insertions(+), 47 deletions(-) diff --git a/projects/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php b/projects/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php index 720f59a979f2a..a0e391633d435 100644 --- a/projects/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php +++ b/projects/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php @@ -6,6 +6,13 @@ class Jetpack_Keyring_Service_Helper { **/ private static $instance = null; + /** + * Whether the `sharing` page is registered. + * + * @var bool + */ + private static $is_sharing_page_registered = false; + static function init() { if ( is_null( self::$instance ) ) { self::$instance = new Jetpack_Keyring_Service_Helper; @@ -42,33 +49,32 @@ static function init() { * Constructor */ private function __construct() { - add_action( 'admin_menu', array( __CLASS__, 'add_sharing_menu' ), 21 ); + add_action( 'admin_menu', array( __CLASS__, 'register_sharing_page' ) ); add_action( 'load-settings_page_sharing', array( __CLASS__, 'admin_page_load' ), 9 ); } /** - * We need a `sharing` submenu page to be able to connect and disconnect services. + * We need a `sharing` page to be able to connect and disconnect services. */ - public static function add_sharing_menu() { - global $submenu; - - if ( - ! isset( $submenu['options-general.php'] ) - || ! is_array( $submenu['options-general.php'] ) - ) { + public static function register_sharing_page() { + if ( self::$is_sharing_page_registered ) { return; } - $general_settings_names = array_map( - function ( $menu ) { - return array_values( $menu )[0]; - }, - $submenu['options-general.php'] - ); - if ( ! in_array( 'Sharing', $general_settings_names, true ) ) { - add_submenu_page( 'options-general.php', '', '', 'manage_options', 'sharing', '__return_empty_string' ); + self::$is_sharing_page_registered = true; + + if ( ! current_user_can( 'manage_options' ) ) { + return; } + + global $_registered_pages; + + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + + $hookname = get_plugin_page_hookname( 'sharing', 'options-general.php' ); + add_action( $hookname, array( __CLASS__, 'admin_page_load' ) ); + $_registered_pages[ $hookname ] = true; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited } function get_services( $filter = 'all' ) { @@ -118,34 +124,43 @@ static function api_url( $service = false, $params = array() ) { } static function connect_url( $service_name, $for ) { - return add_query_arg( array( - 'action' => 'request', - 'service' => $service_name, - 'kr_nonce' => wp_create_nonce( 'keyring-request' ), - 'nonce' => wp_create_nonce( "keyring-request-$service_name" ), - 'for' => $for, - ), menu_page_url( 'sharing', false ) ); + return add_query_arg( + array( + 'action' => 'request', + 'service' => $service_name, + 'kr_nonce' => wp_create_nonce( 'keyring-request' ), + 'nonce' => wp_create_nonce( "keyring-request-$service_name" ), + 'for' => $for, + ), + admin_url( 'options-general.php?page=sharing' ) + ); } static function refresh_url( $service_name, $for ) { - return add_query_arg( array( - 'action' => 'request', - 'service' => $service_name, - 'kr_nonce' => wp_create_nonce( 'keyring-request' ), - 'refresh' => 1, - 'for' => $for, - 'nonce' => wp_create_nonce( "keyring-request-$service_name" ), - ), admin_url( 'options-general.php?page=sharing' ) ); + return add_query_arg( + array( + 'action' => 'request', + 'service' => $service_name, + 'kr_nonce' => wp_create_nonce( 'keyring-request' ), + 'refresh' => 1, + 'for' => $for, + 'nonce' => wp_create_nonce( "keyring-request-$service_name" ), + ), + admin_url( 'options-general.php?page=sharing' ) + ); } static function disconnect_url( $service_name, $id ) { - return add_query_arg( array( - 'action' => 'delete', - 'service' => $service_name, - 'id' => $id, - 'kr_nonce' => wp_create_nonce( 'keyring-request' ), - 'nonce' => wp_create_nonce( "keyring-request-$service_name" ), - ), menu_page_url( 'sharing', false ) ); + return add_query_arg( + array( + 'action' => 'delete', + 'service' => $service_name, + 'id' => $id, + 'kr_nonce' => wp_create_nonce( 'keyring-request' ), + 'nonce' => wp_create_nonce( "keyring-request-$service_name" ), + ), + admin_url( 'options-general.php?page=sharing' ) + ); } static function admin_page_load() { diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-services.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-services.php index 4641b21847561..d199fa25145bd 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-services.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/publicize-services.php @@ -88,15 +88,10 @@ public function get_items( $request ) { global $publicize; /** * We need this because Publicize::get_available_service_data() uses `Jetpack_Keyring_Service_Helper` - * and `Jetpack_Keyring_Service_Helper` relies on `menu_page_url()`. - * - * We also need add_submenu_page(), as the URLs for connecting each service - * rely on the `sharing` menu subpage being present. + * and `Jetpack_Keyring_Service_Helper` needs a `sharing` page to be registered. */ - include_once ABSPATH . 'wp-admin/includes/plugin.php'; - - // The `sharing` submenu page must exist for service connect URLs to be correct. - add_submenu_page( 'options-general.php', '', '', 'manage_options', 'sharing', '__return_empty_string' ); + jetpack_require_lib( 'class.jetpack-keyring-service-helper' ); + Jetpack_Keyring_Service_Helper::register_sharing_page(); $services_data = $publicize->get_available_service_data(); From 41a5e9e46faecfea12dbc637ae40619f60afb393 Mon Sep 17 00:00:00 2001 From: Matthew Denton Date: Fri, 19 Feb 2021 11:20:00 -0500 Subject: [PATCH 22/34] Update Checksums to support blacklisted taxonomies. (#18779) * Update Checksums to support blacklisted taxonomies. new structured helper functions in Settings join field settings to support id and range differences remove range query from parent join * Remove unnecessary operator `<>` when it could be replaced with `NOT IN` * Remove the parent table range exceptions and replace them with nulls on the call Co-authored-by: Biser Perchinkov --- projects/packages/sync/src/class-settings.php | 17 ++++ .../src/replicastore/class-table-checksum.php | 78 +++++++++++++------ 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/projects/packages/sync/src/class-settings.php b/projects/packages/sync/src/class-settings.php index f4fcc72913c6a..2ed069cb71940 100644 --- a/projects/packages/sync/src/class-settings.php +++ b/projects/packages/sync/src/class-settings.php @@ -299,6 +299,23 @@ public static function get_allowed_post_meta_structured() { ); } + /** + * Returns structured SQL clause for blacklisted taxonomies. + * + * @access public + * @static + * + * @return array taxonomies filter values + */ + public static function get_blacklisted_taxonomies_structured() { + return array( + 'taxonomy' => array( + 'operator' => 'NOT IN', + 'values' => array_map( 'esc_sql', self::get_setting( 'taxonomies_blacklist' ) ), + ), + ); + } + /** * Returns escaped SQL for blacklisted comment meta. * Can be injected directly into a WHERE clause. diff --git a/projects/packages/sync/src/replicastore/class-table-checksum.php b/projects/packages/sync/src/replicastore/class-table-checksum.php index 864fc94f66c1d..538af2ee391b9 100644 --- a/projects/packages/sync/src/replicastore/class-table-checksum.php +++ b/projects/packages/sync/src/replicastore/class-table-checksum.php @@ -94,6 +94,20 @@ class Table_Checksum { */ private $parent_table = null; + /** + * What field to use for the parent table join, if it has a "parent" table. + * + * @var mixed|null + */ + private $parent_join_field = null; + + /** + * What field to use for the table join, if it has a "parent" table. + * + * @var mixed|null + */ + private $table_join_field = null; + /** * Table_Checksum constructor. * @@ -144,12 +158,14 @@ private function get_default_tables() { 'filter_values' => Sync\Settings::get_disallowed_post_types_structured(), ), 'postmeta' => array( - 'table' => $wpdb->postmeta, - 'range_field' => 'post_id', - 'key_fields' => array( 'post_id', 'meta_key' ), - 'checksum_fields' => array( 'meta_key', 'meta_value' ), - 'filter_values' => Sync\Settings::get_allowed_post_meta_structured(), - 'parent_table' => 'posts', + 'table' => $wpdb->postmeta, + 'range_field' => 'post_id', + 'key_fields' => array( 'post_id', 'meta_key' ), + 'checksum_fields' => array( 'meta_key', 'meta_value' ), + 'filter_values' => Sync\Settings::get_allowed_post_meta_structured(), + 'parent_table' => 'posts', + 'parent_join_field' => 'ID', + 'table_join_field' => 'post_id', ), 'comments' => array( 'table' => $wpdb->comments, @@ -157,48 +173,58 @@ private function get_default_tables() { 'key_fields' => array( 'comment_ID' ), 'checksum_fields' => array( 'comment_date_gmt' ), 'filter_values' => array( - 'comment_type' => array( + 'comment_type' => array( 'operator' => 'IN', 'values' => apply_filters( 'jetpack_sync_whitelisted_comment_types', array( '', 'comment', 'trackback', 'pingback', 'review' ) ), ), + 'comment_approved' => array( + 'operator' => 'NOT IN', + 'values' => array( 'spam' ), + ), ), - 'filter_sql' => Sync\Settings::get_comments_filter_sql(), ), 'commentmeta' => array( - 'table' => $wpdb->commentmeta, - 'range_field' => 'comment_id', - 'key_fields' => array( 'comment_id', 'meta_key' ), - 'checksum_fields' => array( 'meta_key', 'meta_value' ), - 'filter_values' => Sync\Settings::get_allowed_comment_meta_structured(), - 'parent_table' => 'comments', + 'table' => $wpdb->commentmeta, + 'range_field' => 'comment_id', + 'key_fields' => array( 'comment_id', 'meta_key' ), + 'checksum_fields' => array( 'meta_key', 'meta_value' ), + 'filter_values' => Sync\Settings::get_allowed_comment_meta_structured(), + 'parent_table' => 'comments', + 'parent_join_field' => 'comment_ID', + 'table_join_field' => 'comment_id', ), 'terms' => array( 'table' => $wpdb->terms, 'range_field' => 'term_id', 'key_fields' => array( 'term_id' ), 'checksum_fields' => array( 'term_id', 'name', 'slug' ), + 'parent_table' => 'term_taxonomy', ), 'termmeta' => array( 'table' => $wpdb->termmeta, 'range_field' => 'term_id', 'key_fields' => array( 'term_id', 'meta_key' ), 'checksum_fields' => array( 'meta_key', 'meta_value' ), - 'parent_table' => 'terms', + 'parent_table' => 'term_taxonomy', ), 'term_relationships' => array( - 'table' => $wpdb->term_relationships, - 'range_field' => 'object_id', - 'key_fields' => array( 'object_id' ), - 'checksum_fields' => array( 'object_id', 'term_taxonomy_id' ), + 'table' => $wpdb->term_relationships, + 'range_field' => 'object_id', + 'key_fields' => array( 'object_id' ), + 'checksum_fields' => array( 'object_id', 'term_taxonomy_id' ), + 'parent_table' => 'term_taxonomy', + 'parent_join_field' => 'term_taxonomy_id', + 'table_join_field' => 'term_taxonomy_id', ), 'term_taxonomy' => array( 'table' => $wpdb->term_taxonomy, 'range_field' => 'term_taxonomy_id', 'key_fields' => array( 'term_taxonomy_id' ), 'checksum_fields' => array( 'term_taxonomy_id', 'term_id', 'taxonomy', 'description', 'parent' ), + 'filter_values' => Sync\Settings::get_blacklisted_taxonomies_structured(), ), 'links' => $wpdb->links, // TODO describe in the array format or add exceptions. 'options' => $wpdb->options, // TODO describe in the array format or add exceptions. @@ -217,6 +243,8 @@ private function prepare_fields( $table_configuration ) { $this->filter_values = isset( $table_configuration['filter_values'] ) ? $table_configuration['filter_values'] : null; $this->additional_filter_sql = ! empty( $table_configuration['filter_sql'] ) ? $table_configuration['filter_sql'] : ''; $this->parent_table = isset( $table_configuration['parent_table'] ) ? $table_configuration['parent_table'] : null; + $this->parent_join_field = isset( $table_configuration['parent_join_field'] ) ? $table_configuration['parent_join_field'] : $table_configuration['range_field']; + $this->table_join_field = isset( $table_configuration['table_join_field'] ) ? $table_configuration['table_join_field'] : $table_configuration['range_field']; } /** @@ -331,7 +359,6 @@ private function prepare_filter_values_as_sql( $filter_values = array(), $table_ $result[] = $prepared_statement; break; - // TODO implement other operators if needed. } } @@ -435,7 +462,12 @@ private function build_checksum_query( $range_from = null, $range_to = null, $fi $key_fields = implode( ',', $key_fields ); // Prepare the checksum fields. - $checksum_fields_string = implode( ',', array_merge( $this->checksum_fields, array( $salt ) ) ); + $checksum_fields = array(); + // Prefix the fields with the table name, to avoid clashes in queries with sub-queries (e.g. meta tables). + foreach ( $this->checksum_fields as $field ) { + $checksum_fields[] = $this->table . '.' . $field; + } + $checksum_fields_string = implode( ',', array_merge( $checksum_fields, array( $salt ) ) ); $additional_fields = ''; if ( $granular_result ) { @@ -451,10 +483,10 @@ private function build_checksum_query( $range_from = null, $range_to = null, $fi $join_statement = ''; if ( $this->parent_table ) { $parent_table_obj = new Table_Checksum( $this->parent_table ); - $parent_filter_query = $parent_table_obj->build_filter_statement( $range_from, $range_to, null, 'parent_table' ); + $parent_filter_query = $parent_table_obj->build_filter_statement( null, null, null, 'parent_table' ); $join_statement = " - INNER JOIN {$parent_table_obj->table} as parent_table ON ({$this->table}.{$this->range_field} = parent_table.{$parent_table_obj->range_field} AND {$parent_filter_query}) + INNER JOIN {$parent_table_obj->table} as parent_table ON ({$this->table}.{$this->table_join_field} = parent_table.{$this->parent_join_field} AND {$parent_filter_query}) "; } From 099d3094083ae1132c0004151bf70cdb002e43ec Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Fri, 19 Feb 2021 12:13:43 -0500 Subject: [PATCH 23/34] CLI: Fix CodeQL warning (#18877) We tried to match the regex documented at https://getcomposer.org/doc/04-schema.md#name for allowed package names. But somehow we lost the `/` in the middle. We really only wanted the part after the slash: `^[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$` But that wouldn't satisfy CodeQL. We could simplify that to the more efficient `^[a-z0-9]+(([_.]|-{1,2})[a-z0-9]+)*$` But that's not what Composer is actually using (or, AFAICT, has ever used). They just use `^[a-z0-9_.-]+$`, see https://github.com/composer/composer/blob/03e8cacd/src/Composer/Command/InitCommand.php#L239 --- tools/cli/helpers/projectHelpers.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/cli/helpers/projectHelpers.js b/tools/cli/helpers/projectHelpers.js index 501306800b90a..b0d9c30f7903d 100644 --- a/tools/cli/helpers/projectHelpers.js +++ b/tools/cli/helpers/projectHelpers.js @@ -38,9 +38,7 @@ export function allProjects() { */ export function checkNameValid( dir, newName ) { const existingNames = dirs( './projects/' + pluralize( dir ) ); - const validCharacters = new RegExp( - '^[a-z0-9]([_.-]?[a-z0-9]+)*[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$' - ); + const validCharacters = new RegExp( '^[a-z0-9_.-]+$' ); if ( newName.length === 0 ) { console.error( chalk.red( 'Name must have a value.' ) ); throw new Error( 'Name must have a value' ); From 9bb3fe668f916e725a07427b0c43671c545eaea1 Mon Sep 17 00:00:00 2001 From: Jeremy Herve Date: Fri, 19 Feb 2021 18:16:31 +0100 Subject: [PATCH 24/34] Issues templates: update label name for Beta plugin (#18884) --- .github/ISSUE_TEMPLATE/plugin_jetpack-beta.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/plugin_jetpack-beta.md b/.github/ISSUE_TEMPLATE/plugin_jetpack-beta.md index 30ca83dc5207f..3a703f003b60a 100644 --- a/.github/ISSUE_TEMPLATE/plugin_jetpack-beta.md +++ b/.github/ISSUE_TEMPLATE/plugin_jetpack-beta.md @@ -2,7 +2,7 @@ name: Plugin - Jetpack Beta Tester about: Create an issue report focused on the Jetpack Beta Tester plugin title: 'Beta Plugin: ADD_YOUR_TITLE_HERE' -labels: '[Plugin] Beta Plugin, [Type] Bug' +labels: '[Plugin] Beta, [Type] Bug' assignees: '' --- From 1b7c77513de0f43ca9d76ca986e141c8b7f19f86 Mon Sep 17 00:00:00 2001 From: Paul Bunkham Date: Fri, 19 Feb 2021 18:09:11 +0000 Subject: [PATCH 25/34] Podcast API Endpoint: Add the publish date to the track information. (#18855) * Add the publish date to the track information. * Update the documentation and ensure null for unknown values. * Update the date format to Atom * Make the publish_date API doc more specific * Add the format for validation of the publish_date Co-authored-by: Grant Kinney Co-authored-by: Grant Kinney --- .../jetpack/_inc/lib/class-jetpack-podcast-helper.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/projects/plugins/jetpack/_inc/lib/class-jetpack-podcast-helper.php b/projects/plugins/jetpack/_inc/lib/class-jetpack-podcast-helper.php index 6c7343e51bc74..0eff57a883753 100644 --- a/projects/plugins/jetpack/_inc/lib/class-jetpack-podcast-helper.php +++ b/projects/plugins/jetpack/_inc/lib/class-jetpack-podcast-helper.php @@ -325,6 +325,7 @@ protected function setup_tracks_callback( SimplePie_Item $episode ) { return array(); } + $publish_date = $episode->get_gmdate( DATE_ATOM ); // Build track data. $track = array( 'id' => wp_unique_id( 'podcast-track-' ), @@ -336,6 +337,7 @@ protected function setup_tracks_callback( SimplePie_Item $episode ) { 'title' => $this->get_plain_text( $episode->get_title() ), 'image' => esc_url( $this->get_episode_image_url( $episode ) ), 'guid' => $this->get_plain_text( $episode->get_id() ), + 'publish_date' => $publish_date ? $publish_date : null, ); if ( empty( $track['title'] ) ) { @@ -469,6 +471,11 @@ public static function get_tracks_schema() { 'description' => __( 'The episode title.', 'jetpack' ), 'type' => 'string', ), + 'publish_date' => array( + 'description' => __( 'The UTC publish date and time of the episode', 'jetpack' ), + 'type' => 'string', + 'format' => 'date-time', + ), ), ), ); From 8193af7f3ef6bb18718283def07c2f18c4179307 Mon Sep 17 00:00:00 2001 From: leogermani Date: Fri, 19 Feb 2021 18:16:28 -0300 Subject: [PATCH 26/34] skip master user tests when in userless mode (#18887) --- .../lib/debugger/class-jetpack-cxn-tests.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/projects/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php b/projects/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php index 66b2380efa76e..6ac29e54e9cae 100644 --- a/projects/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php +++ b/projects/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php @@ -297,6 +297,14 @@ protected function test__master_user_exists_on_site() { ) ); } + if ( ! ( new Connection_Manager() )->get_connection_owner_id() && ( new Status() )->is_no_user_testing_mode() ) { + return self::skipped_test( + array( + 'name' => $name, + 'short_description' => __( 'Jetpack is running in userless mode. No master user to check.', 'jetpack' ), + ) + ); + } $local_user = $this->helper_retrieve_local_master_user(); if ( $local_user->exists() ) { @@ -327,6 +335,14 @@ protected function test__master_user_can_manage_options() { ) ); } + if ( ! ( new Connection_Manager() )->get_connection_owner_id() && ( new Status() )->is_no_user_testing_mode() ) { + return self::skipped_test( + array( + 'name' => $name, + 'short_description' => __( 'Jetpack is running in userless mode. No master user to check.', 'jetpack' ), + ) + ); + } $master_user = $this->helper_retrieve_local_master_user(); if ( user_can( $master_user, 'manage_options' ) ) { From 8f030b8afe8f0287832b1989cc74238b6298f198 Mon Sep 17 00:00:00 2001 From: robertf4 <42627630+robertf4@users.noreply.github.com> Date: Mon, 22 Feb 2021 03:11:49 -0600 Subject: [PATCH 27/34] Recommendations: Hide JITMs when banner is displaying (#18853) --- projects/packages/jitm/src/class-jitm.php | 17 ++++---- .../class-jetpack-recommendations-banner.php | 2 +- .../plugins/jetpack/class.jetpack-admin.php | 43 +++++++++++++++++++ .../scss/jetpack-recommendations-banner.scss | 4 -- 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/projects/packages/jitm/src/class-jitm.php b/projects/packages/jitm/src/class-jitm.php index cb2a4c3a9ac20..7c11410e246b1 100644 --- a/projects/packages/jitm/src/class-jitm.php +++ b/projects/packages/jitm/src/class-jitm.php @@ -80,14 +80,15 @@ public function register() { * @param \WP_Screen $screen WP Core's screen object. */ public function prepare_jitms( $screen ) { - if ( ! in_array( - $screen->id, - array( - 'jetpack_page_akismet-key-config', - 'admin_page_jetpack_modules', - ), - true - ) ) { + /** + * Filter to hide JITMs on certain screens. + * + * @since 9.5.0 + * + * @param bool true Whether to show just in time messages. + * @param string $string->id The ID of the current screen. + */ + if ( apply_filters( 'jetpack_display_jitms_on_screen', true, $screen->id ) ) { add_action( 'admin_enqueue_scripts', array( $this, 'jitm_enqueue_files' ) ); add_action( 'admin_notices', array( $this, 'ajax_message' ) ); add_action( 'edit_form_top', array( $this, 'ajax_message' ) ); diff --git a/projects/plugins/jetpack/class-jetpack-recommendations-banner.php b/projects/plugins/jetpack/class-jetpack-recommendations-banner.php index d9d8fae384261..dfd01a8629754 100644 --- a/projects/plugins/jetpack/class-jetpack-recommendations-banner.php +++ b/projects/plugins/jetpack/class-jetpack-recommendations-banner.php @@ -54,7 +54,7 @@ public function maybe_initialize_hooks() { /** * Determines if the banner can be displayed */ - private function can_be_displayed() { + public static function can_be_displayed() { if ( ! Jetpack_Recommendations::is_banner_enabled() ) { return false; } diff --git a/projects/plugins/jetpack/class.jetpack-admin.php b/projects/plugins/jetpack/class.jetpack-admin.php index bdbbae59d2f83..d0e3191cd8962 100644 --- a/projects/plugins/jetpack/class.jetpack-admin.php +++ b/projects/plugins/jetpack/class.jetpack-admin.php @@ -76,6 +76,8 @@ function () { add_action( 'admin_enqueue_scripts', array( $this, 'akismet_logo_replacement_styles' ) ); } } + + add_filter( 'jetpack_display_jitms_on_screen', array( $this, 'should_display_jitms_on_screen' ), 10, 2 ); } /** @@ -328,5 +330,46 @@ function debugger_page() { jetpack_require_lib( 'debugger' ); Jetpack_Debugger::jetpack_debug_display_handler(); } + + /** + * Determines if JITMs should display on a particular screen. + * + * @param bool $value The default value of the filter. + * @param string $screen_id The ID of the screen being tested for JITM display. + * + * @return bool True if JITMs should display, false otherwise. + */ + public function should_display_jitms_on_screen( $value, $screen_id ) { + // Disable all JITMs on these pages. + if ( + in_array( + $screen_id, + array( + 'jetpack_page_akismet-key-config', + 'admin_page_jetpack_modules', + ), + true + ) ) { + return false; + } + + // Disable all JITMs on pages where the recommendations banner is displaying. + if ( + in_array( + $screen_id, + array( + 'dashboard', + 'plugins', + 'jetpack_page_stats', + ), + true + ) + && \Jetpack_Recommendations_Banner::can_be_displayed() + ) { + return false; + } + + return $value; + } } Jetpack_Admin::init(); diff --git a/projects/plugins/jetpack/scss/jetpack-recommendations-banner.scss b/projects/plugins/jetpack/scss/jetpack-recommendations-banner.scss index e1d9ea41469c8..98c08f528b13b 100644 --- a/projects/plugins/jetpack/scss/jetpack-recommendations-banner.scss +++ b/projects/plugins/jetpack/scss/jetpack-recommendations-banner.scss @@ -202,7 +202,3 @@ width: 75%; margin: 10%; } - -.jitm-banner { - display: none !important; -} From c52e77b1b4151e53a430b2d6ceb0adfb4e468faf Mon Sep 17 00:00:00 2001 From: Daniel Walmsley Date: Mon, 22 Feb 2021 01:47:22 -0800 Subject: [PATCH 28/34] Refactor secrets and tokens (#18812) --- .../connection/docs/authorize-user.md | 4 +- .../packages/connection/docs/register-site.md | 18 +- .../legacy/class-jetpack-xmlrpc-server.php | 26 +- .../packages/connection/src/class-client.php | 3 +- .../packages/connection/src/class-manager.php | 858 +++--------------- .../packages/connection/src/class-secrets.php | 268 ++++++ .../packages/connection/src/class-tokens.php | 548 +++++++++++ .../packages/connection/src/class-utils.php | 17 +- .../tests/php/test-rest-endpoints.php | 6 +- .../tests/php/test_Manager_integration.php | 9 +- .../tests/php/test_Manager_unit.php | 19 +- .../jetpack/_inc/class.jetpack-provision.php | 9 +- .../lib/class.core-rest-api-endpoints.php | 8 +- .../class.jetpack-keyring-service-helper.php | 4 +- .../lib/debugger/class-jetpack-cxn-tests.php | 5 +- .../lib/debugger/class-jetpack-debug-data.php | 7 +- .../plugins/jetpack/class.jetpack-cli.php | 10 +- .../plugins/jetpack/class.jetpack-data.php | 19 +- .../plugins/jetpack/class.jetpack-network.php | 6 +- projects/plugins/jetpack/class.jetpack.php | 136 +-- ...ass-jetpack-token-subscription-service.php | 5 +- ...jetpack-json-api-user-connect-endpoint.php | 4 +- projects/plugins/jetpack/load-jetpack.php | 1 - .../jetpack/modules/comments/comments.php | 6 +- .../class-jetpack-post-by-email.php | 4 +- .../modules/publicize/publicize-jetpack.php | 2 +- .../class-jetpack-wpcom-block-editor.php | 5 +- .../test_class.jetpack-client-server.php | 1 - .../php/general/test_class.jetpack-data.php | 231 ----- .../test_class.jetpack-xmlrpc-server.php | 7 +- .../tests/php/general/test_class.jetpack.php | 92 -- .../test_class-jetpack-wpcom-block-editor.php | 10 +- tools/phpcs-excludelist.json | 2 - 33 files changed, 1061 insertions(+), 1289 deletions(-) create mode 100644 projects/packages/connection/src/class-secrets.php create mode 100644 projects/packages/connection/src/class-tokens.php delete mode 100644 projects/plugins/jetpack/tests/php/general/test_class.jetpack-data.php diff --git a/projects/packages/connection/docs/authorize-user.md b/projects/packages/connection/docs/authorize-user.md index 8a6db8360af38..de4c9db44c848 100644 --- a/projects/packages/connection/docs/authorize-user.md +++ b/projects/packages/connection/docs/authorize-user.md @@ -14,8 +14,8 @@ use Automattic\Jetpack\Connection\Manager; // Getting the existing blog token created at registration step. $manager = new Manager( 'plugin-slug' ); -$blog_token = $manager->get_access_token(); -$user_token = $manager->get_access_token( get_current_user_id() ); +$blog_token = ( new Tokens() )->get_access_token(); +$user_token = ( new Tokens() )->get_access_token( get_current_user_id() ); $auth_url = $manager->get_authorization_url(); // Checking if the user is already connected. If not, the token will diff --git a/projects/packages/connection/docs/register-site.md b/projects/packages/connection/docs/register-site.md index a062db702c08b..e20d139273de5 100644 --- a/projects/packages/connection/docs/register-site.md +++ b/projects/packages/connection/docs/register-site.md @@ -1,14 +1,14 @@ # Register the site / Create blog token -This means registering the site with WordPress.com. It will create a "site (or 'blog') token" on both sides, establishing a secure two-way communication path. +This means registering the site with WordPress.com. It will create a "site (or 'blog') token" on both sides, establishing a secure two-way communication path. -The blog token is required in order to [authenticate at a user level](authorize-user.md) later (link to user auth docs here), so let's learn the simplest way we can do that in your plugin. +The blog token is required in order to [authenticate at a user level](authorize-user.md) later (link to user auth docs here), so let's learn the simplest way we can do that in your plugin. ### Install the right packages First, let's make sure that the `automattic/jetpack-connection` package is set up in your composer.json file: -At minimum you need three things. One is the `automattic/jetpack-autoloader` package, which will ensure that you're not colliding with any other plugins on the site that may be including the same packages. Two, of course, is the `automattic/jetpack-connection` package. Third is our `automattic/jetpack-config` package that will be your tool for initializing the packages. +At minimum you need three things. One is the `automattic/jetpack-autoloader` package, which will ensure that you're not colliding with any other plugins on the site that may be including the same packages. Two, of course, is the `automattic/jetpack-connection` package. Third is our `automattic/jetpack-config` package that will be your tool for initializing the packages. We recommend that you always use the latest published versions of our packages, but you can also run our latest master branch builds: ``` @@ -71,11 +71,11 @@ function your_plugin_register_site() { check_admin_referer( 'register-site' ); $manager = new Manager( 'plugin-slug' ); - // Mark the plugin connection as enabled, in case it was disabled earlier. + // Mark the plugin connection as enabled, in case it was disabled earlier. $manager->enable_plugin(); // If the token doesn't exist (see "Soft and Hard Disconnect" section below), we need to register the site. - if ( ! $manager->get_access_token() ) { + if ( ! ( new Tokens() )->get_access_token() ) { $manager->register(); } @@ -97,11 +97,11 @@ How are people supposed to register without something to click? I guess they wou ### Voila! -And that's it! You've just created a button that will register a WordPress site with WordPress.com. Now the only thing left to do is [authorize the user](authorize-user.md). +And that's it! You've just created a button that will register a WordPress site with WordPress.com. Now the only thing left to do is [authorize the user](authorize-user.md). ### Disconnect the site -When disconnecting the site, we recommend also clearing the remaining user tokens, since those won't work anyway and may cause problems on reconnection. +When disconnecting the site, we recommend also clearing the remaining user tokens, since those won't work anyway and may cause problems on reconnection. ```php use Automattic\Jetpack\Connection\Manager; @@ -179,7 +179,7 @@ However, the user explicitly requested the plugin to be disabled, so you need to ```php $manager = new Manager( 'plugin-slug' ); -if ( $manager->is_plugin_enabled() && $manager->get_access_token() ) { +if ( $manager->is_plugin_enabled() && ( new Tokens() )->get_access_token() ) { // Perform the API requests. } else { // Assume the plugin is disconnected, no matter if the tokens actually exist. @@ -193,7 +193,7 @@ If the plugin was *softly* disconnected, removing the flag is enough for it to w $manager = new Manager( 'plugin-slug' ); $manager->enable_plugin(); -if ( ! $manager->get_access_token() ) { +if ( ! ( new Tokens() )->get_access_token() ) { $manager->register(); } ``` diff --git a/projects/packages/connection/legacy/class-jetpack-xmlrpc-server.php b/projects/packages/connection/legacy/class-jetpack-xmlrpc-server.php index f62be1decdca0..db74739d8ac14 100644 --- a/projects/packages/connection/legacy/class-jetpack-xmlrpc-server.php +++ b/projects/packages/connection/legacy/class-jetpack-xmlrpc-server.php @@ -7,7 +7,8 @@ use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Manager as Connection_Manager; -use Automattic\Jetpack\Connection\Utils as Connection_Utils; +use Automattic\Jetpack\Connection\Secrets; +use Automattic\Jetpack\Connection\Tokens; use Automattic\Jetpack\Roles; use Automattic\Jetpack\Sync\Functions; use Automattic\Jetpack\Sync\Sender; @@ -39,11 +40,9 @@ class Jetpack_XMLRPC_Server { /** * Creates a new XMLRPC server object. - * - * @param Automattic\Jetpack\Connection\Manager $manager the connection manager object. */ - public function __construct( $manager = null ) { - $this->connection = is_null( $manager ) ? new Connection_Manager() : $manager; + public function __construct() { + $this->connection = new Connection_Manager(); } /** @@ -183,7 +182,7 @@ public function get_user( $request ) { ); } - $user_token = $this->connection->get_access_token( $user->ID ); + $user_token = ( new Tokens() )->get_access_token( $user->ID ); if ( $user_token ) { list( $user_token_key ) = explode( '.', $user_token->secret ); @@ -328,7 +327,7 @@ public function remote_register( $request ) { ); } - if ( ! Jetpack_Options::get_option( 'id' ) || ! $this->connection->get_access_token() || ! empty( $request['force'] ) ) { + if ( ! Jetpack_Options::get_option( 'id' ) || ! ( new Tokens() )->get_access_token() || ! empty( $request['force'] ) ) { wp_set_current_user( $user->ID ); // This code mostly copied from Jetpack::admin_page_load. @@ -398,7 +397,7 @@ public function remote_provision( $request ) { // Generate secrets. $roles = new Roles(); $role = $roles->translate_user_to_role( $user ); - $secrets = $this->connection->generate_secrets( 'authorize', $user->ID ); + $secrets = ( new Secrets() )->generate( 'authorize', $user->ID ); $response = array( 'jp_version' => JETPACK__VERSION, @@ -470,6 +469,7 @@ public function remote_connect( $request, $ixr_client = false ) { if ( ! $ixr_client ) { $ixr_client = new Jetpack_IXR_Client(); } + // TODO: move this query into the Tokens class? $ixr_client->query( 'jetpack.getUserAccessToken', array( @@ -491,7 +491,7 @@ public function remote_connect( $request, $ixr_client = false ) { } $token = sanitize_text_field( $token ); - Connection_Utils::update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true ); + ( new Tokens() )->update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true ); $this->do_post_authorization(); @@ -570,7 +570,7 @@ public function verify_action( $params ) { $verify_secret = isset( $params[1] ) ? $params[1] : ''; $state = isset( $params[2] ) ? $params[2] : ''; - $result = $this->connection->verify_secrets( $action, $verify_secret, $state ); + $result = ( new Secrets() )->verify( $action, $verify_secret, $state ); if ( is_wp_error( $result ) ) { return $this->error( $result ); @@ -675,7 +675,7 @@ public function test_api_user_code( $args ) { error_log( "VERIFY: $verify" ); */ - $jetpack_token = $this->connection->get_access_token( $user_id ); + $jetpack_token = ( new Tokens() )->get_access_token( $user_id ); $api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true ); if ( ! $api_user_code ) { @@ -753,7 +753,7 @@ public function unlink_user( $user_id = array() ) { * @param string $data Optional data about the event. */ do_action( 'jetpack_event_log', 'unlink' ); - return Connection_Manager::disconnect_user( + return $this->connection->disconnect_user( $user_id, (bool) $user_id ); @@ -908,7 +908,7 @@ public function json_api( $args = array() ) { $token_key = $verified['token_key']; } - $token = $this->connection->get_access_token( $user_id, $token_key ); + $token = ( new Tokens() )->get_access_token( $user_id, $token_key ); if ( ! $token || is_wp_error( $token ) ) { return false; } diff --git a/projects/packages/connection/src/class-client.php b/projects/packages/connection/src/class-client.php index 00d1d065078ff..bd28402497e86 100644 --- a/projects/packages/connection/src/class-client.php +++ b/projects/packages/connection/src/class-client.php @@ -72,8 +72,7 @@ public static function build_signed_request( $args, $body = null ) { $args['auth_location'] = 'query_string'; } - $connection = new Manager(); - $token = $connection->get_access_token( $args['user_id'] ); + $token = ( new Tokens() )->get_access_token( $args['user_id'] ); if ( ! $token ) { return new \WP_Error( 'missing_token' ); } diff --git a/projects/packages/connection/src/class-manager.php b/projects/packages/connection/src/class-manager.php index 965ab2689d488..263e2cc93786c 100644 --- a/projects/packages/connection/src/class-manager.php +++ b/projects/packages/connection/src/class-manager.php @@ -12,7 +12,6 @@ use Automattic\Jetpack\Roles; use Automattic\Jetpack\Status; use Automattic\Jetpack\Tracking; -use Jetpack_Options; use WP_Error; use WP_User; @@ -21,38 +20,6 @@ * and Jetpack. */ class Manager { - - const SECRETS_MISSING = 'secrets_missing'; - const SECRETS_EXPIRED = 'secrets_expired'; - const SECRETS_OPTION_NAME = 'jetpack_secrets'; - const MAGIC_NORMAL_TOKEN_KEY = ';normal;'; - - /** - * Constant used to fetch the master user token. Deprecated. - * - * @deprecated 9.0.0 - * @see Manager::CONNECTION_OWNER - * @var boolean - */ - const JETPACK_MASTER_USER = true; //phpcs:ignore Jetpack.Constants.MasterUserConstant.ShouldNotBeUsed - - /** - * For internal use only. If you need to get the connection owner, use the provided methods - * get_connection_owner_id, get_connection_owner and is_connection_owner - * - * @todo Add private visibility once PHP 7.1 is the minimum supported verion. - * - * @var boolean - */ - const CONNECTION_OWNER = true; - - /** - * The procedure that should be run to generate secrets. - * - * @var Callable - */ - protected $secret_callable; - /** * A copy of the raw POST data for signature verification purposes. * @@ -413,7 +380,7 @@ private function internal_verify_xml_rpc_signature() { } } - $token = $this->get_access_token( $user_id, $token_key, false ); + $token = $this->get_tokens()->get_access_token( $user_id, $token_key, false ); if ( is_wp_error( $token ) ) { $token->add_data( compact( 'signature_details' ) ); return $token; @@ -526,7 +493,16 @@ public function is_active() { if ( ( new Status() )->is_no_user_testing_mode() ) { return $this->is_connected(); } - return (bool) $this->get_access_token( self::CONNECTION_OWNER ); + return (bool) $this->get_tokens()->get_access_token( true ); + } + + /** + * Obtains an instance of the Tokens class. + * + * @return Tokens the Tokens object + */ + public function get_tokens() { + return new Tokens(); } /** @@ -553,7 +529,7 @@ public function is_registered() { */ public function is_connected() { $has_blog_id = (bool) \Jetpack_Options::get_option( 'id' ); - $has_blog_token = (bool) $this->get_access_token( false ); + $has_blog_token = (bool) $this->get_tokens()->get_access_token(); return $has_blog_id && $has_blog_token; } @@ -581,6 +557,17 @@ public function has_connected_user() { return (bool) count( $this->get_connected_users() ); } + /** + * Returns an array of user_id's that have user tokens for communicating with wpcom. + * Able to select by specific capability. + * + * @param string $capability The capability of the user. + * @return array Array of WP_User objects if found. + */ + public function get_connected_users( $capability = 'any' ) { + return $this->get_tokens()->get_connected_users( $capability ); + } + /** * Returns true if the site has a connected Blog owner (master_user). * @@ -620,7 +607,7 @@ public function is_user_connected( $user_id = false ) { return false; } - return (bool) $this->get_access_token( $user_id ); + return (bool) $this->get_tokens()->get_access_token( $user_id ); } /** @@ -633,39 +620,6 @@ public function get_connection_owner_id() { return $owner instanceof \WP_User ? $owner->ID : false; } - /** - * Returns an array of user_id's that have user tokens for communicating with wpcom. - * Able to select by specific capability. - * - * @param string $capability The capability of the user. - * @return array Array of WP_User objects if found. - */ - public function get_connected_users( $capability = 'any' ) { - $connected_users = array(); - $user_tokens = \Jetpack_Options::get_option( 'user_tokens' ); - - if ( ! is_array( $user_tokens ) || empty( $user_tokens ) ) { - return $connected_users; - } - $connected_user_ids = array_keys( $user_tokens ); - - if ( ! empty( $connected_user_ids ) ) { - foreach ( $connected_user_ids as $id ) { - // Check for capability. - if ( 'any' !== $capability && ! user_can( $id, $capability ) ) { - continue; - } - - $user_data = get_userdata( $id ); - if ( $user_data instanceof \WP_User ) { - $connected_users[] = $user_data; - } - } - } - - return $connected_users; - } - /** * Get the wpcom user data of the current|specified connected user. * @@ -715,7 +669,7 @@ public function get_connection_owner() { } // Make sure user is connected. - $user_token = $this->get_access_token( $user_id ); + $user_token = $this->get_tokens()->get_access_token( $user_id ); $connection_owner = false; @@ -785,43 +739,29 @@ public function connect_user( $user_id = null, $redirect_url = null ) { * @param bool $can_overwrite_primary_user Allow for the primary user to be disconnected. * @return Boolean Whether the disconnection of the user was successful. */ - public static function disconnect_user( $user_id = null, $can_overwrite_primary_user = false ) { - $tokens = Jetpack_Options::get_option( 'user_tokens' ); - if ( ! $tokens ) { - return false; - } - + public function disconnect_user( $user_id = null, $can_overwrite_primary_user = false ) { $user_id = empty( $user_id ) ? get_current_user_id() : (int) $user_id; - if ( Jetpack_Options::get_option( 'master_user' ) === $user_id && ! $can_overwrite_primary_user ) { - return false; - } - - if ( ! isset( $tokens[ $user_id ] ) ) { - return false; - } - - $xml = new \Jetpack_IXR_Client( compact( 'user_id' ) ); - $xml->query( 'jetpack.unlink_user', $user_id ); + $result = $this->get_tokens()->disconnect_user( $user_id, $can_overwrite_primary_user ); - unset( $tokens[ $user_id ] ); + if ( $result ) { + $xml = new \Jetpack_IXR_Client( compact( 'user_id' ) ); + $xml->query( 'jetpack.unlink_user', $user_id ); - Jetpack_Options::update_option( 'user_tokens', $tokens ); - - // Delete cached connected user data. - $transient_key = "jetpack_connected_user_data_$user_id"; - delete_transient( $transient_key ); - - /** - * Fires after the current user has been unlinked from WordPress.com. - * - * @since 4.1.0 - * - * @param int $user_id The current user's ID. - */ - do_action( 'jetpack_unlinked_user', $user_id ); + // Delete cached connected user data. + $transient_key = "jetpack_connected_user_data_$user_id"; + delete_transient( $transient_key ); - return true; + /** + * Fires after the current user has been unlinked from WordPress.com. + * + * @since 4.1.0 + * + * @param int $user_id The current user's ID. + */ + do_action( 'jetpack_unlinked_user', $user_id ); + } + return $result; } /** @@ -892,7 +832,7 @@ public function xmlrpc_api_url() { */ public function register( $api_endpoint = 'register' ) { add_action( 'pre_update_jetpack_option_register', array( '\\Jetpack_Options', 'delete_option' ) ); - $secrets = $this->generate_secrets( 'register', get_current_user_id(), 600 ); + $secrets = ( new Secrets() )->generate( 'register', get_current_user_id(), 600 ); if ( false === $secrets ) { return new WP_Error( 'cannot_save_secrets', __( 'Jetpack experienced an issue trying to save options (cannot_save_secrets). We suggest that you contact your hosting provider, and ask them for help checking that the options table is writable on your site.', 'jetpack' ) ); @@ -997,12 +937,13 @@ public function register( $api_endpoint = 'register' ) { \Jetpack_Options::update_options( array( - 'id' => (int) $registration_details->jetpack_id, - 'blog_token' => (string) $registration_details->jetpack_secret, - 'public' => $jetpack_public, + 'id' => (int) $registration_details->jetpack_id, + 'public' => $jetpack_public, ) ); + $this->get_tokens()->update_blog_token( (string) $registration_details->jetpack_secret ); + /** * Fires when a site is registered on WordPress.com. * @@ -1335,35 +1276,6 @@ public static function apply_activation_source_to_args( $args ) { return $args; } - /** - * Returns the callable that would be used to generate secrets. - * - * @return Callable a function that returns a secure string to be used as a secret. - */ - protected function get_secret_callable() { - if ( ! isset( $this->secret_callable ) ) { - /** - * Allows modification of the callable that is used to generate connection secrets. - * - * @param Callable a function or method that returns a secret string. - */ - $this->secret_callable = apply_filters( 'jetpack_connection_secret_generator', array( $this, 'secret_callable_method' ) ); - } - - return $this->secret_callable; - } - - /** - * Runs the wp_generate_password function with the required parameters. This is the - * default implementation of the secret callable, can be overridden using the - * jetpack_connection_secret_generator filter. - * - * @return String $secret value. - */ - private function secret_callable_method() { - return wp_generate_password( 32, false ); - } - /** * Generates two secret tokens and the end of life timestamp for them. * @@ -1372,80 +1284,34 @@ private function secret_callable_method() { * @param Integer $exp Expiration time in seconds. */ public function generate_secrets( $action, $user_id = false, $exp = 600 ) { - if ( false === $user_id ) { - $user_id = get_current_user_id(); - } - - $callable = $this->get_secret_callable(); - - $secrets = \Jetpack_Options::get_raw_option( - self::SECRETS_OPTION_NAME, - array() - ); - - $secret_name = 'jetpack_' . $action . '_' . $user_id; - - if ( - isset( $secrets[ $secret_name ] ) && - $secrets[ $secret_name ]['exp'] > time() - ) { - return $secrets[ $secret_name ]; - } - - $secret_value = array( - 'secret_1' => call_user_func( $callable ), - 'secret_2' => call_user_func( $callable ), - 'exp' => time() + $exp, - ); - - $secrets[ $secret_name ] = $secret_value; - - $res = Jetpack_Options::update_raw_option( self::SECRETS_OPTION_NAME, $secrets ); - return $res ? $secrets[ $secret_name ] : false; + return ( new Secrets() )->generate( $action, $user_id, $exp ); } /** * Returns two secret tokens and the end of life timestamp for them. * + * @deprecated 9.5 Use Automattic\Jetpack\Connection\Secrets->get() instead. + * * @param String $action The action name. * @param Integer $user_id The user identifier. * @return string|array an array of secrets or an error string. */ public function get_secrets( $action, $user_id ) { - $secret_name = 'jetpack_' . $action . '_' . $user_id; - $secrets = \Jetpack_Options::get_raw_option( - self::SECRETS_OPTION_NAME, - array() - ); - - if ( ! isset( $secrets[ $secret_name ] ) ) { - return self::SECRETS_MISSING; - } - - if ( $secrets[ $secret_name ]['exp'] < time() ) { - $this->delete_secrets( $action, $user_id ); - return self::SECRETS_EXPIRED; - } - - return $secrets[ $secret_name ]; + _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Secrets->get' ); + return ( new Secrets() )->get( $action, $user_id ); } /** * Deletes secret tokens in case they, for example, have expired. * + * @deprecated 9.5 Use Automattic\Jetpack\Connection\Secrets->delete() instead. + * * @param String $action The action name. * @param Integer $user_id The user identifier. */ public function delete_secrets( $action, $user_id ) { - $secret_name = 'jetpack_' . $action . '_' . $user_id; - $secrets = \Jetpack_Options::get_raw_option( - self::SECRETS_OPTION_NAME, - array() - ); - if ( isset( $secrets[ $secret_name ] ) ) { - unset( $secrets[ $secret_name ] ); - \Jetpack_Options::update_raw_option( self::SECRETS_OPTION_NAME, $secrets ); - } + _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Secrets->delete' ); + ( new Secrets() )->delete( $action, $user_id ); } /** @@ -1460,6 +1326,7 @@ public function delete_secrets( $action, $user_id ) { * @return bool True if disconnected successfully, false otherwise. */ public function delete_all_connection_tokens( $ignore_connected_plugins = false ) { + // refuse to delete if we're not the last Jetpack plugin installed. if ( ! $ignore_connected_plugins && null !== $this->plugin && ! $this->plugin->is_only() ) { return false; } @@ -1470,22 +1337,20 @@ public function delete_all_connection_tokens( $ignore_connected_plugins = false * * @since 8.7.0 */ - if ( ! apply_filters( 'jetpack_connection_delete_all_tokens', true, $this ) ) { + if ( ! apply_filters( 'jetpack_connection_delete_all_tokens', true ) ) { return false; } \Jetpack_Options::delete_option( array( - 'blog_token', - 'user_token', - 'user_tokens', 'master_user', 'time_diff', 'fallback_no_verify_ssl_certs', ) ); - \Jetpack_Options::delete_raw_option( 'jetpack_secrets' ); + ( new Secrets() )->delete_all(); + $this->get_tokens()->delete_all(); // Delete cached connected user data. $transient_key = 'jetpack_connected_user_data_' . get_current_user_id(); @@ -1568,250 +1433,71 @@ public function reconnect() { * @return string|true|WP_Error True if connection restored or string indicating what's to be done next. A `WP_Error` object otherwise. */ public function restore() { - $invalid_tokens = array(); - $can_restore = $this->can_restore( $invalid_tokens ); - // Tokens are valid. We can't fix the problem we don't see, so the full reconnection is needed. - if ( ! $can_restore ) { + $validate_tokens_response = $this->get_tokens()->validate(); + + $blog_token_healthy = $validate_tokens_response['blog_token']['is_healthy']; + $user_token_healthy = $validate_tokens_response['user_token']['is_healthy']; + + // Tokens are both valid, or both invalid. We can't fix the problem we don't see, so the full reconnection is needed. + if ( $blog_token_healthy === $user_token_healthy ) { $result = $this->reconnect(); - return true === $result ? 'authorize' : $result; + return ( true === $result ) ? 'authorize' : $result; } - if ( in_array( 'blog', $invalid_tokens, true ) ) { - return self::refresh_blog_token(); + if ( ! $blog_token_healthy ) { + return $this->refresh_blog_token(); } - if ( in_array( 'user', $invalid_tokens, true ) ) { - return true === self::refresh_user_token() ? 'authorize' : false; + if ( ! $user_token_healthy ) { + return ( true === $this->refresh_user_token() ) ? 'authorize' : false; } return false; } /** - * Determine whether we can restore the connection, or the full reconnect is needed. - * - * @param array $invalid_tokens The array the invalid tokens are stored in, provided by reference. + * Responds to a WordPress.com call to register the current site. + * Should be changed to protected. * - * @return bool `True` if the connection can be restored, `false` otherwise. + * @param array $registration_data Array of [ secret_1, user_id ]. */ - public function can_restore( &$invalid_tokens ) { - $invalid_tokens = array(); - - $validated_tokens = $this->validate_tokens(); - - if ( ! is_array( $validated_tokens ) || count( array_diff_key( array_flip( array( 'blog_token', 'user_token' ) ), $validated_tokens ) ) ) { - return false; - } - - if ( empty( $validated_tokens['blog_token']['is_healthy'] ) ) { - $invalid_tokens[] = 'blog'; - } - - if ( empty( $validated_tokens['user_token']['is_healthy'] ) ) { - $invalid_tokens[] = 'user'; + public function handle_registration( array $registration_data ) { + list( $registration_secret_1, $registration_user_id ) = $registration_data; + if ( empty( $registration_user_id ) ) { + return new \WP_Error( 'registration_state_invalid', __( 'Invalid Registration State', 'jetpack' ), 400 ); } - // If both tokens are invalid, we can't restore the connection. - return 1 === count( $invalid_tokens ); + return ( new Secrets() )->verify( 'register', $registration_secret_1, (int) $registration_user_id ); } /** * Perform the API request to validate the blog and user tokens. * + * @deprecated 9.5 Use Automattic\Jetpack\Connection\Tokens->validate_tokens() instead. + * * @param int|null $user_id ID of the user we need to validate token for. Current user's ID by default. * * @return array|false|WP_Error The API response: `array( 'blog_token_is_healthy' => true|false, 'user_token_is_healthy' => true|false )`. */ public function validate_tokens( $user_id = null ) { - $blog_id = Jetpack_Options::get_option( 'id' ); - if ( ! $blog_id ) { - return new WP_Error( 'site_not_registered', 'Site not registered.' ); - } - $url = sprintf( - '%s/%s/v%s/%s', - Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ), - 'wpcom', - '2', - 'sites/' . $blog_id . '/jetpack-token-health' - ); - - $user_token = $this->get_access_token( $user_id ? $user_id : get_current_user_id() ); - $blog_token = $this->get_access_token(); - $method = 'POST'; - $body = array( - 'user_token' => $this->get_signed_token( $user_token ), - 'blog_token' => $this->get_signed_token( $blog_token ), - ); - $response = Client::_wp_remote_request( $url, compact( 'body', 'method' ) ); - - if ( is_wp_error( $response ) || ! wp_remote_retrieve_body( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { - return false; - } - - $body = json_decode( wp_remote_retrieve_body( $response ), true ); - - return $body ? $body : false; - } - - /** - * Responds to a WordPress.com call to register the current site. - * Should be changed to protected. - * - * @param array $registration_data Array of [ secret_1, user_id ]. - */ - public function handle_registration( array $registration_data ) { - list( $registration_secret_1, $registration_user_id ) = $registration_data; - if ( empty( $registration_user_id ) ) { - return new \WP_Error( 'registration_state_invalid', __( 'Invalid Registration State', 'jetpack' ), 400 ); - } - - return $this->verify_secrets( 'register', $registration_secret_1, (int) $registration_user_id ); + _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Tokens->validate' ); + return $this->get_tokens()->validate( $user_id ); } /** * Verify a Previously Generated Secret. * + * @deprecated 9.5 Use Automattic\Jetpack\Connection\Secrets->verify() instead. + * * @param string $action The type of secret to verify. * @param string $secret_1 The secret string to compare to what is stored. * @param int $user_id The user ID of the owner of the secret. * @return \WP_Error|string WP_Error on failure, secret_2 on success. */ public function verify_secrets( $action, $secret_1, $user_id ) { - $allowed_actions = array( 'register', 'authorize', 'publicize' ); - if ( ! in_array( $action, $allowed_actions, true ) ) { - return new \WP_Error( 'unknown_verification_action', 'Unknown Verification Action', 400 ); - } - - $user = get_user_by( 'id', $user_id ); - - /** - * We've begun verifying the previously generated secret. - * - * @since 7.5.0 - * - * @param string $action The type of secret to verify. - * @param \WP_User $user The user object. - */ - do_action( 'jetpack_verify_secrets_begin', $action, $user ); - - $return_error = function ( \WP_Error $error ) use ( $action, $user ) { - /** - * Verifying of the previously generated secret has failed. - * - * @since 7.5.0 - * - * @param string $action The type of secret to verify. - * @param \WP_User $user The user object. - * @param \WP_Error $error The error object. - */ - do_action( 'jetpack_verify_secrets_fail', $action, $user, $error ); - - return $error; - }; - - $stored_secrets = $this->get_secrets( $action, $user_id ); - $this->delete_secrets( $action, $user_id ); - - $error = null; - if ( empty( $secret_1 ) ) { - $error = $return_error( - new \WP_Error( - 'verify_secret_1_missing', - /* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */ - sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'secret_1' ), - 400 - ) - ); - } elseif ( ! is_string( $secret_1 ) ) { - $error = $return_error( - new \WP_Error( - 'verify_secret_1_malformed', - /* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */ - sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'secret_1' ), - 400 - ) - ); - } elseif ( empty( $user_id ) ) { - // $user_id is passed around during registration as "state". - $error = $return_error( - new \WP_Error( - 'state_missing', - /* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */ - sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'state' ), - 400 - ) - ); - } elseif ( ! ctype_digit( (string) $user_id ) ) { - $error = $return_error( - new \WP_Error( - 'state_malformed', - /* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */ - sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'state' ), - 400 - ) - ); - } elseif ( self::SECRETS_MISSING === $stored_secrets ) { - $error = $return_error( - new \WP_Error( - 'verify_secrets_missing', - __( 'Verification secrets not found', 'jetpack' ), - 400 - ) - ); - } elseif ( self::SECRETS_EXPIRED === $stored_secrets ) { - $error = $return_error( - new \WP_Error( - 'verify_secrets_expired', - __( 'Verification took too long', 'jetpack' ), - 400 - ) - ); - } elseif ( ! $stored_secrets ) { - $error = $return_error( - new \WP_Error( - 'verify_secrets_empty', - __( 'Verification secrets are empty', 'jetpack' ), - 400 - ) - ); - } elseif ( is_wp_error( $stored_secrets ) ) { - $stored_secrets->add_data( 400 ); - $error = $return_error( $stored_secrets ); - } elseif ( empty( $stored_secrets['secret_1'] ) || empty( $stored_secrets['secret_2'] ) || empty( $stored_secrets['exp'] ) ) { - $error = $return_error( - new \WP_Error( - 'verify_secrets_incomplete', - __( 'Verification secrets are incomplete', 'jetpack' ), - 400 - ) - ); - } elseif ( ! hash_equals( $secret_1, $stored_secrets['secret_1'] ) ) { - $error = $return_error( - new \WP_Error( - 'verify_secrets_mismatch', - __( 'Secret mismatch', 'jetpack' ), - 400 - ) - ); - } - - // Something went wrong during the checks, returning the error. - if ( ! empty( $error ) ) { - return $error; - } - - /** - * We've succeeded at verifying the previously generated secret. - * - * @since 7.5.0 - * - * @param string $action The type of secret to verify. - * @param \WP_User $user The user object. - */ - do_action( 'jetpack_verify_secrets_success', $action, $user ); - - return $stored_secrets['secret_2']; + _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Secrets->verify' ); + return ( new Secrets() )->verify( $action, $secret_1, $user_id ); } /** @@ -1830,147 +1516,7 @@ public function handle_authorization() { * Returns a \WP_Error on failure. */ public function get_token( $data ) { - $roles = new Roles(); - $role = $roles->translate_current_user_to_role(); - - if ( ! $role ) { - return new \WP_Error( 'role', __( 'An administrator for this blog must set up the Jetpack connection.', 'jetpack' ) ); - } - - $client_secret = $this->get_access_token(); - if ( ! $client_secret ) { - return new \WP_Error( 'client_secret', __( 'You need to register your Jetpack before connecting it.', 'jetpack' ) ); - } - - /** - * Filter the URL of the first time the user gets redirected back to your site for connection - * data processing. - * - * @since 8.0.0 - * - * @param string $redirect_url Defaults to the site admin URL. - */ - $processing_url = apply_filters( 'jetpack_token_processing_url', admin_url( 'admin.php' ) ); - - $redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : ''; - - /** - * Filter the URL to redirect the user back to when the authentication process - * is complete. - * - * @since 8.0.0 - * - * @param string $redirect_url Defaults to the site URL. - */ - $redirect = apply_filters( 'jetpack_token_redirect_url', $redirect ); - - $redirect_uri = ( 'calypso' === $data['auth_type'] ) - ? $data['redirect_uri'] - : add_query_arg( - array( - 'handler' => 'jetpack-connection-webhooks', - 'action' => 'authorize', - '_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ), - 'redirect' => $redirect ? rawurlencode( $redirect ) : false, - ), - esc_url( $processing_url ) - ); - - /** - * Filters the token request data. - * - * @since 8.0.0 - * - * @param array $request_data request data. - */ - $body = apply_filters( - 'jetpack_token_request_body', - array( - 'client_id' => \Jetpack_Options::get_option( 'id' ), - 'client_secret' => $client_secret->secret, - 'grant_type' => 'authorization_code', - 'code' => $data['code'], - 'redirect_uri' => $redirect_uri, - ) - ); - - $args = array( - 'method' => 'POST', - 'body' => $body, - 'headers' => array( - 'Accept' => 'application/json', - ), - ); - add_filter( 'http_request_timeout', array( $this, 'increase_timeout' ), PHP_INT_MAX - 1 ); - $response = Client::_wp_remote_request( $this->api_url( 'token' ), $args ); - remove_filter( 'http_request_timeout', array( $this, 'increase_timeout' ), PHP_INT_MAX - 1 ); - - if ( is_wp_error( $response ) ) { - return new \WP_Error( 'token_http_request_failed', $response->get_error_message() ); - } - - $code = wp_remote_retrieve_response_code( $response ); - $entity = wp_remote_retrieve_body( $response ); - - if ( $entity ) { - $json = json_decode( $entity ); - } else { - $json = false; - } - - if ( 200 !== $code || ! empty( $json->error ) ) { - if ( empty( $json->error ) ) { - return new \WP_Error( 'unknown', '', $code ); - } - - /* translators: Error description string. */ - $error_description = isset( $json->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $json->error_description ) : ''; - - return new \WP_Error( (string) $json->error, $error_description, $code ); - } - - if ( empty( $json->access_token ) || ! is_scalar( $json->access_token ) ) { - return new \WP_Error( 'access_token', '', $code ); - } - - if ( empty( $json->token_type ) || 'X_JETPACK' !== strtoupper( $json->token_type ) ) { - return new \WP_Error( 'token_type', '', $code ); - } - - if ( empty( $json->scope ) ) { - return new \WP_Error( 'scope', 'No Scope', $code ); - } - - // TODO: get rid of the error silencer. - // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged - @list( $role, $hmac ) = explode( ':', $json->scope ); - if ( empty( $role ) || empty( $hmac ) ) { - return new \WP_Error( 'scope', 'Malformed Scope', $code ); - } - - if ( $this->sign_role( $role ) !== $json->scope ) { - return new \WP_Error( 'scope', 'Invalid Scope', $code ); - } - - $cap = $roles->translate_role_to_cap( $role ); - if ( ! $cap ) { - return new \WP_Error( 'scope', 'No Cap', $code ); - } - - if ( ! current_user_can( $cap ) ) { - return new \WP_Error( 'scope', 'current_user_cannot', $code ); - } - - return (string) $json->access_token; - } - - /** - * Increases the request timeout value to 30 seconds. - * - * @return int Returns 30. - */ - public function increase_timeout() { - return 30; + return $this->get_tokens()->get( $data, $this->api_url( 'token' ) ); } /** @@ -1988,7 +1534,7 @@ public function get_authorization_url( $user = null, $redirect = null ) { $roles = new Roles(); $role = $roles->translate_user_to_role( $user ); - $signed_role = $this->sign_role( $role ); + $signed_role = $this->get_tokens()->sign_role( $role ); /** * Filter the URL of the first time the user gets redirected back to your site for connection @@ -2010,7 +1556,7 @@ public function get_authorization_url( $user = null, $redirect = null ) { */ $redirect = apply_filters( 'jetpack_connect_redirect_url', $redirect ); - $secrets = $this->generate_secrets( 'authorize', $user->ID, 2 * HOUR_IN_SECONDS ); + $secrets = ( new Secrets() )->generate( 'authorize', $user->ID, 2 * HOUR_IN_SECONDS ); /** * Filter the type of authorization. @@ -2116,7 +1662,7 @@ public function authorize( $data = array() ) { return new \WP_Error( 'no_code', 'Request must include an authorization code.', 400 ); } - $token = $this->get_token( $data ); + $token = $this->get_tokens()->get( $data, $this->api_url( 'token' ) ); if ( is_wp_error( $token ) ) { $code = $token->get_error_code(); @@ -2132,7 +1678,7 @@ public function authorize( $data = array() ) { $is_connection_owner = ! $this->has_connected_owner(); - Utils::update_user_token( $current_user_id, sprintf( '%s.%d', $token, $current_user_id ), $is_connection_owner ); + $this->get_tokens()->update_user_token( $current_user_id, sprintf( '%s.%d', $token, $current_user_id ), $is_connection_owner ); /** * Fires after user has successfully received an auth token. @@ -2285,141 +1831,19 @@ public function is_usable_domain( $domain ) { /** * Gets the requested token. * - * Tokens are one of two types: - * 1. Blog Tokens: These are the "main" tokens. Each site typically has one Blog Token, - * though some sites can have multiple "Special" Blog Tokens (see below). These tokens - * are not associated with a user account. They represent the site's connection with - * the Jetpack servers. - * 2. User Tokens: These are "sub-"tokens. Each connected user account has one User Token. - * - * All tokens look like "{$token_key}.{$private}". $token_key is a public ID for the - * token, and $private is a secret that should never be displayed anywhere or sent - * over the network; it's used only for signing things. - * - * Blog Tokens can be "Normal" or "Special". - * * Normal: The result of a normal connection flow. They look like - * "{$random_string_1}.{$random_string_2}" - * That is, $token_key and $private are both random strings. - * Sites only have one Normal Blog Token. Normal Tokens are found in either - * Jetpack_Options::get_option( 'blog_token' ) (usual) or the JETPACK_BLOG_TOKEN - * constant (rare). - * * Special: A connection token for sites that have gone through an alternative - * connection flow. They look like: - * ";{$special_id}{$special_version};{$wpcom_blog_id};.{$random_string}" - * That is, $private is a random string and $token_key has a special structure with - * lots of semicolons. - * Most sites have zero Special Blog Tokens. Special tokens are only found in the - * JETPACK_BLOG_TOKEN constant. - * - * In particular, note that Normal Blog Tokens never start with ";" and that - * Special Blog Tokens always do. - * - * When searching for a matching Blog Tokens, Blog Tokens are examined in the following - * order: - * 1. Defined Special Blog Tokens (via the JETPACK_BLOG_TOKEN constant) - * 2. Stored Normal Tokens (via Jetpack_Options::get_option( 'blog_token' )) - * 3. Defined Normal Tokens (via the JETPACK_BLOG_TOKEN constant) + * @deprecated 9.5 Use Automattic\Jetpack\Connection\Tokens->get_access_token() instead. * * @param int|false $user_id false: Return the Blog Token. int: Return that user's User Token. * @param string|false $token_key If provided, check that the token matches the provided input. * @param bool|true $suppress_errors If true, return a falsy value when the token isn't found; When false, return a descriptive WP_Error when the token isn't found. * * @return object|false + * + * @see $this->get_tokens()->get_access_token() */ public function get_access_token( $user_id = false, $token_key = false, $suppress_errors = true ) { - $possible_special_tokens = array(); - $possible_normal_tokens = array(); - $user_tokens = \Jetpack_Options::get_option( 'user_tokens' ); - - if ( $user_id ) { - if ( ! $user_tokens ) { - return $suppress_errors ? false : new \WP_Error( 'no_user_tokens', __( 'No user tokens found', 'jetpack' ) ); - } - if ( self::CONNECTION_OWNER === $user_id ) { - $user_id = \Jetpack_Options::get_option( 'master_user' ); - if ( ! $user_id ) { - return $suppress_errors ? false : new \WP_Error( 'empty_master_user_option', __( 'No primary user defined', 'jetpack' ) ); - } - } - if ( ! isset( $user_tokens[ $user_id ] ) || ! $user_tokens[ $user_id ] ) { - // translators: %s is the user ID. - return $suppress_errors ? false : new \WP_Error( 'no_token_for_user', sprintf( __( 'No token for user %d', 'jetpack' ), $user_id ) ); - } - $user_token_chunks = explode( '.', $user_tokens[ $user_id ] ); - if ( empty( $user_token_chunks[1] ) || empty( $user_token_chunks[2] ) ) { - // translators: %s is the user ID. - return $suppress_errors ? false : new \WP_Error( 'token_malformed', sprintf( __( 'Token for user %d is malformed', 'jetpack' ), $user_id ) ); - } - if ( $user_token_chunks[2] !== (string) $user_id ) { - // translators: %1$d is the ID of the requested user. %2$d is the user ID found in the token. - return $suppress_errors ? false : new \WP_Error( 'user_id_mismatch', sprintf( __( 'Requesting user_id %1$d does not match token user_id %2$d', 'jetpack' ), $user_id, $user_token_chunks[2] ) ); - } - $possible_normal_tokens[] = "{$user_token_chunks[0]}.{$user_token_chunks[1]}"; - } else { - $stored_blog_token = \Jetpack_Options::get_option( 'blog_token' ); - if ( $stored_blog_token ) { - $possible_normal_tokens[] = $stored_blog_token; - } - - $defined_tokens_string = Constants::get_constant( 'JETPACK_BLOG_TOKEN' ); - - if ( $defined_tokens_string ) { - $defined_tokens = explode( ',', $defined_tokens_string ); - foreach ( $defined_tokens as $defined_token ) { - if ( ';' === $defined_token[0] ) { - $possible_special_tokens[] = $defined_token; - } else { - $possible_normal_tokens[] = $defined_token; - } - } - } - } - - if ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) { - $possible_tokens = $possible_normal_tokens; - } else { - $possible_tokens = array_merge( $possible_special_tokens, $possible_normal_tokens ); - } - - if ( ! $possible_tokens ) { - // If no user tokens were found, it would have failed earlier, so this is about blog token. - return $suppress_errors ? false : new \WP_Error( 'no_possible_tokens', __( 'No blog token found', 'jetpack' ) ); - } - - $valid_token = false; - - if ( false === $token_key ) { - // Use first token. - $valid_token = $possible_tokens[0]; - } elseif ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) { - // Use first normal token. - $valid_token = $possible_tokens[0]; // $possible_tokens only contains normal tokens because of earlier check. - } else { - // Use the token matching $token_key or false if none. - // Ensure we check the full key. - $token_check = rtrim( $token_key, '.' ) . '.'; - - foreach ( $possible_tokens as $possible_token ) { - if ( hash_equals( substr( $possible_token, 0, strlen( $token_check ) ), $token_check ) ) { - $valid_token = $possible_token; - break; - } - } - } - - if ( ! $valid_token ) { - if ( $user_id ) { - // translators: %d is the user ID. - return $suppress_errors ? false : new \WP_Error( 'no_valid_user_token', sprintf( __( 'Invalid token for user %d', 'jetpack' ), $user_id ) ); - } else { - return $suppress_errors ? false : new \WP_Error( 'no_valid_blog_token', __( 'Invalid blog token', 'jetpack' ) ); - } - } - - return (object) array( - 'secret' => $valid_token, - 'external_user_id' => (int) $user_id, - ); + _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Tokens->get_access_token' ); + return $this->get_tokens()->get_access_token( $user_id, $token_key, $suppress_errors ); } /** @@ -2546,20 +1970,7 @@ public function reset_saved_auth_state() { * @return string Signed user role. */ public function sign_role( $role, $user_id = null ) { - if ( empty( $user_id ) ) { - $user_id = (int) get_current_user_id(); - } - - if ( ! $user_id ) { - return false; - } - - $token = $this->get_access_token(); - if ( ! $token || is_wp_error( $token ) ) { - return false; - } - - return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->secret ); + return $this->get_tokens()->sign_role( $role, $user_id ); } /** @@ -2652,10 +2063,17 @@ public function is_plugin_enabled() { * * @return WP_Error|bool The result of updating the blog_token option. */ - public static function refresh_blog_token() { + /** + * Perform the API request to refresh the blog token. + * Note that we are making this request on behalf of the Jetpack master user, + * given they were (most probably) the ones that registered the site at the first place. + * + * @return WP_Error|bool The result of updating the blog_token option. + */ + public function refresh_blog_token() { ( new Tracking() )->record_user_event( 'restore_connection_refresh_blog_token' ); - $blog_id = Jetpack_Options::get_option( 'id' ); + $blog_id = \Jetpack_Options::get_option( 'id' ); if ( ! $blog_id ) { return new WP_Error( 'site_not_registered', 'Site not registered.' ); } @@ -2700,7 +2118,7 @@ public static function refresh_blog_token() { return new WP_Error( 'jetpack_secret', '', $code ); } - return Jetpack_Options::update_option( 'blog_token', (string) $json->jetpack_secret ); + return $this->get_tokens()->update_blog_token( (string) $json->jetpack_secret ); } /** @@ -2708,67 +2126,23 @@ public static function refresh_blog_token() { * * @return bool */ - public static function refresh_user_token() { + public function refresh_user_token() { ( new Tracking() )->record_user_event( 'restore_connection_refresh_user_token' ); - - self::disconnect_user( null, true ); - + $this->disconnect_user( null, true ); return true; } /** * Fetches a signed token. * + * @deprecated 9.5 Use Automattic\Jetpack\Connection\Tokens->get_signed_token() instead. + * * @param object $token the token. * @return WP_Error|string a signed token */ public function get_signed_token( $token ) { - if ( ! isset( $token->secret ) || empty( $token->secret ) ) { - return new WP_Error( 'invalid_token' ); - } - - list( $token_key, $token_secret ) = explode( '.', $token->secret ); - - $token_key = sprintf( - '%s:%d:%d', - $token_key, - Constants::get_constant( 'JETPACK__API_VERSION' ), - $token->external_user_id - ); - - $timestamp = time(); - - if ( function_exists( 'wp_generate_password' ) ) { - $nonce = wp_generate_password( 10, false ); - } else { - $nonce = substr( sha1( wp_rand( 0, 1000000 ) ), 0, 10 ); - } - - $normalized_request_string = join( - "\n", - array( - $token_key, - $timestamp, - $nonce, - ) - ) . "\n"; - - // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode - $signature = base64_encode( hash_hmac( 'sha1', $normalized_request_string, $token_secret, true ) ); - - $auth = array( - 'token' => $token_key, - 'timestamp' => $timestamp, - 'nonce' => $nonce, - 'signature' => $signature, - ); - - $header_pieces = array(); - foreach ( $auth as $key => $value ) { - $header_pieces[] = sprintf( '%s="%s"', $key, $value ); - } - - return join( ' ', $header_pieces ); + _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Tokens->get_signed_token' ); + return $this->get_tokens()->get_signed_token( $token ); } /** diff --git a/projects/packages/connection/src/class-secrets.php b/projects/packages/connection/src/class-secrets.php new file mode 100644 index 0000000000000..ae5195de10827 --- /dev/null +++ b/projects/packages/connection/src/class-secrets.php @@ -0,0 +1,268 @@ + time() + ) { + return $secrets[ $secret_name ]; + } + + $secret_value = array( + 'secret_1' => call_user_func( $callable ), + 'secret_2' => call_user_func( $callable ), + 'exp' => time() + $exp, + ); + + $secrets[ $secret_name ] = $secret_value; + + $res = Jetpack_Options::update_raw_option( self::LEGACY_SECRETS_OPTION_NAME, $secrets ); + return $res ? $secrets[ $secret_name ] : false; + } + + /** + * Returns two secret tokens and the end of life timestamp for them. + * + * @param String $action The action name. + * @param Integer $user_id The user identifier. + * @return string|array an array of secrets or an error string. + */ + public function get( $action, $user_id ) { + $secret_name = 'jetpack_' . $action . '_' . $user_id; + $secrets = Jetpack_Options::get_raw_option( + self::LEGACY_SECRETS_OPTION_NAME, + array() + ); + + if ( ! isset( $secrets[ $secret_name ] ) ) { + return self::SECRETS_MISSING; + } + + if ( $secrets[ $secret_name ]['exp'] < time() ) { + $this->delete( $action, $user_id ); + return self::SECRETS_EXPIRED; + } + + return $secrets[ $secret_name ]; + } + + /** + * Deletes secret tokens in case they, for example, have expired. + * + * @param String $action The action name. + * @param Integer $user_id The user identifier. + */ + public function delete( $action, $user_id ) { + $secret_name = 'jetpack_' . $action . '_' . $user_id; + $secrets = Jetpack_Options::get_raw_option( + self::LEGACY_SECRETS_OPTION_NAME, + array() + ); + if ( isset( $secrets[ $secret_name ] ) ) { + unset( $secrets[ $secret_name ] ); + Jetpack_Options::update_raw_option( self::LEGACY_SECRETS_OPTION_NAME, $secrets ); + } + } + + /** + * Verify a Previously Generated Secret. + * + * @param string $action The type of secret to verify. + * @param string $secret_1 The secret string to compare to what is stored. + * @param int $user_id The user ID of the owner of the secret. + * @return WP_Error|string WP_Error on failure, secret_2 on success. + */ + public function verify( $action, $secret_1, $user_id ) { + $allowed_actions = array( 'register', 'authorize', 'publicize' ); + if ( ! in_array( $action, $allowed_actions, true ) ) { + return new WP_Error( 'unknown_verification_action', 'Unknown Verification Action', 400 ); + } + + $user = get_user_by( 'id', $user_id ); + + /** + * We've begun verifying the previously generated secret. + * + * @since 7.5.0 + * + * @param string $action The type of secret to verify. + * @param \WP_User $user The user object. + */ + do_action( 'jetpack_verify_secrets_begin', $action, $user ); + + $return_error = function ( WP_Error $error ) use ( $action, $user ) { + /** + * Verifying of the previously generated secret has failed. + * + * @since 7.5.0 + * + * @param string $action The type of secret to verify. + * @param \WP_User $user The user object. + * @param WP_Error $error The error object. + */ + do_action( 'jetpack_verify_secrets_fail', $action, $user, $error ); + + return $error; + }; + + $stored_secrets = $this->get( $action, $user_id ); + $this->delete( $action, $user_id ); + + $error = null; + if ( empty( $secret_1 ) ) { + $error = $return_error( + new WP_Error( + 'verify_secret_1_missing', + /* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */ + sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'secret_1' ), + 400 + ) + ); + } elseif ( ! is_string( $secret_1 ) ) { + $error = $return_error( + new WP_Error( + 'verify_secret_1_malformed', + /* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */ + sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'secret_1' ), + 400 + ) + ); + } elseif ( empty( $user_id ) ) { + // $user_id is passed around during registration as "state". + $error = $return_error( + new WP_Error( + 'state_missing', + /* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */ + sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'state' ), + 400 + ) + ); + } elseif ( ! ctype_digit( (string) $user_id ) ) { + $error = $return_error( + new WP_Error( + 'state_malformed', + /* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */ + sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'state' ), + 400 + ) + ); + } elseif ( self::SECRETS_MISSING === $stored_secrets ) { + $error = $return_error( + new WP_Error( + 'verify_secrets_missing', + __( 'Verification secrets not found', 'jetpack' ), + 400 + ) + ); + } elseif ( self::SECRETS_EXPIRED === $stored_secrets ) { + $error = $return_error( + new WP_Error( + 'verify_secrets_expired', + __( 'Verification took too long', 'jetpack' ), + 400 + ) + ); + } elseif ( ! $stored_secrets ) { + $error = $return_error( + new WP_Error( + 'verify_secrets_empty', + __( 'Verification secrets are empty', 'jetpack' ), + 400 + ) + ); + } elseif ( is_wp_error( $stored_secrets ) ) { + $stored_secrets->add_data( 400 ); + $error = $return_error( $stored_secrets ); + } elseif ( empty( $stored_secrets['secret_1'] ) || empty( $stored_secrets['secret_2'] ) || empty( $stored_secrets['exp'] ) ) { + $error = $return_error( + new WP_Error( + 'verify_secrets_incomplete', + __( 'Verification secrets are incomplete', 'jetpack' ), + 400 + ) + ); + } elseif ( ! hash_equals( $secret_1, $stored_secrets['secret_1'] ) ) { + $error = $return_error( + new WP_Error( + 'verify_secrets_mismatch', + __( 'Secret mismatch', 'jetpack' ), + 400 + ) + ); + } + + // Something went wrong during the checks, returning the error. + if ( ! empty( $error ) ) { + return $error; + } + + /** + * We've succeeded at verifying the previously generated secret. + * + * @since 7.5.0 + * + * @param string $action The type of secret to verify. + * @param \WP_User $user The user object. + */ + do_action( 'jetpack_verify_secrets_success', $action, $user ); + + return $stored_secrets['secret_2']; + } +} diff --git a/projects/packages/connection/src/class-tokens.php b/projects/packages/connection/src/class-tokens.php new file mode 100644 index 0000000000000..730318ecd971b --- /dev/null +++ b/projects/packages/connection/src/class-tokens.php @@ -0,0 +1,548 @@ + true|false, 'user_token_is_healthy' => true|false )`. + */ + public function validate( $user_id = null ) { + $blog_id = Jetpack_Options::get_option( 'id' ); + if ( ! $blog_id ) { + return new WP_Error( 'site_not_registered', 'Site not registered.' ); + } + $url = sprintf( + '%s/%s/v%s/%s', + Constants::get_constant( 'JETPACK__WPCOM_JSON_API_BASE' ), + 'wpcom', + '2', + 'sites/' . $blog_id . '/jetpack-token-health' + ); + + $user_token = $this->get_access_token( $user_id ? $user_id : get_current_user_id() ); + $blog_token = $this->get_access_token(); + $method = 'POST'; + $body = array( + 'user_token' => $this->get_signed_token( $user_token ), + 'blog_token' => $this->get_signed_token( $blog_token ), + ); + $response = Client::_wp_remote_request( $url, compact( 'body', 'method' ) ); + + if ( is_wp_error( $response ) || ! wp_remote_retrieve_body( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { + return false; + } + + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + + return $body ? $body : false; + } + + /** + * Obtains the auth token. + * + * @param array $data The request data. + * @param string $token_api_url The URL of the Jetpack "token" API. + * @return object|WP_Error Returns the auth token on success. + * Returns a WP_Error on failure. + */ + public function get( $data, $token_api_url ) { + $roles = new Roles(); + $role = $roles->translate_current_user_to_role(); + + if ( ! $role ) { + return new WP_Error( 'role', __( 'An administrator for this blog must set up the Jetpack connection.', 'jetpack' ) ); + } + + $client_secret = $this->get_access_token(); + if ( ! $client_secret ) { + return new WP_Error( 'client_secret', __( 'You need to register your Jetpack before connecting it.', 'jetpack' ) ); + } + + /** + * Filter the URL of the first time the user gets redirected back to your site for connection + * data processing. + * + * @since 8.0.0 + * + * @param string $redirect_url Defaults to the site admin URL. + */ + $processing_url = apply_filters( 'jetpack_token_processing_url', admin_url( 'admin.php' ) ); + + $redirect = isset( $data['redirect'] ) ? esc_url_raw( (string) $data['redirect'] ) : ''; + + /** + * Filter the URL to redirect the user back to when the authentication process + * is complete. + * + * @since 8.0.0 + * + * @param string $redirect_url Defaults to the site URL. + */ + $redirect = apply_filters( 'jetpack_token_redirect_url', $redirect ); + + $redirect_uri = ( 'calypso' === $data['auth_type'] ) + ? $data['redirect_uri'] + : add_query_arg( + array( + 'handler' => 'jetpack-connection-webhooks', + 'action' => 'authorize', + '_wpnonce' => wp_create_nonce( "jetpack-authorize_{$role}_{$redirect}" ), + 'redirect' => $redirect ? rawurlencode( $redirect ) : false, + ), + esc_url( $processing_url ) + ); + + /** + * Filters the token request data. + * + * @since 8.0.0 + * + * @param array $request_data request data. + */ + $body = apply_filters( + 'jetpack_token_request_body', + array( + 'client_id' => Jetpack_Options::get_option( 'id' ), + 'client_secret' => $client_secret->secret, + 'grant_type' => 'authorization_code', + 'code' => $data['code'], + 'redirect_uri' => $redirect_uri, + ) + ); + + $args = array( + 'method' => 'POST', + 'body' => $body, + 'headers' => array( + 'Accept' => 'application/json', + ), + ); + add_filter( 'http_request_timeout', array( $this, 'return_30' ), PHP_INT_MAX - 1 ); + $response = Client::_wp_remote_request( $token_api_url, $args ); + remove_filter( 'http_request_timeout', array( $this, 'return_30' ), PHP_INT_MAX - 1 ); + + if ( is_wp_error( $response ) ) { + return new WP_Error( 'token_http_request_failed', $response->get_error_message() ); + } + + $code = wp_remote_retrieve_response_code( $response ); + $entity = wp_remote_retrieve_body( $response ); + + if ( $entity ) { + $json = json_decode( $entity ); + } else { + $json = false; + } + + if ( 200 !== $code || ! empty( $json->error ) ) { + if ( empty( $json->error ) ) { + return new WP_Error( 'unknown', '', $code ); + } + + /* translators: Error description string. */ + $error_description = isset( $json->error_description ) ? sprintf( __( 'Error Details: %s', 'jetpack' ), (string) $json->error_description ) : ''; + + return new WP_Error( (string) $json->error, $error_description, $code ); + } + + if ( empty( $json->access_token ) || ! is_scalar( $json->access_token ) ) { + return new WP_Error( 'access_token', '', $code ); + } + + if ( empty( $json->token_type ) || 'X_JETPACK' !== strtoupper( $json->token_type ) ) { + return new WP_Error( 'token_type', '', $code ); + } + + if ( empty( $json->scope ) ) { + return new WP_Error( 'scope', 'No Scope', $code ); + } + + // TODO: get rid of the error silencer. + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged + @list( $role, $hmac ) = explode( ':', $json->scope ); + if ( empty( $role ) || empty( $hmac ) ) { + return new WP_Error( 'scope', 'Malformed Scope', $code ); + } + + if ( $this->sign_role( $role ) !== $json->scope ) { + return new WP_Error( 'scope', 'Invalid Scope', $code ); + } + + $cap = $roles->translate_role_to_cap( $role ); + if ( ! $cap ) { + return new WP_Error( 'scope', 'No Cap', $code ); + } + + if ( ! current_user_can( $cap ) ) { + return new WP_Error( 'scope', 'current_user_cannot', $code ); + } + + return (string) $json->access_token; + } + + /** + * Enters a user token into the user_tokens option + * + * @param int $user_id The user id. + * @param string $token The user token. + * @param bool $is_master_user Whether the user is the master user. + * @return bool + */ + public function update_user_token( $user_id, $token, $is_master_user ) { + // Not designed for concurrent updates. + $user_tokens = Jetpack_Options::get_option( 'user_tokens' ); + if ( ! is_array( $user_tokens ) ) { + $user_tokens = array(); + } + $user_tokens[ $user_id ] = $token; + if ( $is_master_user ) { + $master_user = $user_id; + $options = compact( 'user_tokens', 'master_user' ); + } else { + $options = compact( 'user_tokens' ); + } + return Jetpack_Options::update_options( $options ); + } + + /** + * Sign a user role with the master access token. + * If not specified, will default to the current user. + * + * @access public + * + * @param string $role User role. + * @param int $user_id ID of the user. + * @return string Signed user role. + */ + public function sign_role( $role, $user_id = null ) { + if ( empty( $user_id ) ) { + $user_id = (int) get_current_user_id(); + } + + if ( ! $user_id ) { + return false; + } + + $token = $this->get_access_token(); + if ( ! $token || is_wp_error( $token ) ) { + return false; + } + + return $role . ':' . hash_hmac( 'md5', "{$role}|{$user_id}", $token->secret ); + } + + /** + * Increases the request timeout value to 30 seconds. + * + * @return int Returns 30. + */ + public function return_30() { + return 30; + } + + /** + * Gets the requested token. + * + * Tokens are one of two types: + * 1. Blog Tokens: These are the "main" tokens. Each site typically has one Blog Token, + * though some sites can have multiple "Special" Blog Tokens (see below). These tokens + * are not associated with a user account. They represent the site's connection with + * the Jetpack servers. + * 2. User Tokens: These are "sub-"tokens. Each connected user account has one User Token. + * + * All tokens look like "{$token_key}.{$private}". $token_key is a public ID for the + * token, and $private is a secret that should never be displayed anywhere or sent + * over the network; it's used only for signing things. + * + * Blog Tokens can be "Normal" or "Special". + * * Normal: The result of a normal connection flow. They look like + * "{$random_string_1}.{$random_string_2}" + * That is, $token_key and $private are both random strings. + * Sites only have one Normal Blog Token. Normal Tokens are found in either + * Jetpack_Options::get_option( 'blog_token' ) (usual) or the JETPACK_BLOG_TOKEN + * constant (rare). + * * Special: A connection token for sites that have gone through an alternative + * connection flow. They look like: + * ";{$special_id}{$special_version};{$wpcom_blog_id};.{$random_string}" + * That is, $private is a random string and $token_key has a special structure with + * lots of semicolons. + * Most sites have zero Special Blog Tokens. Special tokens are only found in the + * JETPACK_BLOG_TOKEN constant. + * + * In particular, note that Normal Blog Tokens never start with ";" and that + * Special Blog Tokens always do. + * + * When searching for a matching Blog Tokens, Blog Tokens are examined in the following + * order: + * 1. Defined Special Blog Tokens (via the JETPACK_BLOG_TOKEN constant) + * 2. Stored Normal Tokens (via Jetpack_Options::get_option( 'blog_token' )) + * 3. Defined Normal Tokens (via the JETPACK_BLOG_TOKEN constant) + * + * @param int|false $user_id false: Return the Blog Token. int: Return that user's User Token. + * @param string|false $token_key If provided, check that the token matches the provided input. + * @param bool|true $suppress_errors If true, return a falsy value when the token isn't found; When false, return a descriptive WP_Error when the token isn't found. + * + * @return object|false + */ + public function get_access_token( $user_id = false, $token_key = false, $suppress_errors = true ) { + $possible_special_tokens = array(); + $possible_normal_tokens = array(); + $user_tokens = Jetpack_Options::get_option( 'user_tokens' ); + + if ( $user_id ) { + if ( ! $user_tokens ) { + return $suppress_errors ? false : new WP_Error( 'no_user_tokens', __( 'No user tokens found', 'jetpack' ) ); + } + if ( true === $user_id ) { // connection owner. + $user_id = Jetpack_Options::get_option( 'master_user' ); + if ( ! $user_id ) { + return $suppress_errors ? false : new WP_Error( 'empty_master_user_option', __( 'No primary user defined', 'jetpack' ) ); + } + } + if ( ! isset( $user_tokens[ $user_id ] ) || ! $user_tokens[ $user_id ] ) { + // translators: %s is the user ID. + return $suppress_errors ? false : new WP_Error( 'no_token_for_user', sprintf( __( 'No token for user %d', 'jetpack' ), $user_id ) ); + } + $user_token_chunks = explode( '.', $user_tokens[ $user_id ] ); + if ( empty( $user_token_chunks[1] ) || empty( $user_token_chunks[2] ) ) { + // translators: %s is the user ID. + return $suppress_errors ? false : new WP_Error( 'token_malformed', sprintf( __( 'Token for user %d is malformed', 'jetpack' ), $user_id ) ); + } + if ( $user_token_chunks[2] !== (string) $user_id ) { + // translators: %1$d is the ID of the requested user. %2$d is the user ID found in the token. + return $suppress_errors ? false : new WP_Error( 'user_id_mismatch', sprintf( __( 'Requesting user_id %1$d does not match token user_id %2$d', 'jetpack' ), $user_id, $user_token_chunks[2] ) ); + } + $possible_normal_tokens[] = "{$user_token_chunks[0]}.{$user_token_chunks[1]}"; + } else { + $stored_blog_token = Jetpack_Options::get_option( 'blog_token' ); + if ( $stored_blog_token ) { + $possible_normal_tokens[] = $stored_blog_token; + } + + $defined_tokens_string = Constants::get_constant( 'JETPACK_BLOG_TOKEN' ); + + if ( $defined_tokens_string ) { + $defined_tokens = explode( ',', $defined_tokens_string ); + foreach ( $defined_tokens as $defined_token ) { + if ( ';' === $defined_token[0] ) { + $possible_special_tokens[] = $defined_token; + } else { + $possible_normal_tokens[] = $defined_token; + } + } + } + } + + if ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) { + $possible_tokens = $possible_normal_tokens; + } else { + $possible_tokens = array_merge( $possible_special_tokens, $possible_normal_tokens ); + } + + if ( ! $possible_tokens ) { + // If no user tokens were found, it would have failed earlier, so this is about blog token. + return $suppress_errors ? false : new WP_Error( 'no_possible_tokens', __( 'No blog token found', 'jetpack' ) ); + } + + $valid_token = false; + + if ( false === $token_key ) { + // Use first token. + $valid_token = $possible_tokens[0]; + } elseif ( self::MAGIC_NORMAL_TOKEN_KEY === $token_key ) { + // Use first normal token. + $valid_token = $possible_tokens[0]; // $possible_tokens only contains normal tokens because of earlier check. + } else { + // Use the token matching $token_key or false if none. + // Ensure we check the full key. + $token_check = rtrim( $token_key, '.' ) . '.'; + + foreach ( $possible_tokens as $possible_token ) { + if ( hash_equals( substr( $possible_token, 0, strlen( $token_check ) ), $token_check ) ) { + $valid_token = $possible_token; + break; + } + } + } + + if ( ! $valid_token ) { + if ( $user_id ) { + // translators: %d is the user ID. + return $suppress_errors ? false : new WP_Error( 'no_valid_user_token', sprintf( __( 'Invalid token for user %d', 'jetpack' ), $user_id ) ); + } else { + return $suppress_errors ? false : new WP_Error( 'no_valid_blog_token', __( 'Invalid blog token', 'jetpack' ) ); + } + } + + return (object) array( + 'secret' => $valid_token, + 'external_user_id' => (int) $user_id, + ); + } + + /** + * Updates the blog token to a new value. + * + * @access public + * + * @param string $token the new blog token value. + * @return Boolean Whether updating the blog token was successful. + */ + public function update_blog_token( $token ) { + return Jetpack_Options::update_option( 'blog_token', $token ); + } + + /** + * Unlinks the current user from the linked WordPress.com user. + * + * @access public + * @static + * + * @todo Refactor to properly load the XMLRPC client independently. + * + * @param Integer $user_id the user identifier. + * @param bool $can_overwrite_primary_user Allow for the primary user to be disconnected. + * @return Boolean Whether the disconnection of the user was successful. + */ + public function disconnect_user( $user_id, $can_overwrite_primary_user = false ) { + $tokens = Jetpack_Options::get_option( 'user_tokens' ); + if ( ! $tokens ) { + return false; + } + + if ( Jetpack_Options::get_option( 'master_user' ) === $user_id && ! $can_overwrite_primary_user ) { + return false; + } + + if ( ! isset( $tokens[ $user_id ] ) ) { + return false; + } + + unset( $tokens[ $user_id ] ); + + Jetpack_Options::update_option( 'user_tokens', $tokens ); + + return true; + } + + /** + * Returns an array of user_id's that have user tokens for communicating with wpcom. + * Able to select by specific capability. + * + * @param string $capability The capability of the user. + * @return array Array of WP_User objects if found. + */ + public function get_connected_users( $capability = 'any' ) { + $connected_users = array(); + $user_tokens = Jetpack_Options::get_option( 'user_tokens' ); + + if ( ! is_array( $user_tokens ) || empty( $user_tokens ) ) { + return $connected_users; + } + $connected_user_ids = array_keys( $user_tokens ); + + if ( ! empty( $connected_user_ids ) ) { + foreach ( $connected_user_ids as $id ) { + // Check for capability. + if ( 'any' !== $capability && ! user_can( $id, $capability ) ) { + continue; + } + + $user_data = get_userdata( $id ); + if ( $user_data instanceof \WP_User ) { + $connected_users[] = $user_data; + } + } + } + + return $connected_users; + } + + /** + * Fetches a signed token. + * + * @param object $token the token. + * @return WP_Error|string a signed token + */ + public function get_signed_token( $token ) { + if ( ! isset( $token->secret ) || empty( $token->secret ) ) { + return new WP_Error( 'invalid_token' ); + } + + list( $token_key, $token_secret ) = explode( '.', $token->secret ); + + $token_key = sprintf( + '%s:%d:%d', + $token_key, + Constants::get_constant( 'JETPACK__API_VERSION' ), + $token->external_user_id + ); + + $timestamp = time(); + + if ( function_exists( 'wp_generate_password' ) ) { + $nonce = wp_generate_password( 10, false ); + } else { + $nonce = substr( sha1( wp_rand( 0, 1000000 ) ), 0, 10 ); + } + + $normalized_request_string = join( + "\n", + array( + $token_key, + $timestamp, + $nonce, + ) + ) . "\n"; + + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + $signature = base64_encode( hash_hmac( 'sha1', $normalized_request_string, $token_secret, true ) ); + + $auth = array( + 'token' => $token_key, + 'timestamp' => $timestamp, + 'nonce' => $nonce, + 'signature' => $signature, + ); + + $header_pieces = array(); + foreach ( $auth as $key => $value ) { + $header_pieces[] = sprintf( '%s="%s"', $key, $value ); + } + + return join( ' ', $header_pieces ); + } +} diff --git a/projects/packages/connection/src/class-utils.php b/projects/packages/connection/src/class-utils.php index c459cb20fec9d..135eeaca694a9 100644 --- a/projects/packages/connection/src/class-utils.php +++ b/projects/packages/connection/src/class-utils.php @@ -32,25 +32,16 @@ public static function fix_url_for_bad_hosts( $url ) { /** * Enters a user token into the user_tokens option * + * @deprecated 9.5 Use Automattic\Jetpack\Connection\Tokens->update_user_token() instead. + * * @param int $user_id The user id. * @param string $token The user token. * @param bool $is_master_user Whether the user is the master user. * @return bool */ public static function update_user_token( $user_id, $token, $is_master_user ) { - // Not designed for concurrent updates. - $user_tokens = \Jetpack_Options::get_option( 'user_tokens' ); - if ( ! is_array( $user_tokens ) ) { - $user_tokens = array(); - } - $user_tokens[ $user_id ] = $token; - if ( $is_master_user ) { - $master_user = $user_id; - $options = compact( 'user_tokens', 'master_user' ); - } else { - $options = compact( 'user_tokens' ); - } - return \Jetpack_Options::update_options( $options ); + _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Tokens->update_user_token' ); + return ( new Tokens() )->update_user_token( $user_id, $token, $is_master_user ); } /** diff --git a/projects/packages/connection/tests/php/test-rest-endpoints.php b/projects/packages/connection/tests/php/test-rest-endpoints.php index fd4b162e463f2..28bb6c1f2a9ca 100644 --- a/projects/packages/connection/tests/php/test-rest-endpoints.php +++ b/projects/packages/connection/tests/php/test-rest-endpoints.php @@ -113,7 +113,7 @@ public function test_remote_authorize() { $options_filter = function ( $value ) use ( $secrets ) { return $secrets; }; - add_filter( 'pre_option_' . Manager::SECRETS_OPTION_NAME, $options_filter ); + add_filter( 'pre_option_' . Secrets::LEGACY_SECRETS_OPTION_NAME, $options_filter ); $user_caps_filter = function ( $allcaps, $caps, $args, $user ) { if ( $user instanceof WP_User && self::USER_ID === $user->ID ) { @@ -133,7 +133,7 @@ public function test_remote_authorize() { $data = $response->get_data(); remove_filter( 'user_has_cap', $user_caps_filter ); - remove_filter( 'pre_option_' . Manager::SECRETS_OPTION_NAME, $options_filter ); + remove_filter( 'pre_option_' . Secrets::LEGACY_SECRETS_OPTION_NAME, $options_filter ); remove_filter( 'pre_http_request', array( $this, 'intercept_auth_token_request' ) ); remove_filter( 'jetpack_options', array( $this, 'mock_jetpack_options' ) ); @@ -278,7 +278,7 @@ public function test_connection_reconnect_partial_user_token_success() { * @return array */ public function bypass_raw_options( array $options ) { - $options[ Manager::SECRETS_OPTION_NAME ] = true; + $options[ Secrets::LEGACY_SECRETS_OPTION_NAME ] = true; return $options; } diff --git a/projects/packages/connection/tests/php/test_Manager_integration.php b/projects/packages/connection/tests/php/test_Manager_integration.php index 4509ab2c44024..d253110f001dc 100644 --- a/projects/packages/connection/tests/php/test_Manager_integration.php +++ b/projects/packages/connection/tests/php/test_Manager_integration.php @@ -271,11 +271,10 @@ public function test_get_access_token( $create_blog_token, $create_user_tokens, } if ( 'CONNECTION_OWNER' === $user_id_query ) { - $manager = $this->manager; // php 5.6 safe. - $user_id_query = $manager::CONNECTION_OWNER; + $user_id_query = true; } - $token = $this->manager->get_access_token( $user_id_query, $token_key_query, false ); + $token = ( new Tokens() )->get_access_token( $user_id_query, $token_key_query, false ); if ( $expected_error_code ) { $this->assertInstanceOf( 'WP_Error', $token ); @@ -450,7 +449,7 @@ public function get_access_token_data_provider() { * Make sure we don´t change how we return errors */ public function test_get_access_token_suppress_errors() { - $this->assertFalse( $this->manager->get_access_token( 123 ) ); - $this->assertInstanceOf( 'WP_Error', $this->manager->get_access_token( 123, '', false ) ); + $this->assertFalse( ( new Tokens() )->get_access_token( 123 ) ); + $this->assertInstanceOf( 'WP_Error', ( new Tokens() )->get_access_token( 123, '', false ) ); } } diff --git a/projects/packages/connection/tests/php/test_Manager_unit.php b/projects/packages/connection/tests/php/test_Manager_unit.php index dd419b6c744bf..04f74ac63bb69 100644 --- a/projects/packages/connection/tests/php/test_Manager_unit.php +++ b/projects/packages/connection/tests/php/test_Manager_unit.php @@ -40,9 +40,15 @@ class ManagerTest extends TestCase { */ public function set_up() { $this->manager = $this->getMockBuilder( 'Automattic\Jetpack\Connection\Manager' ) - ->setMethods( array( 'get_access_token', 'get_connection_owner_id' ) ) + ->setMethods( array( 'get_tokens', 'get_connection_owner_id' ) ) ->getMock(); + $this->tokens = $this->getMockBuilder( 'Automattic\Jetpack\Connection\Tokens' ) + ->setMethods( array( 'get_access_token' ) ) + ->getMock(); + + $this->manager->method( 'get_tokens' )->will( $this->returnValue( $this->tokens ) ); + $this->user_id = wp_insert_user( array( 'user_login' => 'test_is_user_connected_with_user_id_logged_in', @@ -61,6 +67,7 @@ public function tear_down() { wp_set_current_user( 0 ); WorDBless_Users::init()->clear_all_users(); unset( $this->manager ); + unset( $this->tokens ); Constants::clear_constants(); } @@ -74,7 +81,7 @@ public function test_is_active_when_connected() { 'secret' => 'abcd1234', 'external_user_id' => 1, ); - $this->manager->expects( $this->once() ) + $this->tokens->expects( $this->once() ) ->method( 'get_access_token' ) ->will( $this->returnValue( $access_token ) ); @@ -87,7 +94,7 @@ public function test_is_active_when_connected() { * @covers Automattic\Jetpack\Connection\Manager::is_active */ public function test_is_active_when_not_connected() { - $this->manager->expects( $this->once() ) + $this->tokens->expects( $this->once() ) ->method( 'get_access_token' ) ->will( $this->returnValue( false ) ); @@ -185,7 +192,7 @@ public function test_is_user_connected_with_false_user_id_logged_out() { * @covers Automattic\Jetpack\Connection\Manager::is_user_connected */ public function test_is_user_connected_with_user_id_logged_out_not_connected() { - $this->manager->expects( $this->once() ) + $this->tokens->expects( $this->once() ) ->method( 'get_access_token' ) ->will( $this->returnValue( false ) ); @@ -204,7 +211,7 @@ public function test_is_user_connected_with_default_user_id_logged_in() { 'secret' => 'abcd1234', 'external_user_id' => 1, ); - $this->manager->expects( $this->once() ) + $this->tokens->expects( $this->once() ) ->method( 'get_access_token' ) ->will( $this->returnValue( $access_token ) ); @@ -221,7 +228,7 @@ public function test_is_user_connected_with_user_id_logged_in() { 'secret' => 'abcd1234', 'external_user_id' => 1, ); - $this->manager->expects( $this->once() ) + $this->tokens->expects( $this->once() ) ->method( 'get_access_token' ) ->will( $this->returnValue( $access_token ) ); diff --git a/projects/plugins/jetpack/_inc/class.jetpack-provision.php b/projects/plugins/jetpack/_inc/class.jetpack-provision.php index 9d7ca7b38e224..1d0b677fdc8da 100644 --- a/projects/plugins/jetpack/_inc/class.jetpack-provision.php +++ b/projects/plugins/jetpack/_inc/class.jetpack-provision.php @@ -1,7 +1,8 @@ translate_current_user_to_role(); $signed_role = Jetpack::connection()->sign_role( $role ); - $secrets = Jetpack::init()->generate_secrets( 'authorize' ); + $secrets = ( new Secrets() )->generate( 'authorize' ); // Jetpack auth stuff. $request_body['scope'] = $signed_role; @@ -210,7 +211,7 @@ public static function partner_provision( $access_token, $named_args ) { if ( isset( $body_json->access_token ) && is_user_logged_in() ) { // Check if this matches the existing token before replacing. - $existing_token = Jetpack_Data::get_access_token( get_current_user_id() ); + $existing_token = ( new Tokens() )->get_access_token( get_current_user_id() ); if ( empty( $existing_token ) || $existing_token->secret !== $body_json->access_token ) { self::authorize_user( get_current_user_id(), $body_json->access_token ); } @@ -221,7 +222,7 @@ public static function partner_provision( $access_token, $named_args ) { private static function authorize_user( $user_id, $access_token ) { // authorize user and enable SSO - Connection_Utils::update_user_token( $user_id, sprintf( '%s.%d', $access_token, $user_id ), true ); + ( new Tokens() )->update_user_token( $user_id, sprintf( '%s.%d', $access_token, $user_id ), true ); /** * Auto-enable SSO module for new Jetpack Start connections diff --git a/projects/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php b/projects/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php index 8bb8db217539a..2de6ae5f72f09 100644 --- a/projects/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php +++ b/projects/plugins/jetpack/_inc/lib/class.core-rest-api-endpoints.php @@ -1758,7 +1758,7 @@ public static function set_connection_owner( $request ) { * Unlinks current user from the WordPress.com Servers. * * @since 4.3.0 - * @uses Automattic\Jetpack\Connection\Manager::disconnect_user + * @uses Automattic\Jetpack\Connection\Manager->disconnect_user * * @param WP_REST_Request $request The request sent to the WP REST API. * @@ -1770,7 +1770,7 @@ public static function unlink_user( $request ) { return new WP_Error( 'invalid_param', esc_html__( 'Invalid Parameter', 'jetpack' ), array( 'status' => 404 ) ); } - if ( Connection_Manager::disconnect_user() ) { + if ( ( new Connection_Manager( 'jetpack' ) )->disconnect_user() ) { return rest_ensure_response( array( 'code' => 'success' @@ -3966,7 +3966,7 @@ public static function update_licensing_error( $request ) { * @return WP_REST_Response A response object containing the Jetpack CRM data. */ public static function get_jetpack_crm_data() { - $jetpack_crm_data = ( new Automattic\Jetpack\Jetpack_CRM_Data() )->get_crm_data(); + $jetpack_crm_data = ( new Jetpack_CRM_Data() )->get_crm_data(); return rest_ensure_response( $jetpack_crm_data ); } @@ -3981,7 +3981,7 @@ public static function activate_crm_jetpack_forms_extension( $request ) { return new WP_Error( 'invalid_param', esc_html__( 'Missing or invalid extension parameter.', 'jetpack' ), array( 'status' => 404 ) ); } - $result = ( new Automattic\Jetpack\Jetpack_CRM_Data() )->activate_crm_jetpackforms_extension(); + $result = ( new Jetpack_CRM_Data() )->activate_crm_jetpackforms_extension(); if ( is_wp_error( $result ) ) { return $result; diff --git a/projects/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php b/projects/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php index a0e391633d435..d623a3eb73d1e 100644 --- a/projects/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php +++ b/projects/plugins/jetpack/_inc/lib/class.jetpack-keyring-service-helper.php @@ -1,5 +1,7 @@ generate( 'publicize' ); if ( ! $verification ) { $url = Jetpack::admin_url( 'jetpack#/settings' ); wp_die( sprintf( __( "Jetpack is not connected. Please connect Jetpack by visiting Settings.", 'jetpack' ), $url ) ); diff --git a/projects/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php b/projects/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php index 6ac29e54e9cae..7afd383eac188 100644 --- a/projects/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php +++ b/projects/plugins/jetpack/_inc/lib/debugger/class-jetpack-cxn-tests.php @@ -7,6 +7,7 @@ use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Manager as Connection_Manager; +use Automattic\Jetpack\Connection\Tokens; use Automattic\Jetpack\Redirect; use Automattic\Jetpack\Status; use Automattic\Jetpack\Sync\Health as Sync_Health; @@ -85,7 +86,7 @@ protected function helper_is_jetpack_connected() { * @return object|false */ protected function helper_get_blog_token() { - return Jetpack::connection()->get_access_token(); + return ( new Tokens() )->get_access_token(); } /** @@ -481,7 +482,7 @@ protected function test__connection_token_health() { $m = new Connection_Manager(); $user_id = get_current_user_id() ? get_current_user_id() : $m->get_connection_owner_id(); - $validated_tokens = $m->validate_tokens( $user_id ); + $validated_tokens = ( new Tokens() )->validate( $user_id ); if ( ! is_array( $validated_tokens ) || count( array_diff_key( array_flip( array( 'blog_token', 'user_token' ) ), $validated_tokens ) ) ) { return self::skipped_test( diff --git a/projects/plugins/jetpack/_inc/lib/debugger/class-jetpack-debug-data.php b/projects/plugins/jetpack/_inc/lib/debugger/class-jetpack-debug-data.php index 36e8950e14987..2bd536d6e7cfb 100644 --- a/projects/plugins/jetpack/_inc/lib/debugger/class-jetpack-debug-data.php +++ b/projects/plugins/jetpack/_inc/lib/debugger/class-jetpack-debug-data.php @@ -5,7 +5,7 @@ * @package automattic/jetpack */ -use Automattic\Jetpack\Connection\Manager as Connection_Manager; +use Automattic\Jetpack\Connection\Tokens; use Automattic\Jetpack\Constants; use Automattic\Jetpack\Redirect; use Automattic\Jetpack\Sync\Functions; @@ -181,9 +181,8 @@ public static function debug_data() { * If a token does not contain a period, then it is malformed and we report it as such. */ $user_id = get_current_user_id(); - $cxn_mgr = new Connection_Manager(); - $blog_token = $cxn_mgr->get_access_token(); - $user_token = $cxn_mgr->get_access_token( $user_id ); + $blog_token = ( new Tokens() )->get_access_token(); + $user_token = ( new Tokens() )->get_access_token( $user_id ); $tokenset = ''; if ( $blog_token ) { diff --git a/projects/plugins/jetpack/class.jetpack-cli.php b/projects/plugins/jetpack/class.jetpack-cli.php index 0fb3d4957576e..7bf20bb91149e 100644 --- a/projects/plugins/jetpack/class.jetpack-cli.php +++ b/projects/plugins/jetpack/class.jetpack-cli.php @@ -1,10 +1,8 @@ ID ) ) { + if ( ( new Connection_Manager( 'jetpack' ) )->disconnect_user( $user->ID ) ) { Jetpack::log( 'unlink', $user->ID ); WP_CLI::success( __( 'User has been successfully disconnected.', 'jetpack' ) ); } else { @@ -1281,7 +1281,7 @@ public function authorize_user( $args, $named_args ) { $is_master_user = ! Jetpack::is_active(); $current_user_id = get_current_user_id(); - Connection_Utils::update_user_token( $current_user_id, sprintf( '%s.%d', $named_args['token'], $current_user_id ), $is_master_user ); + ( new Tokens() )->update_user_token( $current_user_id, sprintf( '%s.%d', $named_args['token'], $current_user_id ), $is_master_user ); WP_CLI::log( wp_json_encode( $named_args ) ); diff --git a/projects/plugins/jetpack/class.jetpack-data.php b/projects/plugins/jetpack/class.jetpack-data.php index 6dbb8de25f5a5..44b4645f2acc8 100644 --- a/projects/plugins/jetpack/class.jetpack-data.php +++ b/projects/plugins/jetpack/class.jetpack-data.php @@ -1,13 +1,10 @@ get_access_token( $user_id, $token_key, $suppress_errors ); - } -} +// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped +_deprecated_file( basename( __FILE__ ), 'jetpack-9.5' ); diff --git a/projects/plugins/jetpack/class.jetpack-network.php b/projects/plugins/jetpack/class.jetpack-network.php index 86fcdb4336b90..b795ce880c0af 100644 --- a/projects/plugins/jetpack/class.jetpack-network.php +++ b/projects/plugins/jetpack/class.jetpack-network.php @@ -5,10 +5,8 @@ * @package automattic/jetpack */ -use Automattic\Jetpack\Connection\Client; use Automattic\Jetpack\Connection\Manager; -use Automattic\Jetpack\Connection\Utils as Connection_Utils; -use Automattic\Jetpack\Constants; +use Automattic\Jetpack\Connection\Tokens; use Automattic\Jetpack\Status; /** @@ -494,7 +492,7 @@ public function do_subsiteregister( $site_id = null ) { */ public function filter_register_user_token( $token ) { $is_master_user = ! Jetpack::is_active(); - Connection_Utils::update_user_token( + ( new Tokens() )->update_user_token( get_current_user_id(), sprintf( '%s.%d', $token->secret, get_current_user_id() ), $is_master_user diff --git a/projects/plugins/jetpack/class.jetpack.php b/projects/plugins/jetpack/class.jetpack.php index 073add3e18dc7..21e13a18c3628 100644 --- a/projects/plugins/jetpack/class.jetpack.php +++ b/projects/plugins/jetpack/class.jetpack.php @@ -1,4 +1,5 @@ update_user_token() instead. + * * @param int $user_id The user id. * @param string $token The user token. * @param bool $is_master_user Whether the user is the master user. * @return bool */ public static function update_user_token( $user_id, $token, $is_master_user ) { - _deprecated_function( __METHOD__, 'jetpack-8.0', 'Automattic\\Jetpack\\Connection\\Utils::update_user_token' ); - return Connection_Utils::update_user_token( $user_id, $token, $is_master_user ); + _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Tokens->update_user_token' ); + return ( new Tokens() )->update_user_token( $user_id, $token, $is_master_user ); } /** @@ -3385,17 +3386,12 @@ public static function disconnect( $update_activated_state = true ) { } /** - * Unlinks the current user from the linked WordPress.com user. + * Disconnects the user * - * @deprecated since 7.7 - * @see Automattic\Jetpack\Connection\Manager::disconnect_user() - * - * @param Integer $user_id the user identifier. - * @return Boolean Whether the disconnection of the user was successful. + * @param int $user_id The user ID to disconnect. */ - public static function unlink_user( $user_id = null ) { - _deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::disconnect_user' ); - return Connection_Manager::disconnect_user( $user_id ); + public function disconnect_user( $user_id ) { + $this->connection_manager->disconnect_user( $user_id ); } /** @@ -3809,7 +3805,7 @@ function upload_handler( $update_media_item = false ) { $media_keys = array_keys( $_FILES['media'] ); - $token = Jetpack_Data::get_access_token( get_current_user_id() ); + $token = ( new Tokens() )->get_access_token( get_current_user_id() ); if ( ! $token || is_wp_error( $token ) ) { return new WP_Error( 'unknown_token', 'Unknown Jetpack token', 403 ); } @@ -4357,7 +4353,7 @@ function ( $domains ) { check_admin_referer( 'jetpack-reconnect' ); self::log( 'reconnect' ); - $this->disconnect(); + self::disconnect(); wp_redirect( $this->build_connect_url( true, false, 'reconnect' ) ); exit; case 'deactivate': @@ -4380,7 +4376,7 @@ function ( $domains ) { $redirect = isset( $_GET['redirect'] ) ? $_GET['redirect'] : ''; check_admin_referer( 'jetpack-unlink' ); self::log( 'unlink' ); - Connection_Manager::disconnect_user(); + $this->connection_manager->disconnect_user(); self::state( 'message', 'unlinked' ); if ( 'sub-unlink' == $redirect ) { wp_safe_redirect( admin_url() ); @@ -4709,78 +4705,6 @@ static function build_stats_url( $args ) { } - /** - * Get the role of the current user. - * - * @deprecated 7.6 Use Automattic\Jetpack\Roles::translate_current_user_to_role() instead. - * - * @access public - * @static - * - * @return string|boolean Current user's role, false if not enough capabilities for any of the roles. - */ - public static function translate_current_user_to_role() { - _deprecated_function( __METHOD__, 'jetpack-7.6.0' ); - - $roles = new Roles(); - return $roles->translate_current_user_to_role(); - } - - /** - * Get the role of a particular user. - * - * @deprecated 7.6 Use Automattic\Jetpack\Roles::translate_user_to_role() instead. - * - * @access public - * @static - * - * @param \WP_User $user User object. - * @return string|boolean User's role, false if not enough capabilities for any of the roles. - */ - public static function translate_user_to_role( $user ) { - _deprecated_function( __METHOD__, 'jetpack-7.6.0' ); - - $roles = new Roles(); - return $roles->translate_user_to_role( $user ); - } - - /** - * Get the minimum capability for a role. - * - * @deprecated 7.6 Use Automattic\Jetpack\Roles::translate_role_to_cap() instead. - * - * @access public - * @static - * - * @param string $role Role name. - * @return string|boolean Capability, false if role isn't mapped to any capabilities. - */ - public static function translate_role_to_cap( $role ) { - _deprecated_function( __METHOD__, 'jetpack-7.6.0' ); - - $roles = new Roles(); - return $roles->translate_role_to_cap( $role ); - } - - /** - * Sign a user role with the master access token. - * If not specified, will default to the current user. - * - * @deprecated since 7.7 - * @see Automattic\Jetpack\Connection\Manager::sign_role() - * - * @access public - * @static - * - * @param string $role User role. - * @param int $user_id ID of the user. - * @return string Signed user role. - */ - public static function sign_role( $role, $user_id = null ) { - _deprecated_function( __METHOD__, 'jetpack-7.7', 'Automattic\\Jetpack\\Connection\\Manager::sign_role' ); - return self::connection()->sign_role( $role, $user_id ); - } - /** * Builds a URL to the Jetpack connection auth page * @@ -4796,7 +4720,7 @@ public static function sign_role( $role, $user_id = null ) { */ function build_connect_url( $raw = false, $redirect = false, $from = false, $register = false ) { $site_id = Jetpack_Options::get_option( 'id' ); - $blog_token = Jetpack_Data::get_access_token(); + $blog_token = ( new Tokens() )->get_access_token(); if ( $register || ! $blog_token || ! $site_id ) { $url = self::nonce_url_no_esc( self::admin_url( 'action=register' ), 'jetpack-register' ); @@ -5359,6 +5283,8 @@ public static function connection() { * * Note these tokens are unique per call, NOT static per site for connecting. * + * @deprecated 9.5 Use Automattic\Jetpack\Connection\Secrets->generate() instead. + * * @since 2.6 * @param String $action The action name. * @param Integer $user_id The user identifier. @@ -5366,33 +5292,24 @@ public static function connection() { * @return array */ public static function generate_secrets( $action, $user_id = false, $exp = 600 ) { + _deprecated_function( __METHOD__, 'jetpack-9.5', 'Automattic\\Jetpack\\Connection\\Secrets->generate' ); return self::connection()->generate_secrets( $action, $user_id, $exp ); } public static function get_secrets( $action, $user_id ) { - $secrets = self::connection()->get_secrets( $action, $user_id ); + $secrets = ( new Secrets() )->get( $action, $user_id ); - if ( Connection_Manager::SECRETS_MISSING === $secrets ) { + if ( Secrets::SECRETS_MISSING === $secrets ) { return new WP_Error( 'verify_secrets_missing', 'Verification secrets not found' ); } - if ( Connection_Manager::SECRETS_EXPIRED === $secrets ) { + if ( Secrets::SECRETS_EXPIRED === $secrets ) { return new WP_Error( 'verify_secrets_expired', 'Verification took too long' ); } return $secrets; } - /** - * @deprecated 7.5 Use Connection_Manager instead. - * - * @param $action - * @param $user_id - */ - public static function delete_secrets( $action, $user_id ) { - return self::connection()->delete_secrets( $action, $user_id ); - } - /** * Builds the timeout limit for queries talking with the wpcom servers. * @@ -5846,7 +5763,7 @@ public static function xmlrpc_async_call( ...$args ) { $client_blog_id = is_multisite() ? $blog_id : 0; if ( ! isset( $clients[ $client_blog_id ] ) ) { - $clients[ $client_blog_id ] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => Connection_Manager::CONNECTION_OWNER ) ); + $clients[ $client_blog_id ] = new Jetpack_IXR_ClientMulticall( array( 'user_id' => true ) ); if ( function_exists( 'ignore_user_abort' ) ) { ignore_user_abort( true ); } @@ -5982,8 +5899,9 @@ function verify_json_api_authorization_request( $environment = null ) { ? $_REQUEST : $environment; - list( $envToken, $envVersion, $envUserId ) = explode( ':', $environment['token'] ); - $token = Jetpack_Data::get_access_token( $envUserId, $envToken ); + //phpcs:ignore MediaWiki.Classes.UnusedUseStatement.UnusedUse,VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + list( $env_token, $env_version, $env_user_id ) = explode( ':', $environment['token'] ); + $token = ( new Tokens() )->get_access_token( $env_user_id, $env_token ); if ( ! $token || empty( $token->secret ) ) { wp_die( __( 'You must connect your Jetpack plugin to WordPress.com to use this feature.', 'jetpack' ) ); } diff --git a/projects/plugins/jetpack/extensions/blocks/premium-content/_inc/subscription-service/class-jetpack-token-subscription-service.php b/projects/plugins/jetpack/extensions/blocks/premium-content/_inc/subscription-service/class-jetpack-token-subscription-service.php index d27fa8b2259dc..2dc8cfb3a83ac 100644 --- a/projects/plugins/jetpack/extensions/blocks/premium-content/_inc/subscription-service/class-jetpack-token-subscription-service.php +++ b/projects/plugins/jetpack/extensions/blocks/premium-content/_inc/subscription-service/class-jetpack-token-subscription-service.php @@ -8,7 +8,7 @@ namespace Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service; -use Automattic\Jetpack\Connection\Manager; +use Automattic\Jetpack\Connection\Tokens; /** * Class Jetpack_Token_Subscription_Service @@ -41,8 +41,7 @@ public function get_site_id() { * @return string The key. */ public function get_key() { - $connection = new Manager(); - $token = $connection->get_access_token(); + $token = ( new Tokens() )->get_access_token(); if ( ! isset( $token->secret ) ) { return false; } diff --git a/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-user-connect-endpoint.php b/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-user-connect-endpoint.php index cc47d0d1f4eae..b88d040657a43 100644 --- a/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-user-connect-endpoint.php +++ b/projects/plugins/jetpack/json-endpoints/jetpack/class.jetpack-json-api-user-connect-endpoint.php @@ -1,7 +1,7 @@ user_id, sprintf( '%s.%d', $this->user_token, $this->user_id ), false ); + ( new Tokens() )->update_user_token( $this->user_id, sprintf( '%s.%d', $this->user_token, $this->user_id ), false ); return array( 'success' => ( new Connection_Manager( 'jetpack' ) )->is_user_connected( $this->user_id ) ); } diff --git a/projects/plugins/jetpack/load-jetpack.php b/projects/plugins/jetpack/load-jetpack.php index 87e795158fa33..5a02dcb904ec0 100644 --- a/projects/plugins/jetpack/load-jetpack.php +++ b/projects/plugins/jetpack/load-jetpack.php @@ -40,7 +40,6 @@ function jetpack_should_use_minified_assets() { // @todo: Abstract out the admin functions, and only include them if is_admin() require_once JETPACK__PLUGIN_DIR . 'class.jetpack.php'; require_once JETPACK__PLUGIN_DIR . 'class.jetpack-network.php'; -require_once JETPACK__PLUGIN_DIR . 'class.jetpack-data.php'; require_once JETPACK__PLUGIN_DIR . 'class.jetpack-client-server.php'; require_once JETPACK__PLUGIN_DIR . 'class.jetpack-user-agent.php'; require_once JETPACK__PLUGIN_DIR . 'class.jetpack-post-images.php'; diff --git a/projects/plugins/jetpack/modules/comments/comments.php b/projects/plugins/jetpack/modules/comments/comments.php index ef9377da0f2aa..6d2149c7f5604 100644 --- a/projects/plugins/jetpack/modules/comments/comments.php +++ b/projects/plugins/jetpack/modules/comments/comments.php @@ -277,11 +277,11 @@ public function comment_form_after() { $params['has_cookie_consent'] = (int) ! empty( $commenter['comment_author_email'] ); } - $blog_token = Jetpack_Data::get_access_token(); + $blog_token = ( new Tokens() )->get_access_token(); list( $token_key ) = explode( '.', $blog_token->secret, 2 ); // Prophylactic check: anything else should never happen. if ( $token_key && $token_key !== $blog_token->secret ) { - // Is the token a Special Token (@see class.jetpack-data.php)? + // Is the token a Special Token (@see class.tokens.php)? if ( preg_match( '/^;.\d+;\d+;$/', $token_key, $matches ) ) { // The token key for a Special Token is public. $params['token_key'] = $token_key; @@ -510,7 +510,7 @@ public function pre_comment_on_post( $comment ) { $post_array['hc_avatar'] = htmlentities( $post_array['hc_avatar'] ); } - $blog_token = Jetpack_Data::get_access_token( false, $post_array['token_key'] ); + $blog_token = ( new Tokens() )->get_access_token( false, $post_array['token_key'] ); if ( ! $blog_token ) { wp_die( __( 'Unknown security token.', 'jetpack' ), 400 ); } diff --git a/projects/plugins/jetpack/modules/post-by-email/class-jetpack-post-by-email.php b/projects/plugins/jetpack/modules/post-by-email/class-jetpack-post-by-email.php index f631a6278f33a..5aea5209056af 100644 --- a/projects/plugins/jetpack/modules/post-by-email/class-jetpack-post-by-email.php +++ b/projects/plugins/jetpack/modules/post-by-email/class-jetpack-post-by-email.php @@ -5,7 +5,7 @@ * @package automattic/jetpack */ -use Automattic\Jetpack\Connection\Manager as Connection_Manager; +use Automattic\Jetpack\Connection\Tokens; use Automattic\Jetpack\Redirect; /** @@ -72,7 +72,7 @@ public function profile_scripts() { * @return bool True if connected. False if not. */ public function check_user_connection() { - $user_token = ( new Connection_Manager() )->get_access_token( get_current_user_id() ); + $user_token = ( new Tokens() )->get_access_token( get_current_user_id() ); $is_user_connected = $user_token && ! is_wp_error( $user_token ); diff --git a/projects/plugins/jetpack/modules/publicize/publicize-jetpack.php b/projects/plugins/jetpack/modules/publicize/publicize-jetpack.php index fd30e65d7079b..6bf27bc16fa39 100644 --- a/projects/plugins/jetpack/modules/publicize/publicize-jetpack.php +++ b/projects/plugins/jetpack/modules/publicize/publicize-jetpack.php @@ -47,7 +47,7 @@ function add_disconnect_notice() { function force_user_connection() { global $current_user; - $user_token = Jetpack_Data::get_access_token( $current_user->ID ); + $user_token = ( new Tokens() )->get_access_token( $current_user->ID ); $is_user_connected = $user_token && ! is_wp_error( $user_token ); // If the user is already connected via Jetpack, then we're good diff --git a/projects/plugins/jetpack/modules/wpcom-block-editor/class-jetpack-wpcom-block-editor.php b/projects/plugins/jetpack/modules/wpcom-block-editor/class-jetpack-wpcom-block-editor.php index 541fcdd9957e4..694ecba2533cc 100644 --- a/projects/plugins/jetpack/modules/wpcom-block-editor/class-jetpack-wpcom-block-editor.php +++ b/projects/plugins/jetpack/modules/wpcom-block-editor/class-jetpack-wpcom-block-editor.php @@ -7,6 +7,7 @@ * @package automattic/jetpack */ +use Automattic\Jetpack\Connection\Tokens; /** * WordPress.com Block editor for Jetpack */ @@ -224,7 +225,7 @@ public function verify_frame_nonce( $nonce, $action ) { return false; } - $token = Jetpack_Data::get_access_token( $this->nonce_user_id ); + $token = ( new Tokens() )->get_access_token( $this->nonce_user_id ); if ( ! $token ) { return false; } @@ -268,7 +269,7 @@ public function verify_frame_nonce( $nonce, $action ) { */ public function filter_salt( $salt, $scheme ) { if ( 'jetpack_frame_nonce' === $scheme ) { - $token = Jetpack_Data::get_access_token( $this->nonce_user_id ); + $token = ( new Tokens() )->get_access_token( $this->nonce_user_id ); if ( $token ) { $salt = $token->secret; diff --git a/projects/plugins/jetpack/tests/php/general/test_class.jetpack-client-server.php b/projects/plugins/jetpack/tests/php/general/test_class.jetpack-client-server.php index 8988dcc2083af..8f63d90567e73 100644 --- a/projects/plugins/jetpack/tests/php/general/test_class.jetpack-client-server.php +++ b/projects/plugins/jetpack/tests/php/general/test_class.jetpack-client-server.php @@ -1,5 +1,4 @@ connection = new Connection_Manager(); - Jetpack_Options::update_option( 'blog_token', self::STORED ); - Jetpack_Options::update_option( 'user_tokens', [ - 1 => 'user-one.uno.1', - 2 => 'user-two.dos.2', - 4 => 'user-four.cuatro', // malformed: missing user ID. - 5 => 'user-four-cuatro-5', // malformed: wrong structrue. - 6 => '', // malformed: falsey value. - 7 => 'user-seven.siete.1', // malformed: wrong user ID. - ] ); - Jetpack_Options::update_option( 'master_user', 2 ); - } - - public function tearDown() { - Jetpack_Options::delete_option( 'blog_token' ); - Jetpack_Options::delete_option( 'user_tokens' ); - Jetpack_Options::delete_option( 'master_user' ); - - Constants::clear_constants(); - $this->connection = null; - parent::tearDown(); - } - - public function test_get_access_token_with_no_args_returns_false_when_no_blog_token() { - Jetpack_Options::delete_option( 'blog_token' ); - $token = $this->connection->get_access_token(); - - $this->assertFalse( $token ); - } - - public function test_get_access_token_with_no_args_returns_blog_token() { - $token = $this->connection->get_access_token(); - $this->assertEquals( self::STORED, $token->secret ); - $this->assertEquals( 0, $token->external_user_id ); - } - - public function test_get_access_token_with_no_args_returns_defined_blog_token_when_constant_set() { - Constants::set_constant( 'JETPACK_BLOG_TOKEN', self::DEFINED ); - - $token = $this->connection->get_access_token(); - - $this->assertEquals( self::DEFINED, $token->secret ); - $this->assertEquals( 0, $token->external_user_id ); - } - - public function test_get_access_token_with_no_args_returns_defined_blog_token_when_constant_set_and_no_stored_token() { - Jetpack_Options::delete_option( 'blog_token' ); - Constants::set_constant( 'JETPACK_BLOG_TOKEN', self::DEFINED ); - - $token = $this->connection->get_access_token(); - - $this->assertEquals( self::DEFINED, $token->secret ); - $this->assertEquals( 0, $token->external_user_id ); - } - - - public function test_get_access_token_with_stored_key_returns_stored_blog_token() { - Constants::set_constant( 'JETPACK_BLOG_TOKEN', self::DEFINED ); - - $token = $this->connection->get_access_token( false, '12345' ); - - $this->assertEquals( self::STORED, $token->secret ); - $this->assertEquals( 0, $token->external_user_id ); - } - - public function test_get_access_token_with_magic_key_returns_stored_blog_token() { - Constants::set_constant( 'JETPACK_BLOG_TOKEN', self::DEFINED ); - - $token = $this->connection->get_access_token( false, Connection_Manager::MAGIC_NORMAL_TOKEN_KEY ); - - $this->assertEquals( self::STORED, $token->secret ); - $this->assertEquals( 0, $token->external_user_id ); - } - - - public function test_get_access_token_with_magic_key_returns_defined_blog_token_if_it_looks_like_a_stored_token_and_no_stored_token() { - Jetpack_Options::delete_option( 'blog_token' ); - Constants::set_constant( 'JETPACK_BLOG_TOKEN', self::STORED ); - - $token = $this->connection->get_access_token( false, Connection_Manager::MAGIC_NORMAL_TOKEN_KEY ); - - $this->assertEquals( self::STORED, $token->secret ); - $this->assertEquals( 0, $token->external_user_id ); - } - - public function test_get_access_token_with_no_args_returns_first_defined_blog_token_when_constant_multi_set() { - Constants::set_constant( 'JETPACK_BLOG_TOKEN', self::DEFINED_MULTI ); - - $token = $this->connection->get_access_token(); - - $this->assertEquals( ';hello;.world', $token->secret ); - $this->assertEquals( 0, $token->external_user_id ); - } - - public function test_get_access_token_with_no_args_returns_first_defined_blog_token_when_constant_multi_set_and_no_stored_token() { - Jetpack_Options::delete_option( 'blog_token' ); - Constants::set_constant( 'JETPACK_BLOG_TOKEN', self::DEFINED_MULTI ); - - $token = $this->connection->get_access_token(); - - $this->assertEquals( ';hello;.world', $token->secret ); - $this->assertEquals( 0, $token->external_user_id ); - } - - public function test_get_access_token_with_token_key_returns_matching_token_when_constant_multi_set() { - Constants::set_constant( 'JETPACK_BLOG_TOKEN', self::DEFINED_MULTI ); - - $token = $this->connection->get_access_token( false, ';foo;' ); - - $this->assertEquals( ';foo;.bar', $token->secret ); - $this->assertEquals( 0, $token->external_user_id ); - } - - public function test_get_access_token_with_token_key_returns_matching_token_when_constant_multi_set_and_no_stored_token() { - Jetpack_Options::delete_option( 'blog_token' ); - Constants::set_constant( 'JETPACK_BLOG_TOKEN', self::DEFINED_MULTI ); - - $token = $this->connection->get_access_token( false, ';foo;' ); - - $this->assertEquals( ';foo;.bar', $token->secret ); - $this->assertEquals( 0, $token->external_user_id ); - } - - public function test_get_access_token_with_magic_key_returns_stored_token_when_constant_multi_set() { - Constants::set_constant( 'JETPACK_BLOG_TOKEN', self::DEFINED_MULTI ); - - $token = $this->connection->get_access_token( false, Connection_Manager::MAGIC_NORMAL_TOKEN_KEY ); - - $this->assertEquals( self::STORED, $token->secret ); - $this->assertEquals( 0, $token->external_user_id ); - } - - public function test_get_access_token_with_magic_key_returns_matching_token_when_constant_multi_set_and_no_stored_token() { - Jetpack_Options::delete_option( 'blog_token' ); - Constants::set_constant( 'JETPACK_BLOG_TOKEN', self::DEFINED_MULTI ); - - $token = $this->connection->get_access_token( false, Connection_Manager::MAGIC_NORMAL_TOKEN_KEY ); - - $this->assertEquals( 'looks-like-a.stored-token', $token->secret ); - $this->assertEquals( 0, $token->external_user_id ); - } - - public function test_get_access_token_with_token_key_requires_full_key() { - Constants::set_constant( 'JETPACK_BLOG_TOKEN', self::DEFINED_MULTI ); - - $token = $this->connection->get_access_token( false, ';fo' ); - - $this->assertFalse( $token ); - } - - public function test_get_access_token_with_user_id_returns_false_when_no_user_tokens() { - Jetpack_Options::delete_option( 'user_tokens' ); - - $token = $this->connection->get_access_token( 1 ); - $this->assertFalse( $token ); - } - - public function test_get_access_token_with_user_id() { - $token = $this->connection->get_access_token( 1 ); - - $this->assertEquals( 'user-one.uno', $token->secret ); - } - - public function test_get_access_token_with_master_user_returns_false_when_no_master_user() { - Jetpack_Options::delete_option( 'master_user' ); - $token = $this->connection->get_access_token( Connection_Manager::CONNECTION_OWNER ); - - $this->assertFalse( $token ); - } - - public function test_get_access_token_with_master_user() { - $token = $this->connection->get_access_token( Connection_Manager::CONNECTION_OWNER ); - - $this->assertEquals( 'user-two.dos', $token->secret ); - } - - public function test_get_access_token_with_unconnected_user() { - $token = $this->connection->get_access_token( 3 ); - - $this->assertFalse( $token ); - } - - public function test_get_access_token_with_malformed_token_with_missing_user_id() { - $token = $this->connection->get_access_token( 4 ); - - $this->assertFalse( $token ); - } - - public function test_get_access_token_with_malformed_token_with_wrong_structure() { - $token = $this->connection->get_access_token( 5 ); - - $this->assertFalse( $token ); - } - - public function test_get_access_token_with_malformed_token_with_falsey_value() { - $token = $this->connection->get_access_token( 6 ); - - $this->assertFalse( $token ); - } - - public function test_get_access_token_with_malformed_token_with_wrong_user_id() { - $token = $this->connection->get_access_token( 7 ); - - $this->assertFalse( $token ); - } - - public function test_get_access_token_with_empty_constant_does_not_generate_notice() { - Constants::set_constant( 'JETPACK_BLOG_TOKEN', '' ); - - $token = $this->connection->get_access_token(); - - $this->assertEquals( self::STORED, $token->secret ); - } -} diff --git a/projects/plugins/jetpack/tests/php/general/test_class.jetpack-xmlrpc-server.php b/projects/plugins/jetpack/tests/php/general/test_class.jetpack-xmlrpc-server.php index 3b8b9a11bec98..e0d5d2785b134 100644 --- a/projects/plugins/jetpack/tests/php/general/test_class.jetpack-xmlrpc-server.php +++ b/projects/plugins/jetpack/tests/php/general/test_class.jetpack-xmlrpc-server.php @@ -1,7 +1,6 @@ user->create(); $user = get_user_by( 'ID', $user_id ); $user->set_role( 'administrator' ); - Connection_Utils::update_user_token( $user_id, sprintf( '%s.%s.%d', 'key', 'private', $user_id ), false ); + ( new Tokens() )->update_user_token( $user_id, sprintf( '%s.%s.%d', 'key', 'private', $user_id ), false ); self::$xmlrpc_admin = $user_id; } @@ -180,7 +179,7 @@ public function test_remote_connect_error_when_site_active() { 'blog_token' => 1, 'id' => 1001, ) ); - Connection_Utils::update_user_token( 1, sprintf( '%s.%d', 'token', 1 ), true ); + ( new Tokens() )->update_user_token( 1, sprintf( '%s.%d', 'token', 1 ), true ); $server = new Jetpack_XMLRPC_Server(); diff --git a/projects/plugins/jetpack/tests/php/general/test_class.jetpack.php b/projects/plugins/jetpack/tests/php/general/test_class.jetpack.php index c2f3869ce50e8..334776d42d03b 100644 --- a/projects/plugins/jetpack/tests/php/general/test_class.jetpack.php +++ b/projects/plugins/jetpack/tests/php/general/test_class.jetpack.php @@ -616,98 +616,6 @@ function test_normalize_url_protocol_agnostic_strips_protocol_for_ip() { $this->assertTrue( '123.456.789.0/' === $url_normalized ); } - /** - * The generate_secrets method should return and store the secret. - * - * @author zinigor - * @covers Jetpack::generate_secrets - */ - function test_generate_secrets_stores_secrets() { - $secret = Jetpack::generate_secrets( 'name' ); - - $this->assertEquals( $secret, Jetpack::get_secrets( 'name', get_current_user_id() ) ); - } - - /** - * The generate_secrets method should return the same secret after calling generate several times. - * - * @author zinigor - * @covers Jetpack::generate_secrets - */ - function test_generate_secrets_does_not_regenerate_secrets() { - $secret = Jetpack::generate_secrets( 'name' ); - $secret2 = Jetpack::generate_secrets( 'name' ); - $secret3 = Jetpack::generate_secrets( 'name' ); - - $this->assertEquals( $secret, $secret2 ); - $this->assertEquals( $secret, $secret3 ); - $this->assertEquals( $secret, Jetpack::get_secrets( 'name', get_current_user_id() ) ); - } - - /** - * The generate_secrets method should work with filters on wp_generate_password. - * - * @author zinigor - * @covers Jetpack::generate_secrets - */ - function test_generate_secrets_works_with_filters() { - add_filter( 'random_password', array( __CLASS__, 'cyrillic_salt' ), 20 ); - add_filter( 'random_password', array( __CLASS__, 'kanji_salt' ), 21 ); - - $secret = Jetpack::generate_secrets( 'name' ); - - $this->assertEquals( $secret, Jetpack::get_secrets( 'name', get_current_user_id() ) ); - - remove_filter( 'random_password', array( __CLASS__, 'cyrillic_salt' ), 20 ); - remove_filter( 'random_password', array( __CLASS__, 'kanji_salt' ), 21 ); - } - - /** - * The generate_secrets method should work with long strings. - * - * @author zinigor - * @covers Jetpack::generate_secrets - */ - function test_generate_secrets_works_with_long_strings() { - add_filter( 'random_password', array( __CLASS__, 'multiply_filter' ), 20 ); - - $secret = Jetpack::generate_secrets( 'name' ); - - $this->assertEquals( $secret, Jetpack::get_secrets( 'name', get_current_user_id() ) ); - - remove_filter( 'random_password', array( __CLASS__, 'multiply_filter' ), 20 ); - } - - /** - * The get_secrets method should return an error for unknown secrets - * - * @author roccotripaldi - * @covers Jetpack::generate_secrets - */ - function test_generate_secrets_returns_error_for_unknown_secrets() { - Jetpack::generate_secrets( 'name' ); - $unknown_action = Jetpack::get_secrets( 'unknown', get_current_user_id() ); - $unknown_user_id = Jetpack::get_secrets( 'name', 5 ); - - $this->assertInstanceOf( 'WP_Error', $unknown_action ); - $this->assertArrayHasKey( 'verify_secrets_missing', $unknown_action->errors ); - $this->assertInstanceOf( 'WP_Error', $unknown_user_id ); - $this->assertArrayHasKey( 'verify_secrets_missing', $unknown_user_id->errors ); - } - - /** - * The get_secrets method should return an error for expired secrets - * - * @author roccotripaldi - * @covers Jetpack::generate_secrets - */ - function test_generate_secrets_returns_error_for_expired_secrets() { - Jetpack::generate_secrets( 'name', get_current_user_id(), -600 ); - $expired = Jetpack::get_secrets( 'name', get_current_user_id() ); - $this->assertInstanceOf( 'WP_Error', $expired ); - $this->assertArrayHasKey( 'verify_secrets_expired', $expired->errors ); - } - /** * Parse the referer on plugin activation and record the activation source * - featured plugins page diff --git a/projects/plugins/jetpack/tests/php/modules/wpcom-block-editor/test_class-jetpack-wpcom-block-editor.php b/projects/plugins/jetpack/tests/php/modules/wpcom-block-editor/test_class-jetpack-wpcom-block-editor.php index 66465765e20e5..1f1b01093045a 100644 --- a/projects/plugins/jetpack/tests/php/modules/wpcom-block-editor/test_class-jetpack-wpcom-block-editor.php +++ b/projects/plugins/jetpack/tests/php/modules/wpcom-block-editor/test_class-jetpack-wpcom-block-editor.php @@ -1,8 +1,6 @@ assertFalse( $wpcom_block_editor->verify_frame_nonce( $this->create_nonce(), 'action' ) ); - // Set user token. - Connection_Utils::update_user_token( $this->user_id, sprintf( '%s.%d.%d', 'token', JETPACK__API_VERSION, $this->user_id ), true ); + ( new Automattic\Jetpack\Connection\Tokens() )->update_user_token( $this->user_id, sprintf( '%s.%d.%d', 'token', JETPACK__API_VERSION, $this->user_id ), true ); + $nonce = $this->create_nonce(); // User ID mismatch. @@ -79,7 +77,7 @@ public function create_nonce() { */ public function filter_salt( $salt, $scheme ) { if ( 'jetpack_frame_nonce' === $scheme ) { - $token = Jetpack_Data::get_access_token( $this->user_id ); + $token = ( new Automattic\Jetpack\Connection\Tokens() )->get_access_token( $this->user_id ); if ( $token ) { $salt = $token->secret; diff --git a/tools/phpcs-excludelist.json b/tools/phpcs-excludelist.json index 653e4e5a23bbc..0f6b6fa7b4b2d 100644 --- a/tools/phpcs-excludelist.json +++ b/tools/phpcs-excludelist.json @@ -67,7 +67,6 @@ "projects/plugins/jetpack/class.jetpack-cli.php", "projects/plugins/jetpack/class.jetpack-client-server.php", "projects/plugins/jetpack/class.jetpack-connection-banner.php", - "projects/plugins/jetpack/class.jetpack-data.php", "projects/plugins/jetpack/class.jetpack-heartbeat.php", "projects/plugins/jetpack/class.jetpack-idc.php", "projects/plugins/jetpack/class.jetpack-modules-list-table.php", @@ -431,7 +430,6 @@ "projects/plugins/jetpack/tests/php/general/test-class.jetpack-gutenberg.php", "projects/plugins/jetpack/tests/php/general/test-class.jetpack-options.php", "projects/plugins/jetpack/tests/php/general/test_class.jetpack-client-server.php", - "projects/plugins/jetpack/tests/php/general/test_class.jetpack-data.php", "projects/plugins/jetpack/tests/php/general/test_class.jetpack-heartbeat.php", "projects/plugins/jetpack/tests/php/general/test_class.jetpack-plan.php", "projects/plugins/jetpack/tests/php/general/test_class.jetpack-xmlrpc-server.php", From b6b96c3a2a720f49c46d34b2d8eb98e8dc6bd373 Mon Sep 17 00:00:00 2001 From: Glen Davies Date: Mon, 22 Feb 2021 22:48:34 +1300 Subject: [PATCH 29/34] Set up testing-library/react for testing jetpack blocks and add example tests to WhatsApp block (#18582) Co-authored-by: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Co-authored-by: Glen Davies --- .../gathering-tweetstorms/test/editor.js | 5 - .../test/match-media.mock | 13 - .../whatsapp-button/configuration.js | 203 +++++++++++++ .../send-a-message/whatsapp-button/edit.js | 217 +------------- .../whatsapp-button/test/configuration.js | 108 +++++++ .../whatsapp-button/test/edit.js | 50 ++++ .../fixtures/jetpack__whatsapp-button.html | 5 + .../fixtures/jetpack__whatsapp-button.json | 29 ++ .../jetpack__whatsapp-button.parsed.json | 36 +++ .../jetpack__whatsapp-button.serialized.html | 5 + ...etpack__whatsapp-button__deprecated-1.html | 5 + ...etpack__whatsapp-button__deprecated-1.json | 29 ++ ..._whatsapp-button__deprecated-1.parsed.json | 36 +++ ...tsapp-button__deprecated-1.serialized.html | 5 + .../whatsapp-button/test/validate.js | 15 + .../extensions/shared/test/block-fixtures.js | 283 ++++++++++++++++++ .../extensions/shared/test/block-fixtures.md | 111 +++++++ projects/plugins/jetpack/jest.config.js | 5 +- projects/plugins/jetpack/package.json | 8 +- .../plugins/jetpack/tests/jest-globals.js | 15 + projects/plugins/jetpack/yarn.lock | 282 ++++------------- 21 files changed, 1018 insertions(+), 447 deletions(-) delete mode 100644 projects/plugins/jetpack/extensions/blocks/gathering-tweetstorms/test/match-media.mock create mode 100644 projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/configuration.js create mode 100644 projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/test/configuration.js create mode 100644 projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/test/edit.js create mode 100644 projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/test/fixtures/jetpack__whatsapp-button.html create mode 100644 projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/test/fixtures/jetpack__whatsapp-button.json create mode 100644 projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/test/fixtures/jetpack__whatsapp-button.parsed.json create mode 100644 projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/test/fixtures/jetpack__whatsapp-button.serialized.html create mode 100644 projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/test/fixtures/jetpack__whatsapp-button__deprecated-1.html create mode 100644 projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/test/fixtures/jetpack__whatsapp-button__deprecated-1.json create mode 100644 projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/test/fixtures/jetpack__whatsapp-button__deprecated-1.parsed.json create mode 100644 projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/test/fixtures/jetpack__whatsapp-button__deprecated-1.serialized.html create mode 100644 projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/test/validate.js create mode 100644 projects/plugins/jetpack/extensions/shared/test/block-fixtures.js create mode 100644 projects/plugins/jetpack/extensions/shared/test/block-fixtures.md create mode 100644 projects/plugins/jetpack/tests/jest-globals.js diff --git a/projects/plugins/jetpack/extensions/blocks/gathering-tweetstorms/test/editor.js b/projects/plugins/jetpack/extensions/blocks/gathering-tweetstorms/test/editor.js index 46d5a36bbd8a3..505cc6c27ccf9 100644 --- a/projects/plugins/jetpack/extensions/blocks/gathering-tweetstorms/test/editor.js +++ b/projects/plugins/jetpack/extensions/blocks/gathering-tweetstorms/test/editor.js @@ -1,7 +1,3 @@ -/** - * @jest-environment jsdom - */ - /** * External dependencies */ @@ -10,7 +6,6 @@ import { mount } from 'enzyme'; /** * Internal dependencies */ -import './match-media.mock'; import addTweetstormToTweets from '../editor'; describe( 'addTweetstormToTweets', () => { diff --git a/projects/plugins/jetpack/extensions/blocks/gathering-tweetstorms/test/match-media.mock b/projects/plugins/jetpack/extensions/blocks/gathering-tweetstorms/test/match-media.mock deleted file mode 100644 index 855fdd3342279..0000000000000 --- a/projects/plugins/jetpack/extensions/blocks/gathering-tweetstorms/test/match-media.mock +++ /dev/null @@ -1,13 +0,0 @@ -Object.defineProperty( window, 'matchMedia', { - writable: true, - value: jest.fn().mockImplementation( query => ( { - matches: false, - media: query, - onchange: null, - addListener: jest.fn(), // deprecated - removeListener: jest.fn(), // deprecated - addEventListener: jest.fn(), - removeEventListener: jest.fn(), - dispatchEvent: jest.fn(), - } ) ), -} ); \ No newline at end of file diff --git a/projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/configuration.js b/projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/configuration.js new file mode 100644 index 0000000000000..c1d578349c56c --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/send-a-message/whatsapp-button/configuration.js @@ -0,0 +1,203 @@ +/** + * External dependencies + */ +import { __, _x } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; +import { + BaseControl, + TextControl, + TextareaControl, + SelectControl, + Path, + ToggleControl, + Button, + PanelBody, + ToolbarGroup, + Dropdown, +} from '@wordpress/components'; +import { PanelColorSettings } from '@wordpress/block-editor'; +import { DOWN } from '@wordpress/keycodes'; + +/** + * Internal dependencies + */ +import { countryCodes } from '../shared/countrycodes.js'; +import renderMaterialIcon from '../../../shared/render-material-icon'; +import HelpMessage from '../../../shared/help-message'; + +const WHATSAPP_GREEN = '#25D366'; +const WHATSAPP_DARK = '#465B64'; +const WHATSAPP_LIGHT = '#F4F4F4'; + +export default function WhatsAppButtonConfiguration( { attributes, setAttributes, context } ) { + const { countryCode, phoneNumber, firstMessage, openInNewTab, backgroundColor } = attributes; + + const [ isValidPhoneNumber, setIsValidPhoneNumber ] = useState( true ); + + const validatePhoneNumber = newPhoneNumber => { + // No alphabetical characters but allow dots, dashes, and brackets. + // These will be stripped for the WhatsApp API (only numbers), but retain + // them in the UI for a more readable number for the user. + const phoneNumberRegEx = RegExp( /^[+]?[\s./0-9]*[(]?[0-9]{1,4}[)]?[-\s./0-9]*$/, 'g' ); + + if ( undefined === newPhoneNumber || newPhoneNumber.length < 1 ) { + return false; + } + + return phoneNumberRegEx.test( countryCode.replace( /\D/g, '' ) + newPhoneNumber ); + }; + + const setBackgroundColor = color => { + setAttributes( { backgroundColor: color } ); + + if ( color === undefined || color === WHATSAPP_GREEN || color === WHATSAPP_DARK ) { + return setAttributes( { colorClass: 'dark' } ); + } + + setAttributes( { colorClass: 'light' } ); + }; + + const renderSettingsToggle = ( isOpen, onToggle ) => { + const openOnArrowDown = event => { + if ( ! isOpen && event.keyCode === DOWN ) { + event.preventDefault(); + event.stopPropagation(); + onToggle(); + } + }; + + return ( +