diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 36cea536a7cbd..55fc1caa1d31d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,9 +8,6 @@ # Plugins -# Other bits of the Codebase -/projects/plugins/jetpack/_inc/lib/debugger/ @kraftbj - # Functionality that is important to our partners /projects/plugins/jetpack/class.jetpack-cli.php @automattic/jetpack-infinity /tools/partner-provision.sh @automattic/jetpack-infinity diff --git a/.github/files/required-review.yaml b/.github/files/required-review.yaml index 886f4af821007..361be3270cb0f 100644 --- a/.github/files/required-review.yaml +++ b/.github/files/required-review.yaml @@ -24,6 +24,8 @@ - jetpack-reach - red - yamato-backup-and-security + - '@CGastrell' + - '@ice9js' # Jetpack Approvers review the Jetpack plugin and all packages, except for those with specific team ownership. - name: Jetpack and packages @@ -35,6 +37,7 @@ - '!projects/js-packages/svelte-data-sync-client/**' - '!projects/packages/wp-js-data-sync/**' - '!projects/packages/backup/**' + - '!projects/packages/forms/**' - '!projects/packages/import/**' - '!projects/packages/search/**' - '!projects/packages/stats/**' @@ -154,6 +157,18 @@ - jetpack-reach - jetpack-approvers +# The Crowdsignal team reviews changes to the Forms package. +- name: Forms + paths: + - 'projects/packages/forms/**' + - '!projects/plugins/*/composer.lock' + - '!projects/plugins/*/composer.json' + - '!projects/plugins/*/changelog/*' + teams: + - '@CGastrell' + - '@ice9js' + - jetpack-approvers + # Jetpack Approvers review everything that hasn't been specifically assigned above. # This needs to be last. - name: Default to Jetpack Approvers diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b6346c277578..6b2740b2a7a7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1354,6 +1354,9 @@ importers: '@wordpress/i18n': specifier: 4.28.0 version: 4.28.0 + '@wordpress/icons': + specifier: 9.19.0 + version: 9.19.0 classnames: specifier: 2.3.1 version: 2.3.1 @@ -2340,6 +2343,9 @@ importers: concurrently: specifier: 7.6.0 version: 7.6.0 + livereload: + specifier: 0.9.3 + version: 0.9.3 postcss: specifier: 8.4.21 version: 8.4.21 @@ -6050,7 +6056,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.13.11 - dev: true /@babel/template@7.20.7: resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} @@ -7721,7 +7726,7 @@ packages: engines: {node: '>= 8.0.0'} dependencies: estree-walker: 2.0.2 - picomatch: 2.2.3 + picomatch: 2.3.1 dev: true /@rushstack/eslint-patch@1.1.4: @@ -11125,7 +11130,7 @@ packages: resolution: {integrity: sha512-mjpchAandG1igJSDgK2JON61rTaTT7dIZ2X47o6mNCODbh1AVE0O6aX7+edRRCFyeUdyYnUFin+MALH2n7jKog==} engines: {node: '>=12'} dependencies: - '@babel/runtime': 7.20.13 + '@babel/runtime': 7.20.7 '@wordpress/element': 5.5.0 '@wordpress/primitives': 3.26.0 @@ -11882,7 +11887,7 @@ packages: engines: {node: '>= 8'} dependencies: normalize-path: 3.0.0 - picomatch: 2.2.3 + picomatch: 2.3.1 /app-root-dir@1.0.2: resolution: {integrity: sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==} @@ -17761,6 +17766,24 @@ packages: - zenObservable dev: false + /livereload-js@3.4.1: + resolution: {integrity: sha512-5MP0uUeVCec89ZbNOT/i97Mc+q3SxXmiUGhRFOTmhrGPn//uWVQdCvcLJDy64MSBR5MidFdOR7B9viumoavy6g==} + dev: true + + /livereload@0.9.3: + resolution: {integrity: sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw==} + engines: {node: '>=8.0.0'} + hasBin: true + dependencies: + chokidar: 3.5.3 + livereload-js: 3.4.1 + opts: 2.0.2 + ws: 7.5.9 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: true + /load-json-file@1.1.0: resolution: {integrity: sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==} engines: {node: '>=0.10.0'} @@ -18813,6 +18836,10 @@ packages: word-wrap: 1.2.3 dev: true + /opts@2.0.2: + resolution: {integrity: sha512-k41FwbcLnlgnFh69f4qdUfvDQ+5vaSDnVPFI/y5XuhKRq97EnVVneO9F1ESVCdiVu4fCS2L8usX3mU331hB7pg==} + dev: true + /os-homedir@1.0.2: resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==} engines: {node: '>=0.10.0'} @@ -20410,7 +20437,7 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} dependencies: - picomatch: 2.2.3 + picomatch: 2.3.1 /reakit-system@0.15.2(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-TvRthEz0DmD0rcJkGamMYx+bATwnGNWJpe/lc8UV2Js8nnPvkaxrHk5fX9cVASFrWbaIyegZHCWUBfxr30bmmA==} diff --git a/projects/github-actions/repo-gardening/changelog/fix-kitkat-message-emoji b/projects/github-actions/repo-gardening/changelog/fix-kitkat-message-emoji new file mode 100644 index 0000000000000..0ddd02fd1df07 --- /dev/null +++ b/projects/github-actions/repo-gardening/changelog/fix-kitkat-message-emoji @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Use correct emoji in Kitkat notifications diff --git a/projects/github-actions/repo-gardening/src/tasks/notify-kitkat/index.js b/projects/github-actions/repo-gardening/src/tasks/notify-kitkat/index.js index 00334f6c3ee74..b2731a1bce290 100644 --- a/projects/github-actions/repo-gardening/src/tasks/notify-kitkat/index.js +++ b/projects/github-actions/repo-gardening/src/tasks/notify-kitkat/index.js @@ -119,7 +119,7 @@ function formatSlackMessage( payload, channel, message ) { */ async function notifyKitKat( payload, octokit ) { const { - issue: { number }, + issue: { number, state }, repository, } = payload; const { owner, name: repo } = repository; @@ -137,6 +137,12 @@ async function notifyKitKat( payload, octokit ) { return; } + // Only proceed if the issue is stil open. + if ( 'open' !== state ) { + debug( `notify-kitkat: Issue #${ number } is state '${ state }'. Aborting.` ); + return; + } + // Check if Kitkat input was already requested for that issue. const hasBeenRequested = await hasKitkatSignalLabel( octokit, ownerLogin, repo, number ); if ( hasBeenRequested ) { @@ -150,7 +156,7 @@ async function notifyKitKat( payload, octokit ) { debug( `notify-kitkat: Found a [Pri] High label on issue #${ number }. Sending in Slack message.` ); - const message = `:bug_police: New High priority bug! Please take a moment to triage this bug.`; + const message = `:bug-police: New High priority bug! Please take a moment to triage this bug.`; const slackMessageFormat = formatSlackMessage( payload, channel, message ); await sendSlackMessage( message, channel, slackToken, payload, slackMessageFormat ); } @@ -161,7 +167,7 @@ async function notifyKitKat( payload, octokit ) { debug( `notify-kitkat: Found a [Pri] BLOCKER label on issue #${ number }. Sending in Slack message.` ); - const message = `:bug_police: New Blocker bug! Please take a moment to triage this bug.`; + const message = `:bug-police: New Blocker bug! Please take a moment to triage this bug.`; const slackMessageFormat = formatSlackMessage( payload, channel, message ); await sendSlackMessage( message, channel, slackToken, payload, slackMessageFormat ); } diff --git a/projects/github-actions/required-review/README.md b/projects/github-actions/required-review/README.md index a14164870426b..8d43ac727ed5d 100644 --- a/projects/github-actions/required-review/README.md +++ b/projects/github-actions/required-review/README.md @@ -80,6 +80,12 @@ The requirements consist of an array of requirement objects. A requirement objec * `paths` is an array of path patterns, or the string "unmatched". If an array, the reviewers specified will be checked if any path in the array matches any path in the PR. If the string "unmatched", the reviewers are checked if any file in the PR has not been matched yet. +* `consume` is a boolean, defaulting to false. If set, any paths that match this rule will be ignored + for all following rules. + + This is intended for things like lockfiles or changelogs that you might want to allow everyone + to edit in any package in a monorepo, to avoid having to manually exclude these files in every + requirement for each different team owning some package. * `teams` is an array of strings that are GitHub team slugs in the organization or repository. A review is required from a member of any of these teams. @@ -108,6 +114,23 @@ the "Docs" and "Front end" review requirements. If you wanted to avoid that, you teams: - documentation +# Everyone can update lockfiles, even if later rules might otherwise match. +- name: Lockfiles + paths: + - 'packages/*/composer.lock' + - '**.css' + consume: true + teams: + - everyone + +# The "Some package" team must approve anything in `packages/some-package/`. +# Except for changes to `packages/some-package/composer.lock`, because the previous requirement consumed that path. +- name: Some package + paths: + - 'packages/some-package/**' + teams: + - some-package-team + # Any CSS and React .jsx files must be reviewed by a front-end developer AND by a designer, # OR by a member of the maintenance team. - name: Front end diff --git a/projects/github-actions/required-review/changelog/add-required-review-consume-option b/projects/github-actions/required-review/changelog/add-required-review-consume-option new file mode 100644 index 0000000000000..a715828e4ba5c --- /dev/null +++ b/projects/github-actions/required-review/changelog/add-required-review-consume-option @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Added `consume` option for requirements. diff --git a/projects/github-actions/required-review/package.json b/projects/github-actions/required-review/package.json index 22f64824fbc39..d6ab413fac1a6 100644 --- a/projects/github-actions/required-review/package.json +++ b/projects/github-actions/required-review/package.json @@ -1,6 +1,6 @@ { "name": "required-review", - "version": "3.0.2", + "version": "3.1.0-alpha", "description": "Check that a Pull Request has reviews from required teams.", "main": "index.js", "author": "Automattic", diff --git a/projects/github-actions/required-review/src/main.js b/projects/github-actions/required-review/src/main.js index a6bfb5eb0c407..296ddfd9db44f 100644 --- a/projects/github-actions/required-review/src/main.js +++ b/projects/github-actions/required-review/src/main.js @@ -63,17 +63,19 @@ async function main() { reviewers.forEach( r => core.info( r ) ); core.endGroup(); - const paths = await require( './paths.js' )(); + let paths = await require( './paths.js' )(); core.startGroup( `PR affects ${ paths.length } file(s)` ); paths.forEach( p => core.info( p ) ); core.endGroup(); - const matchedPaths = []; + let matchedPaths = []; let ok = true; for ( let i = 0; i < requirements.length; i++ ) { const r = requirements[ i ]; core.startGroup( `Checking requirement "${ r.name }"...` ); - if ( ! r.appliesToPaths( paths, matchedPaths ) ) { + let applies; + ( { applies, matchedPaths, paths } = r.appliesToPaths( paths, matchedPaths ) ); + if ( ! applies ) { core.endGroup(); core.info( `Requirement "${ r.name }" does not apply to any files in this PR.` ); } else if ( await r.isSatisfied( reviewers ) ) { diff --git a/projects/github-actions/required-review/src/requirement.js b/projects/github-actions/required-review/src/requirement.js index 50e3d2f1f8bfd..4d6fd5d735363 100644 --- a/projects/github-actions/required-review/src/requirement.js +++ b/projects/github-actions/required-review/src/requirement.js @@ -117,6 +117,7 @@ class Requirement { * @param {object} config - Object config * @param {string[]|string} config.paths - Paths this requirement applies to. Either an array of picomatch globs, or the string "unmatched". * @param {Array} config.teams - Team reviews requirements. + * @param {boolean} config.consume - Whether matched paths should be ignored by later rules. */ constructor( config ) { this.name = config.name || 'Unnamed requirement'; @@ -163,14 +164,19 @@ class Requirement { } this.reviewerFilter = buildReviewerFilter( config, { 'any-of': config.teams }, ' ' ); + this.consume = !! config.consume; } + // eslint-disable-next-line jsdoc/require-returns, jsdoc/require-returns-check -- Doesn't support documentation of object structure. /** * Test whether this requirement applies to the passed paths. * * @param {string[]} paths - Paths to test against. - * @param {string[]} matchedPaths - Paths that have already been matched. Will be modified if true is returned. - * @returns {boolean} Whether the requirement applies. + * @param {string[]} matchedPaths - Paths that have already been matched. + * @returns {object} _ Results object. + * @returns {boolean} _.applies Whether the requirement applies. + * @returns {string[]} _.matchedPaths New value for `matchedPaths`. + * @returns {string[]} _.paths New value for `paths`. */ appliesToPaths( paths, matchedPaths ) { let matches; @@ -183,14 +189,24 @@ class Requirement { } } - if ( matches.length !== 0 ) { + const ret = { + applies: matches.length !== 0, + matchedPaths, + paths, + }; + + if ( ret.applies ) { core.info( 'Matches the following files:' ); matches.forEach( m => core.info( ` - ${ m }` ) ); - matchedPaths.push( ...matches.filter( p => ! matchedPaths.includes( p ) ) ); - matchedPaths.sort(); + ret.matchedPaths = [ ...new Set( [ ...matchedPaths, ...matches ] ) ].sort(); + + if ( this.consume ) { + core.info( 'Consuming matched files!' ); + ret.paths = ret.paths.filter( p => ! matches.includes( p ) ); + } } - return matches.length !== 0; + return ret; } /** diff --git a/projects/github-actions/required-review/test.sh b/projects/github-actions/required-review/test.sh index 1ffd5a6884e54..f8960de04c87f 100755 --- a/projects/github-actions/required-review/test.sh +++ b/projects/github-actions/required-review/test.sh @@ -19,7 +19,7 @@ function addenv { eval "$( # Read defaults from action.yml node -e 'console.log( JSON.stringify( require( "js-yaml" ).load( require( "fs" ).readFileSync( process.argv[1] ) ) ) );' "$BASE/projects/github-actions/required-review/action.yml" | - jq -r '.inputs | to_entries | .[] | select( .key != "token" and .value.default ) | [ "addenv INPUT_", ( .key | ascii_upcase | sub( " "; "_" ) ), " ", @sh "\(.value.default)" ] | join( "" )'; + jq -r '.inputs | to_entries | .[] | select( .key != "token" and ( .value | has( "default" ) ) ) | [ "addenv INPUT_", ( .key | ascii_upcase | sub( " "; "_" ) ), " ", @sh "\(.value.default)" ] | join( "" )'; # Read values from .github/workflows/required-review.yml node -e 'console.log( JSON.stringify( require( "js-yaml" ).load( require( "fs" ).readFileSync( process.argv[1] ) ) ) );' "$BASE/.github/workflows/required-review.yml" | diff --git a/projects/js-packages/analytics/CHANGELOG.md b/projects/js-packages/analytics/CHANGELOG.md index 09682c8d69a5c..11d610f6ad199 100644 --- a/projects/js-packages/analytics/CHANGELOG.md +++ b/projects/js-packages/analytics/CHANGELOG.md @@ -2,6 +2,10 @@ ### This is a list detailing changes for the Jetpack RNA Analytics package releases. +## 0.1.25 - 2023-03-08 +### Changed +- Updated package dependencies. [#29289] + ## 0.1.24 - 2023-02-20 ### Changed - Minor internal updates. diff --git a/projects/js-packages/analytics/changelog/renovate-jest-monorepo b/projects/js-packages/analytics/changelog/renovate-jest-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/analytics/changelog/renovate-jest-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/analytics/package.json b/projects/js-packages/analytics/package.json index 70fb8bdf996b4..fda314efd4bda 100644 --- a/projects/js-packages/analytics/package.json +++ b/projects/js-packages/analytics/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-analytics", - "version": "0.1.25-alpha", + "version": "0.1.25", "description": "Jetpack Analytics Package", "author": "Automattic", "license": "GPL-2.0-or-later", diff --git a/projects/js-packages/api/CHANGELOG.md b/projects/js-packages/api/CHANGELOG.md index 1d5da0a0bd91c..7bb2d38d1abc0 100644 --- a/projects/js-packages/api/CHANGELOG.md +++ b/projects/js-packages/api/CHANGELOG.md @@ -2,6 +2,10 @@ ### This is a list detailing changes for the Jetpack RNA Components package releases. +## 0.15.2 - 2023-03-08 +### Changed +- Updated package dependencies. [#29216] + ## 0.15.1 - 2023-02-20 ### Changed - Minor internal updates. diff --git a/projects/js-packages/api/changelog/renovate-jest-monorepo b/projects/js-packages/api/changelog/renovate-jest-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/api/changelog/renovate-jest-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/api/changelog/renovate-wordpress-monorepo b/projects/js-packages/api/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/api/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/api/package.json b/projects/js-packages/api/package.json index 41a7e93473e3f..a4f69e97a1812 100644 --- a/projects/js-packages/api/package.json +++ b/projects/js-packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-api", - "version": "0.15.2-alpha", + "version": "0.15.2", "description": "Jetpack Api Package", "author": "Automattic", "license": "GPL-2.0-or-later", diff --git a/projects/js-packages/babel-plugin-replace-textdomain/CHANGELOG.md b/projects/js-packages/babel-plugin-replace-textdomain/CHANGELOG.md index c788fa23cb430..a47e1ef7aa0aa 100644 --- a/projects/js-packages/babel-plugin-replace-textdomain/CHANGELOG.md +++ b/projects/js-packages/babel-plugin-replace-textdomain/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.23] - 2023-03-08 +### Changed +- Updated package dependencies. [#29289] + ## [1.0.22] - 2023-02-06 ### Changed - Updated package dependencies. @@ -102,6 +106,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial release. - Replace missing domains too. +[1.0.23]: https://github.com/Automattic/babel-plugin-replace-textdomain/compare/v1.0.22...v1.0.23 [1.0.22]: https://github.com/Automattic/babel-plugin-replace-textdomain/compare/v1.0.21...v1.0.22 [1.0.21]: https://github.com/Automattic/babel-plugin-replace-textdomain/compare/v1.0.20...v1.0.21 [1.0.20]: https://github.com/Automattic/babel-plugin-replace-textdomain/compare/v1.0.19...v1.0.20 diff --git a/projects/js-packages/babel-plugin-replace-textdomain/changelog/remove-remnants-of-automated-code-coverage b/projects/js-packages/babel-plugin-replace-textdomain/changelog/remove-remnants-of-automated-code-coverage deleted file mode 100644 index 085e2e863ddb4..0000000000000 --- a/projects/js-packages/babel-plugin-replace-textdomain/changelog/remove-remnants-of-automated-code-coverage +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: removed -Comment: Remove `test-coverage` scripts and other remnants of automated code coverage. - - diff --git a/projects/js-packages/babel-plugin-replace-textdomain/changelog/renovate-jest-monorepo b/projects/js-packages/babel-plugin-replace-textdomain/changelog/renovate-jest-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/babel-plugin-replace-textdomain/changelog/renovate-jest-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/babel-plugin-replace-textdomain/package.json b/projects/js-packages/babel-plugin-replace-textdomain/package.json index dc5b152009063..10679b666ec51 100644 --- a/projects/js-packages/babel-plugin-replace-textdomain/package.json +++ b/projects/js-packages/babel-plugin-replace-textdomain/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/babel-plugin-replace-textdomain", - "version": "1.0.23-alpha", + "version": "1.0.23", "description": "A Babel plugin to replace the textdomain in gettext-style function calls.", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/babel-plugin-replace-textdomain/#readme", "bugs": { diff --git a/projects/js-packages/base-styles/CHANGELOG.md b/projects/js-packages/base-styles/CHANGELOG.md index 9035dadbb610c..2a5b394a3499e 100644 --- a/projects/js-packages/base-styles/CHANGELOG.md +++ b/projects/js-packages/base-styles/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.20] - 2023-03-08 +### Changed +- Updated package dependencies. [#29216] + ## [0.3.19] - 2023-02-15 ### Changed - Update to React 18. [#28710] @@ -147,6 +151,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated package dependencies. - Use Node 16.7.0 in tooling. This shouldn't change the behavior of the code itself. +[0.3.20]: https://github.com/Automattic/jetpack-base-styles/compare/0.3.19...0.3.20 [0.3.19]: https://github.com/Automattic/jetpack-base-styles/compare/0.3.18...0.3.19 [0.3.18]: https://github.com/Automattic/jetpack-base-styles/compare/0.3.17...0.3.18 [0.3.17]: https://github.com/Automattic/jetpack-base-styles/compare/0.3.16...0.3.17 diff --git a/projects/js-packages/base-styles/changelog/renovate-wordpress-monorepo b/projects/js-packages/base-styles/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/base-styles/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/base-styles/package.json b/projects/js-packages/base-styles/package.json index 2d447c902c305..8deb02b9f5c67 100644 --- a/projects/js-packages/base-styles/package.json +++ b/projects/js-packages/base-styles/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-base-styles", - "version": "0.3.20-alpha", + "version": "0.3.20", "description": "Jetpack components base styles", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/base-styles/#readme", "bugs": { diff --git a/projects/js-packages/components/CHANGELOG.md b/projects/js-packages/components/CHANGELOG.md index 70a9127ebfc44..be4c1f6923399 100644 --- a/projects/js-packages/components/CHANGELOG.md +++ b/projects/js-packages/components/CHANGELOG.md @@ -2,6 +2,13 @@ ### This is a list detailing changes for the Jetpack RNA Components package releases. +## 0.29.0 - 2023-03-08 +### Added +- Add loading placeholder component into js-packages [#29270] + +### Changed +- Updated package dependencies. [#29216] + ## 0.28.0 - 2023-02-28 ### Added - Added arrow-left and arrow-right icons to the Gridicon component [#28826] diff --git a/projects/js-packages/components/changelog/add-loading-placeholder b/projects/js-packages/components/changelog/add-loading-placeholder deleted file mode 100644 index 1a6cbb166c842..0000000000000 --- a/projects/js-packages/components/changelog/add-loading-placeholder +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Add loading placeholder component into js-packages diff --git a/projects/js-packages/components/changelog/add-zendesk-chat-module b/projects/js-packages/components/changelog/add-zendesk-chat-module new file mode 100644 index 0000000000000..009af355b6fe8 --- /dev/null +++ b/projects/js-packages/components/changelog/add-zendesk-chat-module @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add Zendesk chat module to My Jetpack page diff --git a/projects/js-packages/components/changelog/renovate-jest-monorepo b/projects/js-packages/components/changelog/renovate-jest-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/components/changelog/renovate-jest-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/components/changelog/renovate-wordpress-monorepo b/projects/js-packages/components/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/components/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/components/components/zendesk-chat/README.md b/projects/js-packages/components/components/zendesk-chat/README.md new file mode 100644 index 0000000000000..bcc5dedd7f135 --- /dev/null +++ b/projects/js-packages/components/components/zendesk-chat/README.md @@ -0,0 +1,23 @@ +# Summary + +This component adds a Zendesk chat widget via + +{#if $errors.length > 0} + {@const error = $errors[0]} +
+

Error

+

{error.message}

+
+{/if} +``` + +#### Global Error Store + +`initializeClient()` also returns a global error store that can be used to display errors from all stores. + +It uses `derived()` under the hood, so it will only update when the error store of any of the stores changes. + +For example, assuming + +```svelte + + +
+ {#if $globalErrors.length > 0} + {#each $globalErrors as error} +
+

Error

+

{error.message}

+
+ {/each} + {/if} +
+``` ## Security diff --git a/projects/js-packages/svelte-data-sync-client/changelog/boost-update-data-sync-error-handling b/projects/js-packages/svelte-data-sync-client/changelog/boost-update-data-sync-error-handling new file mode 100644 index 0000000000000..d4f9241892e73 --- /dev/null +++ b/projects/js-packages/svelte-data-sync-client/changelog/boost-update-data-sync-error-handling @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Added an error store to help track errors that happen during syncing. diff --git a/projects/js-packages/svelte-data-sync-client/src/API.ts b/projects/js-packages/svelte-data-sync-client/src/API.ts index cceb6363a3ae0..383a4339b4181 100644 --- a/projects/js-packages/svelte-data-sync-client/src/API.ts +++ b/projects/js-packages/svelte-data-sync-client/src/API.ts @@ -1,4 +1,5 @@ /* eslint-disable no-console */ +import { ApiError } from './ApiError'; import { JSONSchema } from './utils'; export type RequestParams = string | JSONSchema; export type RequestMethods = 'GET' | 'POST' | 'DELETE'; @@ -71,8 +72,7 @@ export class API { const result = await fetch( url, args ); if ( ! result.ok ) { - console.error( 'Failed to fetch', url, result ); - throw new Error( `Failed to "${ method }" to ${ url }. Received ${ result.status }` ); + throw new ApiError( url, result.status, result.statusText ); } let data; @@ -81,6 +81,7 @@ export class API { data = JSON.parse( text ); } catch ( e ) { console.error( 'Failed to parse the response\n', { url, text, result, error: e } ); + throw new ApiError( url, 'json_parse_error', 'Failed to parse the response' ); } /** @@ -91,8 +92,8 @@ export class API { * @see https://github.com/WordPress/wordpress-develop/blob/28f10e4af559c9b4dbbd1768feff0bae575d5e78/src/wp-includes/rest-api/class-wp-rest-request.php#L701 */ if ( ! data || data.JSON === undefined ) { - console.error( 'Failed to parse the response\n', { url, text, result } ); - throw new Error( `Failed to "${ method }" to ${ url }. Received ${ result.status }` ); + console.error( 'JSON response is empty.\n', { url, text, result } ); + throw new ApiError( url, 'json_empty', 'JSON response is empty' ); } return data.JSON; diff --git a/projects/js-packages/svelte-data-sync-client/src/ApiError.ts b/projects/js-packages/svelte-data-sync-client/src/ApiError.ts new file mode 100644 index 0000000000000..f96b807eaeba3 --- /dev/null +++ b/projects/js-packages/svelte-data-sync-client/src/ApiError.ts @@ -0,0 +1,19 @@ +export class ApiError extends Error { + public name = 'ApiError'; + constructor( + public location: string, + public status: number | 'failed_to_sync' | 'json_parse_error' | 'json_empty' | 'schema_error', + public message: string + ) { + super( message ); + + /** + * This makes `foo instanceof ApiError` work. + * To make instanceof work correctly + * set the prototype explicitly. + * + * @see https://stackoverflow.com/a/41102306/1015046 + */ + Object.setPrototypeOf( this, ApiError.prototype ); + } +} diff --git a/projects/js-packages/svelte-data-sync-client/src/SyncedStore.ts b/projects/js-packages/svelte-data-sync-client/src/SyncedStore.ts index 5184bfed8e2fa..0d33a4e174ead 100644 --- a/projects/js-packages/svelte-data-sync-client/src/SyncedStore.ts +++ b/projects/js-packages/svelte-data-sync-client/src/SyncedStore.ts @@ -1,19 +1,28 @@ import deepEqual from 'deep-equal'; -import { writable } from 'svelte/store'; +import { Writable, writable } from 'svelte/store'; +import { ApiError } from './ApiError'; +import { + Pending, + SyncedStoreInterface, + SyncedWritable, + SyncedStoreCallback, + SyncedStoreError, +} from './types'; import { sleep } from './utils'; -import type { Pending, SyncedStoreInterface, SyncedWritable, SyncedStoreCallback } from './types'; + /* * A custom Svelte Store that's used to indicate if a value is being synced. */ export class SyncedStore< T > { private store: SyncedWritable< T >; + private errorStore: Writable< SyncedStoreError< T >[] >; private pending: Pending; - private failedToSync = Symbol( 'failedToSync' ); private updateCallback?: SyncedStoreCallback< T >; private abortController: AbortController; constructor( initialValue?: T ) { this.store = this.createStore( initialValue ); + this.errorStore = writable< SyncedStoreError< T >[] >( [] ); this.pending = this.createPendingStore(); } @@ -57,34 +66,46 @@ export class SyncedStore< T > { }; } - public getPublicInterface(): SyncedStoreInterface< T > { - return { - store: this.store, - pending: { - subscribe: this.pending.subscribe, - }, - setCallback: this.setCallback.bind( this ), - }; - } - - public setCallback( callback: SyncedStoreCallback< T > ) { + /** + * A callback that will synchronize the store in some way. + * By default, this is set to endpoint.POST in the client initializer + */ + private setCallback( callback: SyncedStoreCallback< T > ) { this.updateCallback = callback; } - private async synchronize( value: T ): Promise< T | typeof this.failedToSync > { + /** + * Attempt to synchronize the store with the API. + */ + private async synchronize( value: T ): Promise< T | ApiError > { if ( ! this.updateCallback ) { return value; } - const result = await this.updateCallback( value, this.abortController.signal ); - // Success is only when the updateCallback result matches the value. - if ( this.equals( result, value ) ) { - return result ? result : value; + try { + const result = await this.updateCallback( value, this.abortController.signal ); + + // Success is only when the updateCallback result matches the value. + if ( this.equals( result, value ) ) { + return result ? result : value; + } + } catch ( error ) { + if ( error instanceof ApiError || error.name === 'ApiError' ) { + return error as ApiError; + } + + // Rethrow the error if it's not an ApiError. + throw error; } - return this.failedToSync; + return new ApiError( 'SyncedStore::synchronize', 'failed_to_sync', 'Failed to sync' ); } + /** + * A debounced version of synchronize. + * This is used to prevent the API from being spammed with requests. + * It also prevents the store from updating when the API returns an error. + */ private async abortableSynchronize( prevValue: T, value: T, retry = 0 ) { if ( this.abortController ) { this.abortController.abort(); @@ -102,7 +123,7 @@ export class SyncedStore< T > { return; } - if ( result === this.failedToSync ) { + if ( result instanceof ApiError ) { if ( retry < 3 ) { // Wait a second before retrying. await sleep( 1000 ); @@ -112,7 +133,17 @@ export class SyncedStore< T > { this.abortableSynchronize( prevValue, value, retry + 1 ); return; } - + this.errorStore.update( errors => { + errors.push( { + time: Date.now(), + previousValue: prevValue, + value, + location: result.location, + status: result.status, + message: result.message, + } ); + return errors; + } ); this.store.override( prevValue ); } @@ -135,4 +166,22 @@ export class SyncedStore< T > { return a === b; } + + /** + * All of the class methods in this class are private. + * Use this method to get the public interface of this class, + * exposing as little as possible. + */ + public getPublicInterface(): SyncedStoreInterface< T > { + return { + store: this.store, + pending: { + subscribe: this.pending.subscribe, + }, + errors: { + subscribe: this.errorStore.subscribe, + }, + setCallback: this.setCallback.bind( this ), + }; + } } diff --git a/projects/js-packages/svelte-data-sync-client/src/initializeClient.ts b/projects/js-packages/svelte-data-sync-client/src/initializeClient.ts index 9afa5f5252e70..734463946b117 100644 --- a/projects/js-packages/svelte-data-sync-client/src/initializeClient.ts +++ b/projects/js-packages/svelte-data-sync-client/src/initializeClient.ts @@ -1,3 +1,4 @@ +import { derived } from 'svelte/store'; import { z } from 'zod'; import { API } from './API'; import { API_Endpoint } from './Endpoint'; @@ -89,13 +90,23 @@ function setupRestApi( namespace: string ) { */ export function initializeClient( namespace: string ) { const api = setupRestApi( namespace ); + const errorStores = []; - function createAsyncStore< T extends z.ZodSchema >( valueName: string, schema: T ) { + type AsyncStoreOptions = { + // If this is set to true, the store won't be added to the global error store. + hideFromGlobalErrors?: boolean; + }; + + function createAsyncStore< T extends z.ZodSchema >( + valueName: string, + schema: T, + opts: AsyncStoreOptions = {} + ) { // Get the value from window and validate it with the schema. const { nonce, value } = getValidatedValue( namespace, valueName, schema ); // Setup the Svelte Store and the API Endpoint for this value - const store = new SyncedStore< z.infer< T > >( value ); + const syncedStore = new SyncedStore< z.infer< T > >( value ); const endpoint = new API_Endpoint< z.infer< T > >( api, valueName, schema ); /** @@ -114,19 +125,46 @@ export function initializeClient( namespace: string ) { * } ); */ endpoint.nonce = nonce; - store.setCallback( endpoint.POST ); // The client doesn't need the whole store object. // Only expose selected public methods: - const storeInterface = store.getPublicInterface(); + const store = syncedStore.getPublicInterface(); + + store.setCallback( endpoint.POST ); - return { + const client = { endpoint, - ...storeInterface, + ...store, }; + + if ( opts.hideFromGlobalErrors !== true ) { + // Keep track of all the error stores that don't opt out. + errorStores.push( client.errors ); + } + + return client; } + return { + /** + * Create a new Synced Store. + * @see createAsyncStore + */ createAsyncStore, + + /** + * The API Object to interact with the REST API directly. + * @see API + * + * Note that each client has `endpoint` property available. + * @see API_Endpoint + */ api, + /** + * Each client has its own error store. + * This takes all error stores and joins them into one. + * Make sure that you run `globalErrorStore.subscribe()` after all the stores are created. + */ + globalErrorStore: () => derived( errorStores, $errorStores => $errorStores.flat() ), }; } diff --git a/projects/js-packages/svelte-data-sync-client/src/types.ts b/projects/js-packages/svelte-data-sync-client/src/types.ts index a28e9ec4073ff..bae6859197271 100644 --- a/projects/js-packages/svelte-data-sync-client/src/types.ts +++ b/projects/js-packages/svelte-data-sync-client/src/types.ts @@ -14,6 +14,7 @@ export type SyncedStoreCallback< T > = ( value: T, abortSignal?: AbortSignal ) = export type SyncedStoreInterface< T > = { store: SyncedWritable< T >; pending: Readable< boolean >; + errors: Readable< SyncedStoreError< T >[] >; setCallback: ( callback: SyncedStoreCallback< T > ) => void; }; @@ -41,3 +42,12 @@ export interface Pending { export type SyncedWritable< T > = Writable< T > & { override: ( value: T ) => void; }; + +export type SyncedStoreError< T > = { + time: number; + status: number | string; + message: string; + location: string; + value: T; + previousValue: T; +}; diff --git a/projects/js-packages/webpack-config/CHANGELOG.md b/projects/js-packages/webpack-config/CHANGELOG.md index 61b811f3229c1..923f55582a677 100644 --- a/projects/js-packages/webpack-config/CHANGELOG.md +++ b/projects/js-packages/webpack-config/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 1.3.25 - 2023-03-08 +### Changed +- Updated package dependencies. [#29216] + ## 1.3.24 - 2023-02-15 ### Changed - Update to React 18. [#28710] diff --git a/projects/js-packages/webpack-config/changelog/renovate-wordpress-monorepo b/projects/js-packages/webpack-config/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/js-packages/webpack-config/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/js-packages/webpack-config/package.json b/projects/js-packages/webpack-config/package.json index f200a3cdbab05..8e4c98886f8be 100644 --- a/projects/js-packages/webpack-config/package.json +++ b/projects/js-packages/webpack-config/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-webpack-config", - "version": "1.3.25-alpha", + "version": "1.3.25", "description": "Library of pieces for webpack config in Jetpack projects.", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/webpack-config/#readme", "bugs": { diff --git a/projects/packages/action-bar/CHANGELOG.md b/projects/packages/action-bar/CHANGELOG.md index a037b6a6ae270..1516ef15c3b52 100644 --- a/projects/packages/action-bar/CHANGELOG.md +++ b/projects/packages/action-bar/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.13] - 2023-03-08 +### Changed +- Updated package dependencies. [#29216] + ## [0.1.12] - 2023-02-20 ### Changed - Minor internal updates. @@ -57,6 +61,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Adds the Action Bar package and Jetpack plugin module for follows, likes, and comments. Just a scaffold to build on, for now. [#25447] +[0.1.13]: https://github.com/Automattic/jetpack-action-bar/compare/v0.1.12...v0.1.13 [0.1.12]: https://github.com/Automattic/jetpack-action-bar/compare/v0.1.11...v0.1.12 [0.1.11]: https://github.com/Automattic/jetpack-action-bar/compare/v0.1.10...v0.1.11 [0.1.10]: https://github.com/Automattic/jetpack-action-bar/compare/v0.1.9...v0.1.10 diff --git a/projects/packages/action-bar/changelog/renovate-wordpress-monorepo b/projects/packages/action-bar/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/action-bar/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/action-bar/package.json b/projects/packages/action-bar/package.json index 19aea767bf9c4..feb6085eb46d4 100644 --- a/projects/packages/action-bar/package.json +++ b/projects/packages/action-bar/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-action-bar", - "version": "0.1.13-alpha", + "version": "0.1.13", "description": "An easy way for visitors to follow, like, and comment on your WordPress site.", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/action-bar/#readme", "bugs": { diff --git a/projects/packages/assets/CHANGELOG.md b/projects/packages/assets/CHANGELOG.md index d1108ebe9834c..5586a1f9cadaa 100644 --- a/projects/packages/assets/CHANGELOG.md +++ b/projects/packages/assets/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.17.33] - 2023-03-08 +### Changed +- Updated package dependencies. [#29216] + ## [1.17.32] - 2023-02-20 ### Changed - Minor internal updates. @@ -304,6 +308,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Statically access asset tools +[1.17.33]: https://github.com/Automattic/jetpack-assets/compare/v1.17.32...v1.17.33 [1.17.32]: https://github.com/Automattic/jetpack-assets/compare/v1.17.31...v1.17.32 [1.17.31]: https://github.com/Automattic/jetpack-assets/compare/v1.17.30...v1.17.31 [1.17.30]: https://github.com/Automattic/jetpack-assets/compare/v1.17.29...v1.17.30 diff --git a/projects/packages/assets/changelog/renovate-jest-monorepo b/projects/packages/assets/changelog/renovate-jest-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/assets/changelog/renovate-jest-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/assets/changelog/renovate-wordpress-monorepo b/projects/packages/assets/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/assets/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/backup/CHANGELOG.md b/projects/packages/backup/CHANGELOG.md index 140241e52367e..30569a73ff304 100644 --- a/projects/packages/backup/CHANGELOG.md +++ b/projects/packages/backup/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.12.6] - 2023-03-08 +### Changed +- Switch to use tracking check from connection package [#29187] +- Updated package dependencies. [#29216] + ## [1.12.5] - 2023-02-28 ### Changed - Update billing language [#29126] @@ -356,6 +361,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add API endpoints and Jetpack Backup package for managing Help… +[1.12.6]: https://github.com/Automattic/jetpack-backup/compare/v1.12.5...v1.12.6 [1.12.5]: https://github.com/Automattic/jetpack-backup/compare/v1.12.4...v1.12.5 [1.12.4]: https://github.com/Automattic/jetpack-backup/compare/v1.12.3...v1.12.4 [1.12.3]: https://github.com/Automattic/jetpack-backup/compare/v1.12.2...v1.12.3 diff --git a/projects/packages/backup/changelog/renovate-concurrently-7.x b/projects/packages/backup/changelog/renovate-concurrently-7.x deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/backup/changelog/renovate-concurrently-7.x +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/backup/changelog/renovate-jest-monorepo b/projects/packages/backup/changelog/renovate-jest-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/backup/changelog/renovate-jest-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/backup/changelog/renovate-wordpress-monorepo b/projects/packages/backup/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/backup/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/backup/changelog/update-backup-tracking-connection-package b/projects/packages/backup/changelog/update-backup-tracking-connection-package deleted file mode 100644 index 25427cd34ccac..0000000000000 --- a/projects/packages/backup/changelog/update-backup-tracking-connection-package +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Switch to use tracking check from connection package diff --git a/projects/packages/backup/src/class-package-version.php b/projects/packages/backup/src/class-package-version.php index c022957c7ced3..eb7872b9f948c 100644 --- a/projects/packages/backup/src/class-package-version.php +++ b/projects/packages/backup/src/class-package-version.php @@ -12,7 +12,7 @@ */ class Package_Version { - const PACKAGE_VERSION = '1.12.6-alpha'; + const PACKAGE_VERSION = '1.12.6'; const PACKAGE_SLUG = 'backup'; diff --git a/projects/packages/blaze/CHANGELOG.md b/projects/packages/blaze/CHANGELOG.md index 9e6940a10d059..adf4a183ed8ff 100644 --- a/projects/packages/blaze/CHANGELOG.md +++ b/projects/packages/blaze/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.5] - 2023-03-08 +### Changed +- Updated package dependencies. [#29216] + ## [0.5.4] - 2023-02-20 ### Changed - Minor internal updates. @@ -90,6 +94,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated package dependencies. [#27906] +[0.5.5]: https://github.com/automattic/jetpack-blaze/compare/v0.5.4...v0.5.5 [0.5.4]: https://github.com/automattic/jetpack-blaze/compare/v0.5.3...v0.5.4 [0.5.3]: https://github.com/automattic/jetpack-blaze/compare/v0.5.2...v0.5.3 [0.5.2]: https://github.com/automattic/jetpack-blaze/compare/v0.5.1...v0.5.2 diff --git a/projects/packages/blaze/changelog/renovate-wordpress-monorepo b/projects/packages/blaze/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/blaze/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/blaze/package.json b/projects/packages/blaze/package.json index 77cb0515c750b..034a860cf781e 100644 --- a/projects/packages/blaze/package.json +++ b/projects/packages/blaze/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-blaze", - "version": "0.5.5-alpha", + "version": "0.5.5", "description": "Attract high-quality traffic to your site using Blaze. Using this service, you can advertise a post or page on some of the millions of pages across WordPress.com and Tumblr from just $5 per day.", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/blaze/#readme", "bugs": { diff --git a/projects/packages/blaze/src/class-blaze.php b/projects/packages/blaze/src/class-blaze.php index d033f534c167d..2653bc1bf3f22 100644 --- a/projects/packages/blaze/src/class-blaze.php +++ b/projects/packages/blaze/src/class-blaze.php @@ -17,7 +17,7 @@ */ class Blaze { - const PACKAGE_VERSION = '0.5.5-alpha'; + const PACKAGE_VERSION = '0.5.5'; /** * Script handle for the JS file we enqueue in the post editor. diff --git a/projects/packages/connection/CHANGELOG.md b/projects/packages/connection/CHANGELOG.md index 7d46a65f00bbf..f8d313db92d4b 100644 --- a/projects/packages/connection/CHANGELOG.md +++ b/projects/packages/connection/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.51.1] - 2023-03-08 +### Changed +- Improve JS code in the connection owner removal notice. [#29087] +- Updated package dependencies. [#29216] + ## [1.51.0] - 2023-02-20 ### Changed - Moving deleting connection owner notice from JITM to Connection package. [#28516] @@ -765,6 +770,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Separate the connection library into its own package. +[1.51.1]: https://github.com/Automattic/jetpack-connection/compare/v1.51.0...v1.51.1 [1.51.0]: https://github.com/Automattic/jetpack-connection/compare/v1.50.1...v1.51.0 [1.50.1]: https://github.com/Automattic/jetpack-connection/compare/v1.50.0...v1.50.1 [1.50.0]: https://github.com/Automattic/jetpack-connection/compare/v1.49.1...v1.50.0 diff --git a/projects/packages/connection/changelog/renovate-wordpress-monorepo b/projects/packages/connection/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/connection/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/connection/changelog/update-remove-connection-owner-jquery b/projects/packages/connection/changelog/update-remove-connection-owner-jquery deleted file mode 100644 index e3a30481c1ae6..0000000000000 --- a/projects/packages/connection/changelog/update-remove-connection-owner-jquery +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Improve JS code in the connection owner removal notice. diff --git a/projects/packages/connection/src/class-package-version.php b/projects/packages/connection/src/class-package-version.php index 46b94ec20c5f4..2eb5554ea3948 100644 --- a/projects/packages/connection/src/class-package-version.php +++ b/projects/packages/connection/src/class-package-version.php @@ -12,7 +12,7 @@ */ class Package_Version { - const PACKAGE_VERSION = '1.51.1-alpha'; + const PACKAGE_VERSION = '1.51.1'; const PACKAGE_SLUG = 'connection'; diff --git a/projects/packages/forms/CHANGELOG.md b/projects/packages/forms/CHANGELOG.md index 6b44133891267..3c08947dcc25f 100644 --- a/projects/packages/forms/CHANGELOG.md +++ b/projects/packages/forms/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.7.0] - 2023-03-08 +### Added +- Add weekly/monthly props to sent message tracking [#28999] +- Add form responses app and state into package (out of plugin) [#29007] +- Fix search by invalidating resolution on the selector [#29259] +- Implement RWD navigation for feedback dashboard [#29315] + +### Changed +- Forms: Move field width settings and remove placeholder field from MC/SC fields [#29292] +- Updated package dependencies. [#29216] + +### Fixed +- Add defaults for Jetpack Forms CSS variables. [#29236] +- Fix table interactions for feedback dashboard [#29282] +- Forms Responses endpoint: fix permission check. [#29223] +- Move search into state, fix double fetch on search and paging [#29336] + ## [0.6.0] - 2023-02-28 ### Added - Added a page navigation component for the new feedback dashboard [#28826] @@ -60,6 +77,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added a new jetpack/forms package [#28409] - Added a public load_contact_form method for initializing the contact form module. [#28416] +[0.7.0]: https://github.com/automattic/jetpack-forms/compare/v0.6.0...v0.7.0 [0.6.0]: https://github.com/automattic/jetpack-forms/compare/v0.5.1...v0.6.0 [0.5.1]: https://github.com/automattic/jetpack-forms/compare/v0.5.0...v0.5.1 [0.5.0]: https://github.com/automattic/jetpack-forms/compare/v0.4.0...v0.5.0 diff --git a/projects/packages/forms/changelog/add-jetpack-forms-dashboard-view-switch b/projects/packages/forms/changelog/add-jetpack-forms-dashboard-view-switch new file mode 100644 index 0000000000000..28aa67012f4de --- /dev/null +++ b/projects/packages/forms/changelog/add-jetpack-forms-dashboard-view-switch @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Added a 'view' toggle for switching between the new and old feedback views. diff --git a/projects/packages/forms/changelog/add-response-inbox-store-endpoint b/projects/packages/forms/changelog/add-response-inbox-store-endpoint deleted file mode 100644 index c4dbbc5bda36b..0000000000000 --- a/projects/packages/forms/changelog/add-response-inbox-store-endpoint +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Add form responses app and state into package (out of plugin) diff --git a/projects/packages/forms/changelog/add-tracking_of_forms_csv_exports b/projects/packages/forms/changelog/add-tracking_of_forms_csv_exports new file mode 100644 index 0000000000000..089fdf359b45d --- /dev/null +++ b/projects/packages/forms/changelog/add-tracking_of_forms_csv_exports @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Added tracking of Jetpack Forms exports to CSV files. diff --git a/projects/packages/forms/changelog/add-week_month_props_on_sent_tracks b/projects/packages/forms/changelog/add-week_month_props_on_sent_tracks deleted file mode 100644 index 0038d88384d0f..0000000000000 --- a/projects/packages/forms/changelog/add-week_month_props_on_sent_tracks +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Added week/monthly props to sent message tracking diff --git a/projects/packages/forms/changelog/change-forms-actions-component b/projects/packages/forms/changelog/change-forms-actions-component new file mode 100644 index 0000000000000..128a1c4c6371f --- /dev/null +++ b/projects/packages/forms/changelog/change-forms-actions-component @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Move BulkActionsMenu component inside Inbox, too tailored to be reused diff --git a/projects/packages/forms/changelog/change-forms-responses-decouple-action-bar b/projects/packages/forms/changelog/change-forms-responses-decouple-action-bar new file mode 100644 index 0000000000000..00b49d0a50b9a --- /dev/null +++ b/projects/packages/forms/changelog/change-forms-responses-decouple-action-bar @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Move action bar components out of inbox diff --git a/projects/packages/forms/changelog/fix-contact-form-namespace-package b/projects/packages/forms/changelog/fix-contact-form-namespace-package new file mode 100644 index 0000000000000..ebee4f8ad1e95 --- /dev/null +++ b/projects/packages/forms/changelog/fix-contact-form-namespace-package @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Avoid Fatal errors by calling method from the right class in the paackage. diff --git a/projects/packages/forms/changelog/fix-contact-form-style-defaults b/projects/packages/forms/changelog/fix-contact-form-style-defaults deleted file mode 100644 index e6625adb1c6a0..0000000000000 --- a/projects/packages/forms/changelog/fix-contact-form-style-defaults +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Added defaults for Jetpack Forms CSS variables. diff --git a/projects/packages/forms/changelog/fix-forms-endpoint-permissions b/projects/packages/forms/changelog/fix-forms-endpoint-permissions deleted file mode 100644 index 18c9b7335aa2e..0000000000000 --- a/projects/packages/forms/changelog/fix-forms-endpoint-permissions +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Forms Responses endpoint: fix permission check. diff --git a/projects/packages/forms/changelog/fix-forms-inbox-empty-results b/projects/packages/forms/changelog/fix-forms-inbox-empty-results new file mode 100644 index 0000000000000..88da9f2afbc30 --- /dev/null +++ b/projects/packages/forms/changelog/fix-forms-inbox-empty-results @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Better handling for loading state and empty results diff --git a/projects/packages/forms/changelog/renovate-concurrently-7.x b/projects/packages/forms/changelog/renovate-concurrently-7.x deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/forms/changelog/renovate-concurrently-7.x +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/forms/changelog/renovate-wordpress-monorepo b/projects/packages/forms/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/forms/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/forms/changelog/update-form-fields-sidebar b/projects/packages/forms/changelog/update-form-fields-sidebar deleted file mode 100644 index 1f6d66bffbb3a..0000000000000 --- a/projects/packages/forms/changelog/update-form-fields-sidebar +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: changed - -Forms: Move field width settings and remove placeholder field from MC/SC fields diff --git a/projects/packages/forms/changelog/update-mutiple-single-choice-field b/projects/packages/forms/changelog/update-mutiple-single-choice-field new file mode 100644 index 0000000000000..089e0edcd0d84 --- /dev/null +++ b/projects/packages/forms/changelog/update-mutiple-single-choice-field @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Multiple Choice and Single Choice fields redesign diff --git a/projects/packages/forms/composer.json b/projects/packages/forms/composer.json index e70d8664c4152..777fae7a14a92 100644 --- a/projects/packages/forms/composer.json +++ b/projects/packages/forms/composer.json @@ -58,7 +58,7 @@ "link-template": "https://github.com/automattic/jetpack-forms/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.7.x-dev" + "dev-trunk": "0.8.x-dev" }, "textdomain": "jetpack-forms", "version-constants": { diff --git a/projects/packages/forms/package.json b/projects/packages/forms/package.json index aac898ec565f4..db2029b00fa2e 100644 --- a/projects/packages/forms/package.json +++ b/projects/packages/forms/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-forms", - "version": "0.7.0-alpha", + "version": "0.8.0-alpha", "description": "Jetpack Forms", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/forms/#readme", "bugs": { @@ -38,6 +38,7 @@ "@wordpress/element": "5.5.0", "@wordpress/hooks": "3.28.0", "@wordpress/i18n": "4.28.0", + "@wordpress/icons": "9.19.0", "classnames": "2.3.1", "email-validator": "2.0.4", "gridicons": "3.4.1", diff --git a/projects/packages/forms/src/blocks/contact-form/child-blocks.js b/projects/packages/forms/src/blocks/contact-form/child-blocks.js index 1be87000ea308..0e08bbb4abf59 100644 --- a/projects/packages/forms/src/blocks/contact-form/child-blocks.js +++ b/projects/packages/forms/src/blocks/contact-form/child-blocks.js @@ -1,12 +1,15 @@ +import { InnerBlocks } from '@wordpress/block-editor'; import { createBlock, getBlockType } from '@wordpress/blocks'; import { Path } from '@wordpress/components'; import { Fragment } from '@wordpress/element'; import { __, _x } from '@wordpress/i18n'; +import { filter, isEmpty, map, trim } from 'lodash'; import JetpackField from './components/jetpack-field'; import JetpackFieldCheckbox from './components/jetpack-field-checkbox'; import JetpackFieldConsent from './components/jetpack-field-consent'; import JetpackDropdown from './components/jetpack-field-dropdown'; import JetpackFieldMultiple from './components/jetpack-field-multiple'; +import { JetpackFieldOptionEdit } from './components/jetpack-field-option'; import JetpackFieldTextarea from './components/jetpack-field-textarea'; import { getIconColor } from './util/block-icons'; import { useFormWrapper } from './util/form'; @@ -32,7 +35,7 @@ const FieldDefaults = { }, options: { type: 'array', - default: [ '' ], + default: [], }, defaultValue: { type: 'string', @@ -161,6 +164,49 @@ const FieldDefaults = { example: {}, }; +const OptionFieldDefaults = { + category: 'contact-form', + edit: JetpackFieldOptionEdit, + attributes: { + label: { + type: 'string', + }, + fieldType: { + enum: [ 'checkbox', 'radio' ], + default: 'checkbox', + }, + }, + supports: { + reusable: false, + html: false, + }, +}; + +const multiFieldV1 = fieldType => ( { + attributes: { + ...FieldDefaults.attributes, + label: { + type: 'string', + default: fieldType === 'checkbox' ? 'Choose several options' : 'Choose one option', + }, + }, + migrate: attributes => { + const blockName = `jetpack/field-option-${ fieldType }`; + const nonEmptyOptions = filter( attributes.options, o => ! isEmpty( trim( o ) ) ); + const newInnerBlocks = map( nonEmptyOptions, option => + createBlock( blockName, { + label: option, + } ) + ); + + attributes.options = []; + + return [ attributes, newInnerBlocks ]; + }, + isEligible: attr => attr.options && filter( attr.options, o => ! isEmpty( trim( o ) ) ).length, + save: () => null, +} ); + const getFieldLabel = ( { attributes, name: blockName } ) => { return null === attributes.label ? getBlockType( blockName ).title : attributes.label; }; @@ -512,6 +558,36 @@ export const childBlocks = [ edit: EditConsent, }, }, + { + name: 'field-option-checkbox', + settings: { + ...OptionFieldDefaults, + parent: [ 'jetpack/field-checkbox-multiple' ], + title: __( 'Multiple Choice Option', 'jetpack-forms' ), + icon: renderMaterialIcon( + <> + + + ), + }, + }, + { + name: 'field-option-radio', + settings: { + ...OptionFieldDefaults, + parent: [ 'jetpack/field-radio' ], + title: __( 'Single Choice Option', 'jetpack-forms' ), + icon: renderMaterialIcon( + + ), + }, + }, { name: 'field-checkbox-multiple', settings: { @@ -529,6 +605,7 @@ export const childBlocks = [ /> ), edit: editMultiField( 'checkbox' ), + save: () => , attributes: { ...FieldDefaults.attributes, label: { @@ -536,6 +613,7 @@ export const childBlocks = [ default: 'Choose several options', }, }, + deprecated: [ multiFieldV1( 'checkbox' ) ], }, }, { @@ -561,6 +639,7 @@ export const childBlocks = [ ), edit: editMultiField( 'radio' ), + save: () => , attributes: { ...FieldDefaults.attributes, label: { @@ -568,6 +647,7 @@ export const childBlocks = [ default: 'Choose one option', }, }, + deprecated: [ multiFieldV1( 'radio' ) ], }, }, { @@ -597,6 +677,10 @@ export const childBlocks = [ type: 'string', default: null, }, + options: { + type: 'array', + default: [ '' ], + }, }, }, }, diff --git a/projects/packages/forms/src/blocks/contact-form/components/jetpack-field-multiple.js b/projects/packages/forms/src/blocks/contact-form/components/jetpack-field-multiple.js index d4ca7a8aaafb2..7ee3150cfc151 100644 --- a/projects/packages/forms/src/blocks/contact-form/components/jetpack-field-multiple.js +++ b/projects/packages/forms/src/blocks/contact-form/components/jetpack-field-multiple.js @@ -1,15 +1,15 @@ -import { Button } from '@wordpress/components'; +import { InnerBlocks } from '@wordpress/block-editor'; import { compose, withInstanceId } from '@wordpress/compose'; -import { useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; import classnames from 'classnames'; import { useFormStyle } from '../util/form'; import { withSharedFieldAttributes } from '../util/with-shared-field-attributes'; import JetpackFieldControls from './jetpack-field-controls'; import JetpackFieldLabel from './jetpack-field-label'; -import JetpackOption from './jetpack-option'; import { useJetpackFieldStyles } from './use-jetpack-field-styles'; +const ALLOWED_BLOCKS = [ 'jetpack/field-option' ]; + function JetpackFieldMultiple( props ) { const { clientId, @@ -27,52 +27,19 @@ function JetpackFieldMultiple( props ) { } = props; const formStyle = useFormStyle( clientId ); + const innerBlocks = useSelect( + select => { + return select( 'core/block-editor' ).getBlock( clientId ).innerBlocks; + }, + [ clientId ] + ); + const classes = classnames( 'jetpack-field jetpack-field-multiple', { 'is-selected': isSelected, - 'has-placeholder': options.length, + 'has-placeholder': ( options && options.length ) || innerBlocks.length, } ); - const [ inFocus, setInFocus ] = useState( null ); - - const onChangeOption = ( key = null, option = null ) => { - const newOptions = options.slice( 0 ); - - if ( null === option ) { - // Remove a key - newOptions.splice( key, 1 ); - if ( key > 0 ) { - setInFocus( key - 1 ); - } - } else { - // update a key - newOptions.splice( key, 1, option ); - setInFocus( key ); // set the focus. - } - setAttributes( { options: newOptions } ); - }; - - const addNewOption = ( key = null ) => { - const newOptions = options.slice( 0 ); - let newInFocus = 0; - - if ( 'object' === typeof key ) { - newOptions.push( '' ); - newInFocus = newOptions.length - 1; - } else { - newOptions.splice( key + 1, 0, '' ); - newInFocus = key + 1; - } - - setInFocus( newInFocus ); - setAttributes( { options: newOptions } ); - }; - - const { blockStyle, fieldStyle } = useJetpackFieldStyles( attributes ); - const optionStyle = { - color: fieldStyle.color, - fontSize: fieldStyle.fontSize, - lineHeight: fieldStyle.lineHeight, - }; + const { blockStyle } = useJetpackFieldStyles( attributes ); return ( <> @@ -87,40 +54,16 @@ function JetpackFieldMultiple( props ) { label={ label } setAttributes={ setAttributes } isSelected={ isSelected } - resetFocus={ () => setInFocus( null ) } attributes={ attributes } style={ formStyle } /> -
    - { options.map( ( option, index ) => ( - - ) ) } - { isSelected && ( -
  1. - -
  2. - ) } -
+
+ +
{ + const { attributes, clientId, name, onReplace, setAttributes } = props; + const { removeBlock } = useDispatch( 'core/block-editor' ); + const parentAttributes = useParentAttributes( clientId ); + const { optionStyle } = useJetpackFieldStyles( parentAttributes ); + + const siblingsCount = useSelect( + select => { + const blockEditor = select( 'core/block-editor' ); + const parentBlockId = first( blockEditor.getBlockParents( clientId, true ) ); + return blockEditor.getBlock( parentBlockId ).innerBlocks.length; + }, + [ clientId ] + ); + + const type = name.replace( 'jetpack/field-option-', '' ); + + const handleSplit = label => + createBlock( name, { + ...attributes, + clientId: label && attributes.label.indexOf( label ) === 0 ? attributes.clientId : undefined, + label, + } ); + + const handleDelete = () => { + if ( siblingsCount <= 1 ) { + return; + } + + removeBlock( clientId ); + }; + + return ( +
+ + { + setAttributes( { label: value } ); + } } + onRemove={ handleDelete } + onSplit={ handleSplit } + onReplace={ onReplace } + placeholder={ __( 'Add option…', 'jetpack-forms' ) } + preserveWhiteSpace={ false } + withoutInteractiveFormatting + value={ attributes.label } + style={ optionStyle } + /> +
+ ); +}; diff --git a/projects/packages/forms/src/blocks/contact-form/components/use-jetpack-field-styles.js b/projects/packages/forms/src/blocks/contact-form/components/use-jetpack-field-styles.js index 96409b578d96a..432614c49831d 100644 --- a/projects/packages/forms/src/blocks/contact-form/components/use-jetpack-field-styles.js +++ b/projects/packages/forms/src/blocks/contact-form/components/use-jetpack-field-styles.js @@ -31,9 +31,16 @@ export const useJetpackFieldStyles = attributes => { lineHeight: attributes.lineHeight, }; + const optionStyle = { + color: fieldStyle.color, + fontSize: fieldStyle.fontSize, + lineHeight: fieldStyle.lineHeight, + }; + return { blockStyle, fieldStyle, labelStyle, + optionStyle, }; }; diff --git a/projects/packages/forms/src/blocks/contact-form/edit.js b/projects/packages/forms/src/blocks/contact-form/edit.js index 54369f8330224..33617cc6fc697 100644 --- a/projects/packages/forms/src/blocks/contact-form/edit.js +++ b/projects/packages/forms/src/blocks/contact-form/edit.js @@ -21,7 +21,7 @@ import { withDispatch, withSelect } from '@wordpress/data'; import { Fragment, useEffect, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import classnames from 'classnames'; -import { filter, get, map } from 'lodash'; +import { filter, get, isArray, map } from 'lodash'; import { childBlocks } from './child-blocks'; import InspectorHint from './components/inspector-hint'; import CRMIntegrationSettings from './components/jetpack-crm-integration/jetpack-crm-integration-settings'; @@ -33,8 +33,16 @@ import SalesforceLeadFormSettings, { } from './components/jetpack-salesforce-lead-form/jetpack-salesforce-lead-form-settings'; import defaultVariations from './variations'; +const validFields = filter( childBlocks, ( { settings } ) => { + return ( + ! settings.parent || + settings.parent === 'jetpack/contact-form' || + ( isArray( settings.parent ) && settings.parent.includes( 'jetpack/contact-form' ) ) + ); +} ); + const ALLOWED_BLOCKS = [ - ...map( childBlocks, block => `jetpack/${ block.name }` ), + ...map( validFields, block => `jetpack/${ block.name }` ), 'core/audio', 'core/columns', 'core/group', diff --git a/projects/packages/forms/src/blocks/contact-form/editor.scss b/projects/packages/forms/src/blocks/contact-form/editor.scss index 345e68c70901b..c57745b251848 100644 --- a/projects/packages/forms/src/blocks/contact-form/editor.scss +++ b/projects/packages/forms/src/blocks/contact-form/editor.scss @@ -321,7 +321,8 @@ height: 1rem; border-color: currentColor; opacity: 1; - margin: 0 0.75rem 0 0 + margin: 0 0.75rem 0 0; + pointer-events: none; } &.jetpack-field-multiple { @@ -351,6 +352,20 @@ .jetpack-field-consent__checkbox + .jetpack-field-label { line-height: normal; } + + .jetpack-field-option { + display: flex; + align-items: center; + justify-content: flex-start; + + > input { + margin: 0 5px 0 0; + } + + > .rich-text { + line-height: 2; + } + } } :where(:not(.contact-form)) > .jetpack-field { @@ -733,6 +748,10 @@ } } + &.jetpack-field-checkbox { + margin-top: 0; + } + .jetpack-field-dropdown { &__popover .rich-text { padding-left: calc(var(--notch-width) + 4px); diff --git a/projects/packages/forms/src/blocks/contact-form/util/use-parent-attributes.js b/projects/packages/forms/src/blocks/contact-form/util/use-parent-attributes.js new file mode 100644 index 0000000000000..b841c37968aa8 --- /dev/null +++ b/projects/packages/forms/src/blocks/contact-form/util/use-parent-attributes.js @@ -0,0 +1,9 @@ +import { useSelect } from '@wordpress/data'; +import { first } from 'lodash'; + +export const useParentAttributes = clientId => + useSelect( select => { + const blockEditor = select( 'core/block-editor' ); + + return blockEditor.getBlockAttributes( first( blockEditor.getBlockParents( clientId, true ) ) ); + } ); diff --git a/projects/packages/forms/src/class-jetpack-forms.php b/projects/packages/forms/src/class-jetpack-forms.php index 6814ba4736ffb..04e1042483752 100644 --- a/projects/packages/forms/src/class-jetpack-forms.php +++ b/projects/packages/forms/src/class-jetpack-forms.php @@ -9,12 +9,13 @@ use Automattic\Jetpack\Forms\ContactForm\Util; use Automattic\Jetpack\Forms\Dashboard\Dashboard; +use Automattic\Jetpack\Forms\Dashboard\Dashboard_View_Switch; /** * Understands the Jetpack Forms package. */ class Jetpack_Forms { - const PACKAGE_VERSION = '0.7.0-alpha'; + const PACKAGE_VERSION = '0.8.0-alpha'; /** * Load the contact form module. @@ -23,7 +24,9 @@ public static function load_contact_form() { Util::init(); if ( is_admin() && self::is_feedback_dashboard_enabled() ) { - $dashboard = new Dashboard(); + $view_switch = new Dashboard_View_Switch(); + + $dashboard = new Dashboard( $view_switch ); $dashboard->init(); } diff --git a/projects/packages/forms/src/contact-form/class-contact-form-plugin.php b/projects/packages/forms/src/contact-form/class-contact-form-plugin.php index 8df3f6c03b010..0c2e951669a24 100644 --- a/projects/packages/forms/src/contact-form/class-contact-form-plugin.php +++ b/projects/packages/forms/src/contact-form/class-contact-form-plugin.php @@ -367,12 +367,24 @@ private static function register_contact_form_blocks() { 'render_callback' => array( __CLASS__, 'gutenblock_render_field_checkbox_multiple' ), ) ); + Blocks::jetpack_register_block( + 'jetpack/field-option-checkbox', + array( + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_option' ), + ) + ); Blocks::jetpack_register_block( 'jetpack/field-radio', array( 'render_callback' => array( __CLASS__, 'gutenblock_render_field_radio' ), ) ); + Blocks::jetpack_register_block( + 'jetpack/field-option-radio', + array( + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_option' ), + ) + ); Blocks::jetpack_register_block( 'jetpack/field-select', array( @@ -550,6 +562,19 @@ public static function gutenblock_render_field_checkbox_multiple( $atts, $conten return Contact_Form::parse_contact_field( $atts, $content ); } + /** + * Render the multiple choice field option. + * + * @param array $atts - the block attributes. + * @param string $content - html content. + * + * @return string HTML for the contact form field. + */ + public static function gutenblock_render_field_option( $atts, $content ) { + $atts = self::block_attributes_to_shortcode_attributes( $atts, 'field-option' ); + return Contact_Form::parse_contact_field( $atts, $content ); + } + /** * Render the radio button field. * @@ -770,7 +795,7 @@ public function process_form_submission() { // end of copy-pasta from wp-includes/template-loader.php. // Ensure 'block_template' attribute is added to any shortcodes in the template. - $template = grunion_contact_form_set_block_template_attribute( $template ); + $template = Util::grunion_contact_form_set_block_template_attribute( $template ); // Process the block template to populate Grunion_Contact_Form::$last get_the_block_template_html(); @@ -891,6 +916,10 @@ public function insert_feedback_filter( $data, $postarr ) { public function add_shortcode() { add_shortcode( 'contact-form', array( '\Automattic\Jetpack\Forms\ContactForm\Contact_Form', 'parse' ) ); add_shortcode( 'contact-field', array( '\Automattic\Jetpack\Forms\ContactForm\Contact_Form', 'parse_contact_field' ) ); + + // We need 'contact-field-option' to be registered, so it's included to the get_shortcode_regex() method + // But we don't need a callback because we're handling contact-field-option manually + add_shortcode( 'contact-field-option', '__return_null' ); } /** @@ -1863,9 +1892,58 @@ public function download_feedback_as_csv() { } fclose( $output ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose + + $this->record_tracks_event( 'forms_export_responses', array( 'format' => 'csv' ) ); exit(); } + /** + * Send an event to Tracks + * + * @param string $event_name - the name of the event. + * @param array $event_props - event properties to send. + * + * @return null|void + */ + private function record_tracks_event( $event_name, $event_props ) { + /* + * Event details. + */ + $event_user = wp_get_current_user(); + + /* + * Record event. + * We use different libs on wpcom and Jetpack. + */ + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $event_name = 'wpcom_' . $event_name; + $event_props['blog_id'] = get_current_blog_id(); + // logged out visitor, record event with blog owner. + if ( empty( $event_user->ID ) ) { + $event_user_id = wpcom_get_blog_owner( $event_props['blog_id'] ); + $event_user = get_userdata( $event_user_id ); + } + + require_lib( 'tracks/client' ); + tracks_record_event( $event_user, $event_name, $event_props ); + } else { + $user_connected = ( new \Automattic\Jetpack\Connection\Manager( 'jetpack-forms' ) )->is_user_connected( get_current_user_id() ); + if ( ! $user_connected ) { + return; + } + // logged out visitor, record event with Jetpack master user. + if ( empty( $event_user->ID ) ) { + $master_user_id = Jetpack_Options::get_option( 'master_user' ); + if ( ! empty( $master_user_id ) ) { + $event_user = get_userdata( $master_user_id ); + } + } + + $tracking = new \Automattic\Jetpack\Tracking(); + $tracking->record_user_event( $event_name, $event_props, $event_user ); + } + } + /** * Escape a string to be used in a CSV context * diff --git a/projects/packages/forms/src/contact-form/class-contact-form.php b/projects/packages/forms/src/contact-form/class-contact-form.php index 5fbcb4f545210..e855a6cb5f6a0 100644 --- a/projects/packages/forms/src/contact-form/class-contact-form.php +++ b/projects/packages/forms/src/contact-form/class-contact-form.php @@ -733,11 +733,27 @@ private static function esc_shortcode_val( $val ) { public static function parse_contact_field( $attributes, $content ) { // Don't try to parse contact form fields if not inside a contact form if ( ! Contact_Form_Plugin::$using_contact_form_field ) { - $att_strs = array(); + $type = isset( $attributes['type'] ) ? $attributes['type'] : null; + + if ( $type === 'checkbox-multiple' || $type === 'radio' ) { + preg_match_all( '/' . get_shortcode_regex() . '/s', $content, $matches ); + + if ( ! empty( $matches[0] ) ) { + $options = array(); + foreach ( $matches[0] as $shortcode ) { + $attr = shortcode_parse_atts( $shortcode ); + $options[] = $attr['label']; + } + + $attributes['options'] = $options; + } + } + if ( ! isset( $attributes['label'] ) ) { - $type = isset( $attributes['type'] ) ? $attributes['type'] : null; $attributes['label'] = self::get_default_label_from_type( $type ); } + + $att_strs = array(); foreach ( $attributes as $att => $val ) { if ( is_numeric( $att ) ) { // Is a valueless attribute $att_strs[] = self::esc_shortcode_val( $val ); @@ -756,7 +772,12 @@ public static function parse_contact_field( $attributes, $content ) { } } - $html = '[contact-field ' . implode( ' ', $att_strs ); + $shortcode_type = 'contact-field'; + if ( $type === 'field-option' ) { + $shortcode_type = 'contact-field-option'; + } + + $html = '[' . $shortcode_type . ' ' . implode( ' ', $att_strs ); if ( isset( $content ) && ! empty( $content ) ) { // If there is content, let's add a closing tag $html .= ']' . esc_html( $content ) . '[/contact-field]'; diff --git a/projects/packages/forms/src/contact-form/css/grunion.css b/projects/packages/forms/src/contact-form/css/grunion.css index 4d710748a4d6d..22adcf4d3d406 100644 --- a/projects/packages/forms/src/contact-form/css/grunion.css +++ b/projects/packages/forms/src/contact-form/css/grunion.css @@ -99,14 +99,14 @@ font-weight: normal; display: inline-flex; align-items: center; - line-height: 1.5; + line-height: 1; } .contact-form .grunion-checkbox-multiple-options, .contact-form .grunion-radio-options { display: flex; flex-direction: column; - gap: 0.25em + gap: 12px; } .contact-form label span { diff --git a/projects/packages/forms/src/dashboard/class-dashboard-view-switch.php b/projects/packages/forms/src/dashboard/class-dashboard-view-switch.php new file mode 100644 index 0000000000000..5362f671c698d --- /dev/null +++ b/projects/packages/forms/src/dashboard/class-dashboard-view-switch.php @@ -0,0 +1,299 @@ +is_visible() ) { + return; + } + + ?> + + + is_visible() ) { + return; + } + + wp_register_style( + 'jetpack-forms-dashboard-switch', + false, + array(), + JETPACK__VERSION + ); + wp_enqueue_style( 'jetpack-forms-dashboard-switch' ); + + wp_add_inline_style( + 'jetpack-forms-dashboard-switch', + << #jetpack-forms__view-link-wrap { + position: fixed; + right: 32px; + top: var(--wp-admin--admin-bar--height); + z-index: 179; + } + + .toplevel_page_jetpack-forms #jetpack-forms__view-link { + background-color: #fff; + border: 1px solid #c3c4c7; + border-top: none; + border-radius: 0 0 4px 4px; + color: #646970; + cursor: pointer; + font-size: 13px; + line-height: 1.7; + padding: 3px 6px 3px 16px; + } + + .toplevel_page_jetpack-forms #jetpack-forms__view-link::after { + right: 0; + content: "\\f140"; + font: normal 20px/1 dashicons; + speak: never; + display: inline-flex; + padding: 0 5px 0 0; + bottom: 2px; + position: relative; + vertical-align: bottom; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-decoration: none; + } + + .screen-options-tab__wrapper { + position:relative + } + + .screen-options-tab__dropdown { + background-color: #fff; + border: 1px solid var(--color-neutral-5); + border-radius: 4px; + box-shadow: 0 4px 10px #0000001a; + padding: 3px; + position: absolute; + right: 20px; + top: 37px; + width:215px; + z-index: 9999; + } + + @media screen and (max-width: 782px) { + .screen-options-tab__dropdown { + right: 10px; + top: 47px; + } + } + + @media screen and (max-width: 600px) { + .screen-options-tab__dropdown { + top: 93px; + } + } + + .jp-forms__view-switcher:not(:hover) .jp-forms__view-switcher-button:nth-child(2) > strong { + color:var(--wp-admin-theme-color) + } + + .jp-forms__view-switcher-button, a.jp-forms__view-switcher-button { + background: transparent; + border: 1px solid #0000; + border-radius: 4px; + color: var(--color-text); + cursor: pointer; + display: inline-block; + font-size: .75rem; + line-height: normal; + text-decoration: none; + padding: 8px; + text-align:left + } + + a.jp-forms__view-switcher-button.is-active { + border-color: var(--wp-admin-theme-color); + margin-bottom:4px + } + + .jp-forms__view-switcher-button:last-child, a.jp-forms__view-switcher-button:last-child { + margin-bottom:0 + } + + .jp-forms__view-switcher-button strong, a.jp-forms__view-switcher-button strong { + display: block; + font-size: 13px; + margin-bottom:4px + } + + .jp-forms__view-switcher-button:focus > strong, .jp-forms__view-switcher-button:hover > strong, a.jp-forms__view-switcher-button:focus > strong, a.jp-forms__view-switcher-button:hover > strong { + color:var(--wp-admin-theme-color) + } +CSS + ); + } + + /** + * Add scripts for the switch component. + */ + public function add_scripts() { + if ( ! $this->is_visible() ) { + return; + } + + wp_add_inline_script( + 'common', + "(function( $ ) { + $( '#jetpack-forms__view-link-wrap' ).appendTo( '#screen-meta-links' ); + + var viewLink = $( '#jetpack-forms__view-link' ); + var viewWrap = $( '#jetpack-forms__view-wrap' ); + + viewLink.on( 'click', function() { + viewWrap.toggle(); + viewLink.toggleClass( 'screen-meta-active' ); + } ); + + $( document ).on( 'mouseup', function( event ) { + if ( ! viewLink.is( event.target ) && ! viewWrap.is( event.target ) && viewWrap.has( event.target ).length === 0 ) { + viewWrap.hide(); + viewLink.removeClass( 'screen-meta-active' ); + } + }); + })( jQuery );" + ); + } + + /** + * Updates the prefeerred view setting for the user if a GET param is present. + */ + public function handle_preferred_view() { + // For simplicity, we only treat this as a valid operation + // if it occurs on one of the screens with the switch active. + // phpcs:disable WordPress.Security.NonceVerification + if ( ! $this->is_visible() || ! isset( $_GET['preferred-view'] ) ) { + return; + } + + // phpcs:disable WordPress.Security.NonceVerification + $view = sanitize_key( $_GET['preferred-view'] ); + + if ( ! in_array( $view, array( self::CLASSIC_VIEW, self::MODERN_VIEW ), true ) ) { + return; + } + + update_user_option( get_current_user_id(), 'jetpack_forms_admin_preferred_view', $view ); + wp_safe_redirect( remove_query_arg( 'preferred-view' ) ); + exit; + } + + /** + * Returns the preferred feedback view for the current user. + * + * @return string + */ + public function get_preferred_view() { + $preferred_view = get_user_option( 'jetpack_forms_admin_preferred_view' ); + + return in_array( $preferred_view, array( self::CLASSIC_VIEW, self::MODERN_VIEW ), true ) ? $preferred_view : self::MODERN_VIEW; + } + + /** + * Returns true if the switch should be visible on the current page. + * + * @return boolean + */ + public function is_visible() { + return Jetpack_Forms::is_feedback_dashboard_enabled() && ( + $this->is_classic_view() || + $this->is_modern_view() + ); + } + + /** + * Returns true if the given screen features the classic view. + * + * @return boolean + */ + public function is_classic_view() { + $screen = get_current_screen(); + + return $screen && $screen->id === 'edit-feedback'; + } + + /** + * Returns true if the given screen features the modern view. + * + * @return boolean + */ + public function is_modern_view() { + $screen = get_current_screen(); + + // When classic view is set as preferred, jetpack-forms is registered under an empty parrent so it doesn't appear in the menu. + // Because of this, we need to support these two screens. + return $screen && in_array( $screen->id, array( 'admin_page_jetpack-forms', 'toplevel_page_jetpack-forms' ), true ); + } +} diff --git a/projects/packages/forms/src/dashboard/class-dashboard.php b/projects/packages/forms/src/dashboard/class-dashboard.php index a77c0faf360af..e53395f510546 100644 --- a/projects/packages/forms/src/dashboard/class-dashboard.php +++ b/projects/packages/forms/src/dashboard/class-dashboard.php @@ -22,12 +22,30 @@ class Dashboard { */ const MENU_PRIORITY = 999; + /** + * Dashboard_View_Switch instance + * + * @var Dashboard_View_Switch + */ + private $switch; + + /** + * Creates a new Dashboard instance. + * + * @param Dashboard_View_Switch $switch Dashboard_View_Switch instance to use. + */ + public function __construct( Dashboard_View_Switch $switch ) { + $this->switch = $switch; + } + /** * Initialize the dashboard. */ public function init() { add_action( 'admin_menu', array( $this, 'add_admin_submenu' ), self::MENU_PRIORITY ); add_action( 'admin_enqueue_scripts', array( $this, 'load_admin_scripts' ) ); + + $this->switch->init(); } /** @@ -67,6 +85,20 @@ public function load_admin_scripts( $hook ) { * Register the dashboard admin submenu. */ public function add_admin_submenu() { + if ( $this->switch->get_preferred_view() === Dashboard_View_Switch::CLASSIC_VIEW ) { + // We still need to register the jetpack forms page so it can be accessed manually. + add_submenu_page( + '', + __( 'Form Responses', 'jetpack-forms' ), + _x( 'Feedback', 'post type name shown in menu', 'jetpack-forms' ), + 'read', + 'jetpack-forms', + array( $this, 'render_dashboard' ) + ); + + return; + } + remove_menu_page( 'feedback' ); add_menu_page( diff --git a/projects/packages/forms/src/dashboard/components/layout/index.js b/projects/packages/forms/src/dashboard/components/layout/index.js index 53a24ffe266f7..d451053169cf1 100644 --- a/projects/packages/forms/src/dashboard/components/layout/index.js +++ b/projects/packages/forms/src/dashboard/components/layout/index.js @@ -1,12 +1,15 @@ import { JetpackFooter } from '@automattic/jetpack-components'; import { __ } from '@wordpress/i18n'; +import classnames from 'classnames'; import JetpackFormsLogo from '../logo'; import './style.scss'; -const Layout = ( { children, title, subtitle } ) => { +const Layout = ( { children, className, title, subtitle } ) => { + const classes = classnames( 'jp-forms__layout', className ); + return ( -
+
diff --git a/projects/packages/forms/src/dashboard/components/layout/style.scss b/projects/packages/forms/src/dashboard/components/layout/style.scss index ce0d36fb1204f..c3a0403741490 100644 --- a/projects/packages/forms/src/dashboard/components/layout/style.scss +++ b/projects/packages/forms/src/dashboard/components/layout/style.scss @@ -12,6 +12,7 @@ body.toplevel_page_jetpack-forms { flex-direction: column; margin: 0; padding: 0; + width: 100%; .jp-forms__logo { margin: 40px 64px; diff --git a/projects/packages/forms/src/dashboard/components/search-form/index.js b/projects/packages/forms/src/dashboard/components/search-form/index.js new file mode 100644 index 0000000000000..a2f801f72e610 --- /dev/null +++ b/projects/packages/forms/src/dashboard/components/search-form/index.js @@ -0,0 +1,29 @@ +import { + __experimentalInputControl as InputControl, // eslint-disable-line wpcalypso/no-unsafe-wp-apis +} from '@wordpress/components'; +import { useCallback, useState } from '@wordpress/element'; +import './style.scss'; + +const SearchInput = ( { onSearch, initialValue } ) => { + const [ searchText, setSearchText ] = useState( initialValue ); + const handleSearch = useCallback( + event => { + event.preventDefault(); + onSearch( searchText ); + }, + [ searchText, onSearch ] + ); + return ( +
+ + + ); +}; + +export default SearchInput; diff --git a/projects/packages/forms/src/dashboard/components/search-form/style.scss b/projects/packages/forms/src/dashboard/components/search-form/style.scss new file mode 100644 index 0000000000000..1faa7dac92ab0 --- /dev/null +++ b/projects/packages/forms/src/dashboard/components/search-form/style.scss @@ -0,0 +1,6 @@ +.jp-forms__search-form { + input.components-input-control__input { + // hateful important: couldn't override input's style + height: 36px !important; + } +} diff --git a/projects/packages/forms/src/dashboard/components/table/index.js b/projects/packages/forms/src/dashboard/components/table/index.js index 382329082405e..606a18cdcec89 100644 --- a/projects/packages/forms/src/dashboard/components/table/index.js +++ b/projects/packages/forms/src/dashboard/components/table/index.js @@ -1,6 +1,6 @@ import { useCallback, useState } from '@wordpress/element'; import classnames from 'classnames'; -import { difference, includes, kebabCase, last, map, without } from 'lodash'; +import { difference, includes, kebabCase, map, without } from 'lodash'; import TableItem from './item'; import './style.scss'; @@ -13,7 +13,7 @@ const Table = ( { className, columns, defaultSelected, items, onSelectionChange const newState = includes( selected, id ) ? without( selected, id ) : [ ...selected, id ]; setSelected( newState ); - onSelectionChange( last( newState ) ); + onSelectionChange( newState ); }, [ selected, onSelectionChange ] ); diff --git a/projects/packages/forms/src/dashboard/components/table/item.js b/projects/packages/forms/src/dashboard/components/table/item.js index 4106e8fcd5c15..b30e80d235103 100644 --- a/projects/packages/forms/src/dashboard/components/table/item.js +++ b/projects/packages/forms/src/dashboard/components/table/item.js @@ -2,20 +2,26 @@ import { Fragment, useCallback } from '@wordpress/element'; import classnames from 'classnames'; import { kebabCase, map } from 'lodash'; +const stopPropagation = event => event.stopPropagation(); + const TableItem = ( { columns, item, isSelected, onSelectChange } ) => { const handleChange = useCallback( () => onSelectChange( item.id ), [ item.id, onSelectChange ] ); const classes = classnames( 'jp-forms__table-item', { + 'is-active': item.isActive, + 'is-clickable': item.onClick, 'is-selected': isSelected, } ); return ( -
+ /* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */ +
{ !! onSelectChange && (
@@ -24,7 +30,7 @@ const TableItem = ( { columns, item, isSelected, onSelectChange } ) => { { map( columns, ( { additionalClassNames, component, getProps, key } ) => { let Wrapper = Fragment; - let props = []; + let props = {}; if ( component ) { Wrapper = component; diff --git a/projects/packages/forms/src/dashboard/components/table/style.scss b/projects/packages/forms/src/dashboard/components/table/style.scss index 87ea0548c4ee5..97dba98caf725 100644 --- a/projects/packages/forms/src/dashboard/components/table/style.scss +++ b/projects/packages/forms/src/dashboard/components/table/style.scss @@ -14,6 +14,15 @@ } } +.jp-forms__table-item.is-clickable { + cursor: pointer; +} + +.jp-forms__table-item.is-active { + background-color: #000; + color: #fff; +} + .jp-forms__table-cell { box-sizing: border-box; display: table-cell; @@ -25,7 +34,6 @@ text-align: left; transition: background-color .1s ease-out, opacity .1s ease-out; vertical-align: middle; - white-space: nowrap; // Max-width is required on all columns for text-overflow: ellipsis; to work on any. // Hence setting this here to a conservatively high number. diff --git a/projects/packages/forms/src/dashboard/inbox/bulk-actions-menu.js b/projects/packages/forms/src/dashboard/inbox/bulk-actions-menu.js new file mode 100644 index 0000000000000..6916cbfecf662 --- /dev/null +++ b/projects/packages/forms/src/dashboard/inbox/bulk-actions-menu.js @@ -0,0 +1,35 @@ +import { AntiSpamIcon } from '@automattic/jetpack-components'; +import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { chevronDown, trash, archive, inbox } from '@wordpress/icons'; + +const ActionsMenu = () => { + return ( + + { ( { onClose } ) => ( + + } iconPosition="left"> + { __( 'Spam check', 'jetpack-forms' ) } + + + { __( 'Mark unread', 'jetpack-forms' ) } + + + { __( 'Archive', 'jetpack-forms' ) } + + + { __( 'Delete', 'jetpack-forms' ) } + + + ) } + + ); +}; + +export default ActionsMenu; diff --git a/projects/packages/forms/src/dashboard/inbox/index.js b/projects/packages/forms/src/dashboard/inbox/index.js index eaaea11926c1e..0862beef25393 100644 --- a/projects/packages/forms/src/dashboard/inbox/index.js +++ b/projects/packages/forms/src/dashboard/inbox/index.js @@ -1,33 +1,43 @@ -import { - Button, - __experimentalInputControl as InputControl, // eslint-disable-line wpcalypso/no-unsafe-wp-apis - SelectControl, -} from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; +import { Gridicon } from '@automattic/jetpack-components'; +import { useDispatch, useSelect } from '@wordpress/data'; import { useCallback, useEffect, useState } from '@wordpress/element'; -import { __, _n, sprintf } from '@wordpress/i18n'; -import { first, includes, map } from 'lodash'; +import { __ } from '@wordpress/i18n'; +import classnames from 'classnames'; +import { find, includes, map } from 'lodash'; import Layout from '../components/layout'; +import SearchForm from '../components/search-form'; import { STORE_NAME } from '../state'; +import BulkActionsMenu from './bulk-actions-menu'; import InboxList from './list'; import InboxResponse from './response'; - import './style.scss'; +const RESPONSES_FETCH_LIMIT = 20; + const Inbox = () => { const [ currentResponseId, setCurrentResponseId ] = useState( -1 ); - const [ searchText, setSearchText ] = useState( '' ); + const [ view, setView ] = useState( 'list' ); + + const { invalidateResolution, setSearchQuery } = useDispatch( STORE_NAME ); + + const searchQuery = useSelect( select => select( STORE_NAME ).getSearchQuery() ); + + const [ currentPage, setCurrentPage ] = useState( 1 ); const [ loading, responses, total ] = useSelect( select => { const stateSelector = select( STORE_NAME ); return [ stateSelector.isFetchingResponses(), - stateSelector.getResponses( searchText ), + stateSelector.getResponses( + searchQuery, + RESPONSES_FETCH_LIMIT, + ( currentPage - 1 ) * RESPONSES_FETCH_LIMIT + ), stateSelector.getTotalResponses(), ]; }, - [ searchText ] + [ searchQuery, currentPage ] ); useEffect( () => { @@ -38,56 +48,77 @@ const Inbox = () => { setCurrentResponseId( responses[ 0 ].id ); }, [ responses, currentResponseId ] ); - const handleLoadMore = useCallback( () => { - // this only needs to change the offset for the query + const handleSearch = useCallback( + searchTerm => { + invalidateResolution( 'getResponses', [ searchTerm, RESPONSES_FETCH_LIMIT, 0 ] ); + setCurrentPage( 1 ); + setSearchQuery( searchTerm ); + }, + [ setSearchQuery, setCurrentPage, invalidateResolution ] + ); + + const handlePageChange = useCallback( + page => { + invalidateResolution( 'getResponses', [ + searchQuery, + RESPONSES_FETCH_LIMIT, + ( page - 1 ) * RESPONSES_FETCH_LIMIT, + ] ); + setCurrentPage( page ); + }, + [ searchQuery, setCurrentPage, invalidateResolution ] + ); + + const selectResponse = useCallback( id => { + setCurrentResponseId( id ); + setView( 'response' ); }, [] ); - const handleSearch = useCallback( event => { + const handleGoBack = useCallback( event => { event.preventDefault(); - // this only needs to actually set a searchText (called differently) so we put as dependency on the useSelect - // currently the search is being triggered every time searchText changes + setView( 'list' ); }, [] ); - const numberOfResponses = sprintf( - /* translators: %s: Number of responses. */ - _n( '%s response', '%s responses', total, 'jetpack-forms' ), - total + const classes = classnames( 'jp-forms__inbox', { + 'is-response-view': view === 'response', + } ); + + const title = ( + <> + { __( 'Responses', 'jetpack-forms' ) } + { /* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */ } + + + { __( 'View all responses', 'jetpack-forms' ) } + + ); return ( - +
-
- - - -
- - - + +
+
- +
diff --git a/projects/packages/forms/src/dashboard/inbox/list.js b/projects/packages/forms/src/dashboard/inbox/list.js index faca18fa4cea4..b38ab5f5cbad9 100644 --- a/projects/packages/forms/src/dashboard/inbox/list.js +++ b/projects/packages/forms/src/dashboard/inbox/list.js @@ -1,5 +1,6 @@ -import { useState } from '@wordpress/element'; +import { useMemo } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { noop } from 'lodash'; import PageNavigation from '../components/page-navigation'; import Table from '../components/table'; @@ -18,21 +19,56 @@ const COLUMNS = [ }, ]; -const InboxList = ( { responses, onSelectionChange } ) => { - const [ currentPage, setCurrentPage ] = useState( 1 ); +const InboxList = ( { + currentPage, + currentResponseId, + pages, + responses, + setCurrentPage, + setCurrentResponseId, + loading, +} ) => { + const tableItems = useMemo( + () => + responses.map( response => ( { + ...response, + onClick: () => setCurrentResponseId( response.id ), + isActive: response.id === currentResponseId, + } ) ), + [ currentResponseId, responses, setCurrentResponseId ] + ); + + if ( loading ) { + return ( + + ); + } + if ( responses.length === 0 ) { + return ( +
+ ); + } return ( <>
diff --git a/projects/packages/forms/src/dashboard/inbox/style.scss b/projects/packages/forms/src/dashboard/inbox/style.scss index d9fbb9715d7b4..fe13612e0234b 100644 --- a/projects/packages/forms/src/dashboard/inbox/style.scss +++ b/projects/packages/forms/src/dashboard/inbox/style.scss @@ -1,11 +1,43 @@ +.jp-forms__inbox { + a.back-button { + align-items: center; + color: #2C3338; + display: none; + font-size: 14px; + } + + @media (min-width: 1025px) { + a.back-button { + display: none !important; + } + + span.title { + display: inline-flex !important; + } + } + + &.is-response-view { + span.title { + display: none; + } + + a.back-button { + display: inline-flex; + } + } +} + .jp-forms__inbox-content { box-sizing: border-box; display: flex; flex-direction: row; - gap: 24px; + gap: 0; padding: 32px 0; + position: relative; + width: 100%; - @media (min-width: 481px) { + @media (min-width: 1025px) { + gap: 24px; padding-left: 64px; padding-right: 64px; } @@ -14,16 +46,37 @@ .jp-forms__inbox-content-column { align-items: center; box-sizing: border-box; - display: flex; - flex: 3; flex-direction: column; + min-width: 0; + width: 100%; + + &:first-child { + display: flex; + } &:last-child { display: none; - flex: 2; + } - @media (min-width: 1025px) { - display: flex; + .jp-forms__inbox.is-response-view &:first-child { + display: none; + } + + .jp-forms__inbox.is-response-view &:last-child { + display: flex; + } + + @media (min-width: 481px) and (max-width: 1025px) { + padding-left: 64px; + padding-right: 64px; + } + + @media (min-width: 1025px) { + display: flex !important; + flex: 3; + width: auto; + + &:last-child { flex: 2; } } @@ -46,6 +99,7 @@ .jp-forms__inbox-list.jp-forms__table { display: table; + min-height: unset; @media (max-width: 480px) { border-left-width: 0; @@ -53,11 +107,43 @@ border-radius: 0; } + @media (max-width: 660px) { + .jp-forms__table-cell.is-source { + display: none; + } + } + + @media (max-width: 1024px) { + .jp-forms__table-item.is-active { + background-color: #fff; + color: inherit; + + &:nth-child(even) { + background-color: #f9f9f6; + } + } + } + + .jp-forms__table-cell { + white-space: nowrap; + } + .jp-forms__table-cell.is-name, .jp-forms__table-cell.is-source { overflow: hidden; text-overflow: ellipsis; - white-space: nowrap; + } + + .jp-forms__table-cell.is-source { + @media (min-width: 1025px) and (max-width: 1440px) { + max-width: 130px; + } + + max-width: 350px; + } + + .jp-forms__table-cell.is-name { + max-width: 100px; width: 100%; } } @@ -168,6 +254,14 @@ flex-direction: row; padding: 20px 64px; + .jp-forms__inbox.is-response-view & { + display: none; + + @media (min-width: 1025px) { + display: flex; + } + } + button { margin: 0 5px; } diff --git a/projects/packages/forms/src/dashboard/state/action-types.js b/projects/packages/forms/src/dashboard/state/action-types.js index 202b1c79efbe5..aa6af0e5b1967 100644 --- a/projects/packages/forms/src/dashboard/state/action-types.js +++ b/projects/packages/forms/src/dashboard/state/action-types.js @@ -4,3 +4,5 @@ export const FINISH_RESOLUTION = 'FINISH_RESOLUTION'; export const RESPONSES_FETCH = 'RESPONSES_FETCH'; export const RESPONSES_FETCH_RECEIVE = 'RESPONSES_FETCH_RECEIVE'; export const RESPONSES_FETCH_FAIL = 'RESPONSES_FETCH_FAIL'; + +export const RESPONSES_QUERY_SEARCH_UPDATE = 'RESPONSES_QUERY_SEARCH_UPDATE'; diff --git a/projects/packages/forms/src/dashboard/state/actions.js b/projects/packages/forms/src/dashboard/state/actions.js index 2158345cf16f5..f9e3d97447fcc 100644 --- a/projects/packages/forms/src/dashboard/state/actions.js +++ b/projects/packages/forms/src/dashboard/state/actions.js @@ -7,6 +7,7 @@ import { RESPONSES_FETCH, RESPONSES_FETCH_FAIL, RESPONSES_FETCH_RECEIVE, + RESPONSES_QUERY_SEARCH_UPDATE, } from './action-types'; /** @@ -57,3 +58,10 @@ export const failResponsesFetch = error => { error, }; }; + +export const setSearchQuery = searchQuery => { + return { + type: RESPONSES_QUERY_SEARCH_UPDATE, + searchQuery, + }; +}; diff --git a/projects/packages/forms/src/dashboard/state/reducer.js b/projects/packages/forms/src/dashboard/state/reducer.js index 8bdc6a353699a..4cc41ccd5af6d 100644 --- a/projects/packages/forms/src/dashboard/state/reducer.js +++ b/projects/packages/forms/src/dashboard/state/reducer.js @@ -5,7 +5,12 @@ import { combineReducers } from '@wordpress/data'; /** * Internal dependencies */ -import { RESPONSES_FETCH, RESPONSES_FETCH_RECEIVE, RESPONSES_FETCH_FAIL } from './action-types'; +import { + RESPONSES_FETCH, + RESPONSES_FETCH_RECEIVE, + RESPONSES_FETCH_FAIL, + RESPONSES_QUERY_SEARCH_UPDATE, +} from './action-types'; const loading = ( state = false, action ) => { if ( action.type === RESPONSES_FETCH ) { @@ -43,8 +48,17 @@ const total = ( state = 0, action ) => { return state; }; +const searchQuery = ( state = '', action ) => { + if ( action.type === RESPONSES_QUERY_SEARCH_UPDATE ) { + return action.searchQuery; + } + + return state; +}; + export default combineReducers( { loading, responses, total, + searchQuery, } ); diff --git a/projects/packages/forms/src/dashboard/state/selectors.js b/projects/packages/forms/src/dashboard/state/selectors.js index 37e20c08a1c32..50a343338568b 100644 --- a/projects/packages/forms/src/dashboard/state/selectors.js +++ b/projects/packages/forms/src/dashboard/state/selectors.js @@ -8,11 +8,14 @@ export const isFetchingResponses = state => state.loading; export const getResponses = state => map( state.responses, response => { - response.date = dateI18n( 'F j, Y', response.date ); - response.source = response.entry_title; - response.name = - response.author_name || response.author_email || response.author_url || response.ip; - return response; + return { + ...response, + date: dateI18n( 'F j, Y', response.date ), + source: response.entry_title, + name: response.author_name || response.author_email || response.author_url || response.ip, + }; } ); export const getTotalResponses = state => state.total; + +export const getSearchQuery = state => state.searchQuery; diff --git a/projects/packages/identity-crisis/CHANGELOG.md b/projects/packages/identity-crisis/CHANGELOG.md index 0bca67afeb1e3..fe65687b53799 100644 --- a/projects/packages/identity-crisis/CHANGELOG.md +++ b/projects/packages/identity-crisis/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.39] - 2023-03-08 +### Changed +- Updated package dependencies. [#29216] + ## [0.8.38] - 2023-02-20 ### Changed - Minor internal updates. @@ -328,6 +332,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated package dependencies. - Use Connection/Urls for home_url and site_url functions migrated from Sync. +[0.8.39]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.38...v0.8.39 [0.8.38]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.37...v0.8.38 [0.8.37]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.36...v0.8.37 [0.8.36]: https://github.com/Automattic/jetpack-identity-crisis/compare/v0.8.35...v0.8.36 diff --git a/projects/packages/identity-crisis/changelog/renovate-wordpress-monorepo b/projects/packages/identity-crisis/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/identity-crisis/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/identity-crisis/package.json b/projects/packages/identity-crisis/package.json index cf5afda9f01f9..7a2a227a8ab9c 100644 --- a/projects/packages/identity-crisis/package.json +++ b/projects/packages/identity-crisis/package.json @@ -1,6 +1,6 @@ { "name": "jetpack-identity-crisis", - "version": "0.8.39-alpha", + "version": "0.8.39", "description": "Jetpack Identity Crisis", "main": "_inc/admin.jsx", "repository": { diff --git a/projects/packages/identity-crisis/src/class-identity-crisis.php b/projects/packages/identity-crisis/src/class-identity-crisis.php index 62fd19b9957a6..060b47692b654 100644 --- a/projects/packages/identity-crisis/src/class-identity-crisis.php +++ b/projects/packages/identity-crisis/src/class-identity-crisis.php @@ -28,7 +28,7 @@ class Identity_Crisis { /** * Package Version */ - const PACKAGE_VERSION = '0.8.39-alpha'; + const PACKAGE_VERSION = '0.8.39'; /** * Instance of the object. diff --git a/projects/packages/import/CHANGELOG.md b/projects/packages/import/CHANGELOG.md index e48ef509d95c4..1cc9c38b564fb 100644 --- a/projects/packages/import/CHANGELOG.md +++ b/projects/packages/import/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.0] - 2023-03-08 +### Added +- Add the `/jetpack/v4/import/media/*` endpoints. [#29080] + ## 0.1.0 - 2023-02-20 ### Added - Added import REST endpoints. [#28824] @@ -12,3 +16,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed various imported resources hierarchies [#29012] + +[0.2.0]: https://github.com/Automattic/jetpack-import/compare/v0.1.0...v0.2.0 diff --git a/projects/packages/import/composer.json b/projects/packages/import/composer.json index 95ecf8bf73eb5..13b12b554ca80 100644 --- a/projects/packages/import/composer.json +++ b/projects/packages/import/composer.json @@ -51,7 +51,7 @@ }, "autotagger": true, "branch-alias": { - "dev-trunk": "0.1.x-dev" + "dev-trunk": "0.2.x-dev" }, "textdomain": "jetpack-import", "version-constants": { diff --git a/projects/packages/import/package.json b/projects/packages/import/package.json index 3f7591026a2bc..b0c2ef86d178e 100644 --- a/projects/packages/import/package.json +++ b/projects/packages/import/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-import", - "version": "0.1.0", + "version": "0.2.0", "description": "Set of REST API routes used in WPCOM Unified Importer.", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/import/#readme", "bugs": { diff --git a/projects/packages/import/src/class-main.php b/projects/packages/import/src/class-main.php index 828f61ac9670e..949b7fef67943 100644 --- a/projects/packages/import/src/class-main.php +++ b/projects/packages/import/src/class-main.php @@ -20,7 +20,7 @@ class Main { * * @var string */ - const PACKAGE_VERSION = '0.1.0'; + const PACKAGE_VERSION = '0.2.0'; /** * A list of all the routes. @@ -67,6 +67,7 @@ public static function initialize_rest_api() { $routes = array( 'categories' => new Endpoints\Category(), 'comments' => new Endpoints\Comment(), + 'media' => new Endpoints\Attachment(), 'pages' => new Endpoints\Page(), 'posts' => new Endpoints\Post(), 'tags' => new Endpoints\Tag(), diff --git a/projects/packages/import/src/endpoints/class-attachment.php b/projects/packages/import/src/endpoints/class-attachment.php new file mode 100644 index 0000000000000..9f3b26695e1af --- /dev/null +++ b/projects/packages/import/src/endpoints/class-attachment.php @@ -0,0 +1,131 @@ +import_id_meta_type = 'post'; + } + + /** + * Registers the routes for the objects of the controller. + * + * @see WP_REST_Terms_Controller::register_rest_route() + */ + public function register_routes() { + register_rest_route( + self::$rest_namespace, + '/' . $this->rest_base, + $this->get_route_options() + ); + + register_rest_route( + self::$rest_namespace, + '/' . $this->rest_base . '/(?P[\d]+)', + array( + 'args' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the attachment.', 'jetpack-import' ), + 'type' => 'integer', + ), + ), + array( + 'methods' => \WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_item' ), + 'permission_callback' => array( $this, 'update_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ), + ), + 'allow_batch' => array( 'v1' => true ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + register_rest_route( + self::$rest_namespace, + '/' . $this->rest_base . '/(?P[\d]+)/post-process', + array( + 'methods' => \WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'post_process_item' ), + 'permission_callback' => array( $this, 'import_permissions_callback' ), + 'args' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the attachment.', 'jetpack-import' ), + 'type' => 'integer', + ), + 'action' => array( + 'type' => 'string', + 'enum' => array( 'create-image-subsizes' ), + 'required' => true, + ), + ), + ) + ); + } + + /** + * Updates a single attachment. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function update_item( $request ) { + $response = parent::update_item( $request ); + + return $this->add_import_id_metadata( $request, $response ); + } + + /** + * Adds the schema from additional fields to a schema array. + * + * The type of object is inferred from the passed schema. + * + * @param array $schema Schema array. + * @return array Modified Schema array. + */ + public function add_additional_fields_schema( $schema ) { + // The unique identifier is only required for PUT requests. + return $this->add_unique_identifier_to_schema( $schema, isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] === 'PUT' ); + } + + /** + * Performs post processing on an attachment. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure. + */ + public function post_process_item( $request ) { + require_once ABSPATH . 'wp-admin/includes/image.php'; + + \wp_update_image_subsizes( $request['id'] ); + $request['context'] = 'edit'; + + return $this->prepare_item_for_response( \get_post( $request['id'] ), $request ); + } +} diff --git a/projects/packages/import/src/endpoints/class-category.php b/projects/packages/import/src/endpoints/class-category.php index 2ddcb7b202463..a14080374273b 100644 --- a/projects/packages/import/src/endpoints/class-category.php +++ b/projects/packages/import/src/endpoints/class-category.php @@ -17,6 +17,13 @@ class Category extends \WP_REST_Terms_Controller { */ use Import; + /** + * Whether the controller supports batching. Default true. + * + * @var array + */ + protected $allow_batch = array( 'v1' => true ); + /** * Constructor. */ diff --git a/projects/packages/import/src/endpoints/class-comment.php b/projects/packages/import/src/endpoints/class-comment.php index 638cf5592ed04..e7c9baa97647f 100644 --- a/projects/packages/import/src/endpoints/class-comment.php +++ b/projects/packages/import/src/endpoints/class-comment.php @@ -17,6 +17,13 @@ class Comment extends \WP_REST_Comments_Controller { */ use Import; + /** + * Whether the controller supports batching. + * + * @var array + */ + protected $allow_batch = array( 'v1' => true ); + /** * Constructor. */ diff --git a/projects/packages/import/src/endpoints/class-page.php b/projects/packages/import/src/endpoints/class-page.php index 8d10533339d4e..0e517c355bcd8 100644 --- a/projects/packages/import/src/endpoints/class-page.php +++ b/projects/packages/import/src/endpoints/class-page.php @@ -19,6 +19,19 @@ public function __construct() { parent::__construct( 'page' ); } + /** + * Adds the schema from additional fields to a schema array. + * + * The type of object is inferred from the passed schema. + * + * @param array $schema Schema array. + * @return array Modified Schema array. + */ + public function add_additional_fields_schema( $schema ) { + // Add the import unique ID to the schema. + return $this->add_unique_identifier_to_schema( $schema ); + } + /** * Creates a single page. * diff --git a/projects/packages/import/src/endpoints/class-post.php b/projects/packages/import/src/endpoints/class-post.php index 5a3be46033790..675849421f04a 100644 --- a/projects/packages/import/src/endpoints/class-post.php +++ b/projects/packages/import/src/endpoints/class-post.php @@ -21,6 +21,13 @@ class Post extends \WP_REST_Posts_Controller { */ use Import; + /** + * Whether the controller supports batching. + * + * @var array + */ + protected $allow_batch = array( 'v1' => true ); + /** * Constructor. * diff --git a/projects/packages/import/src/endpoints/class-tag.php b/projects/packages/import/src/endpoints/class-tag.php index 154db4beb200e..cca008e477aef 100644 --- a/projects/packages/import/src/endpoints/class-tag.php +++ b/projects/packages/import/src/endpoints/class-tag.php @@ -17,6 +17,13 @@ class Tag extends \WP_REST_Terms_Controller { */ use Import; + /** + * Whether the controller supports batching. Default true. + * + * @var array + */ + protected $allow_batch = array( 'v1' => true ); + /** * Constructor. */ diff --git a/projects/packages/import/src/endpoints/trait-import.php b/projects/packages/import/src/endpoints/trait-import.php index b6fc9a974ac1f..4dff50523a17a 100644 --- a/projects/packages/import/src/endpoints/trait-import.php +++ b/projects/packages/import/src/endpoints/trait-import.php @@ -62,15 +62,16 @@ public function create_item( $request ) { * Adds the unique identifier to the schema array. * * @param array $schema Schema array. + * @param bool $required Whether the field is required. * @return array Modified Schema array. */ - protected function add_unique_identifier_to_schema( $schema ) { + protected function add_unique_identifier_to_schema( $schema, $required = true ) { // Add the import unique ID to the schema. $schema['properties'][ $this->import_id_field_name ] = array( 'description' => __( 'Jetpack Import unique identifier for the term.', 'jetpack-import' ), 'type' => 'integer', 'context' => array( 'view', 'embed', 'edit' ), - 'required' => true, + 'required' => $required, ); return $schema; @@ -89,10 +90,11 @@ protected function add_import_id_metadata( $request, $response ) { return $response; } - $data = $response->get_data(); + $data = $response->get_data(); + $status = $response->get_status(); - // Skip if the resource has not been added. - if ( $response->get_status() !== 201 ) { + // Skip if the resource has not been added or modified. + if ( ! ( $status === 200 || $status === 201 ) ) { return $response; } @@ -158,7 +160,7 @@ protected function get_route_options() { 'permission_callback' => array( $this, 'import_permissions_callback' ), 'args' => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::CREATABLE ), ), - 'allow_batch' => array( 'v1' => true ), + 'allow_batch' => $this->allow_batch, 'schema' => array( $this, 'get_public_item_schema' ), ); } diff --git a/projects/packages/jetpack-mu-wpcom/CHANGELOG.md b/projects/packages/jetpack-mu-wpcom/CHANGELOG.md index b47fe6099cd9b..2005ae2dff728 100644 --- a/projects/packages/jetpack-mu-wpcom/CHANGELOG.md +++ b/projects/packages/jetpack-mu-wpcom/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.0] - 2023-03-08 +### Added +- Add a Launchpad REST API endpoint for cross-platform benefit [#29082] + ## [1.0.1] - 2023-02-28 ### Changed - Updated checks for loading the coming soon feature. [#28932] @@ -40,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Testing initial package release. +[1.1.0]: https://github.com/Automattic/jetpack-mu-wpcom/compare/v1.0.1...v1.1.0 [1.0.1]: https://github.com/Automattic/jetpack-mu-wpcom/compare/v1.0.0...v1.0.1 [0.2.2]: https://github.com/Automattic/jetpack-mu-wpcom/compare/v0.2.1...v0.2.2 [0.2.1]: https://github.com/Automattic/jetpack-mu-wpcom/compare/v0.2.0...v0.2.1 diff --git a/projects/packages/jetpack-mu-wpcom/changelog/add-launchpad-rest-api-endpoint b/projects/packages/jetpack-mu-wpcom/changelog/add-launchpad-rest-api-endpoint deleted file mode 100644 index a6edb3f834eea..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/changelog/add-launchpad-rest-api-endpoint +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Add a Launchpad REST API endpoint for cross-platform benefit diff --git a/projects/packages/jetpack-mu-wpcom/changelog/fix-defensive-loader-function b/projects/packages/jetpack-mu-wpcom/changelog/fix-defensive-loader-function new file mode 100644 index 0000000000000..f0cae4a264d4a --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/fix-defensive-loader-function @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Check for existence of wpcom_rest_api_v2_load_plugin function before loading wpcom endpoints. diff --git a/projects/packages/jetpack-mu-wpcom/package.json b/projects/packages/jetpack-mu-wpcom/package.json index b4a8a54628ab9..ebee4e154b80f 100644 --- a/projects/packages/jetpack-mu-wpcom/package.json +++ b/projects/packages/jetpack-mu-wpcom/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-mu-wpcom", - "version": "1.1.0-alpha", + "version": "1.1.1-alpha", "description": "Enhances your site with features powered by WordPress.com", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/jetpack-mu-wpcom/#readme", "bugs": { diff --git a/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php b/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php index 78d7f74b06f9d..4b27214893a10 100644 --- a/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php +++ b/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php @@ -14,7 +14,7 @@ */ class Jetpack_Mu_Wpcom { - const PACKAGE_VERSION = '1.1.0-alpha'; + const PACKAGE_VERSION = '1.1.1-alpha'; const PKG_DIR = __DIR__ . '/../'; /** @@ -70,6 +70,10 @@ public static function load_coming_soon() { * Load WP REST API plugins for wpcom */ public static function load_wpcom_rest_api_endpoints() { + if ( ! function_exists( 'wpcom_rest_api_v2_load_plugin' ) ) { + return; + } + // We don't use `wpcom_rest_api_v2_load_plugin_files` because it operates inconsisently. $plugins = glob( __DIR__ . '/features/wpcom-endpoints/*.php' ); diff --git a/projects/packages/jitm/CHANGELOG.md b/projects/packages/jitm/CHANGELOG.md index fcddbf03f50af..485d523254e71 100644 --- a/projects/packages/jitm/CHANGELOG.md +++ b/projects/packages/jitm/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.3.1] - 2023-03-08 +### Changed +- Updated package dependencies. [#29216] + ## [2.3.0] - 2023-02-20 ### Changed - Moving deleting connection owner notice from JITM to Connection package. [#28516] @@ -546,6 +550,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update Jetpack to use new JITM package +[2.3.1]: https://github.com/Automattic/jetpack-jitm/compare/v2.3.0...v2.3.1 [2.3.0]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.42...v2.3.0 [2.2.42]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.41...v2.2.42 [2.2.41]: https://github.com/Automattic/jetpack-jitm/compare/v2.2.40...v2.2.41 diff --git a/projects/packages/jitm/changelog/renovate-wordpress-monorepo b/projects/packages/jitm/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/jitm/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/jitm/src/class-jitm.php b/projects/packages/jitm/src/class-jitm.php index a489e0b84ef68..ec3e98ec48ae4 100644 --- a/projects/packages/jitm/src/class-jitm.php +++ b/projects/packages/jitm/src/class-jitm.php @@ -20,7 +20,7 @@ */ class JITM { - const PACKAGE_VERSION = '2.3.1-alpha'; + const PACKAGE_VERSION = '2.3.1'; /** * The configuration method that is called from the jetpack-config package. diff --git a/projects/packages/lazy-images/CHANGELOG.md b/projects/packages/lazy-images/CHANGELOG.md index 4edd616dc5c56..ebbec60ed059e 100644 --- a/projects/packages/lazy-images/CHANGELOG.md +++ b/projects/packages/lazy-images/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.1.33] - 2023-03-08 +### Changed +- Updated package dependencies. [#29216] + +### Fixed +- Fix lazy-load images in Safari [#29243] + ## [2.1.32] - 2023-02-20 ### Changed - Minor internal updates. @@ -293,6 +300,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Lazy Images: Move into a package +[2.1.33]: https://github.com/Automattic/jetpack-lazy-images/compare/v2.1.32...v2.1.33 [2.1.32]: https://github.com/Automattic/jetpack-lazy-images/compare/v2.1.31...v2.1.32 [2.1.31]: https://github.com/Automattic/jetpack-lazy-images/compare/v2.1.30...v2.1.31 [2.1.30]: https://github.com/Automattic/jetpack-lazy-images/compare/v2.1.29...v2.1.30 diff --git a/projects/packages/lazy-images/changelog/fix-safari-lazy-loading b/projects/packages/lazy-images/changelog/fix-safari-lazy-loading deleted file mode 100644 index f680491214e5d..0000000000000 --- a/projects/packages/lazy-images/changelog/fix-safari-lazy-loading +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Fix lazy-load images in Safari diff --git a/projects/packages/lazy-images/changelog/renovate-wordpress-monorepo b/projects/packages/lazy-images/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/lazy-images/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/my-jetpack/CHANGELOG.md b/projects/packages/my-jetpack/CHANGELOG.md index 6ab559a41139e..00d1b295db216 100644 --- a/projects/packages/my-jetpack/CHANGELOG.md +++ b/projects/packages/my-jetpack/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.7.13] - 2023-03-08 +### Changed +- Updated package dependencies. [#29216] + ## [2.7.12] - 2023-02-28 ### Changed - Update billing language [#29126] @@ -758,6 +762,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Created package +[2.7.13]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.12...2.7.13 [2.7.12]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.11...2.7.12 [2.7.11]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.10...2.7.11 [2.7.10]: https://github.com/Automattic/jetpack-my-jetpack/compare/2.7.9...2.7.10 diff --git a/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/index.jsx b/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/index.jsx index 571c6215a1a2e..4908417956258 100644 --- a/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/index.jsx +++ b/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/index.jsx @@ -5,6 +5,7 @@ import { Container, Col, Text, + ZendeskChat, useBreakpointMatch, } from '@automattic/jetpack-components'; import { useConnectionErrorNotice, ConnectionError } from '@automattic/jetpack-connection'; @@ -122,6 +123,7 @@ export default function MyJetpackScreen() { + ); } diff --git a/projects/packages/my-jetpack/changelog/add-zendesk-chat-module b/projects/packages/my-jetpack/changelog/add-zendesk-chat-module new file mode 100644 index 0000000000000..009af355b6fe8 --- /dev/null +++ b/projects/packages/my-jetpack/changelog/add-zendesk-chat-module @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add Zendesk chat module to My Jetpack page diff --git a/projects/packages/my-jetpack/changelog/renovate-jest-monorepo b/projects/packages/my-jetpack/changelog/renovate-jest-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/my-jetpack/changelog/renovate-jest-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/my-jetpack/changelog/renovate-wordpress-monorepo b/projects/packages/my-jetpack/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/my-jetpack/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/my-jetpack/composer.json b/projects/packages/my-jetpack/composer.json index e32a389943e7d..9ce27124ff69a 100644 --- a/projects/packages/my-jetpack/composer.json +++ b/projects/packages/my-jetpack/composer.json @@ -71,7 +71,7 @@ "link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}" }, "branch-alias": { - "dev-trunk": "2.7.x-dev" + "dev-trunk": "2.8.x-dev" }, "version-constants": { "::PACKAGE_VERSION": "src/class-initializer.php" diff --git a/projects/packages/my-jetpack/package.json b/projects/packages/my-jetpack/package.json index 32832202bfe6a..228701fddc127 100644 --- a/projects/packages/my-jetpack/package.json +++ b/projects/packages/my-jetpack/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-my-jetpack", - "version": "2.7.13-alpha", + "version": "2.8.0-alpha", "description": "WP Admin page with information and configuration shared among all Jetpack stand-alone plugins", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/my-jetpack/#readme", "bugs": { diff --git a/projects/packages/my-jetpack/src/class-initializer.php b/projects/packages/my-jetpack/src/class-initializer.php index 0027e4c47d1fa..18e87b0e6f4c6 100644 --- a/projects/packages/my-jetpack/src/class-initializer.php +++ b/projects/packages/my-jetpack/src/class-initializer.php @@ -30,7 +30,7 @@ class Initializer { * * @var string */ - const PACKAGE_VERSION = '2.7.13-alpha'; + const PACKAGE_VERSION = '2.8.0-alpha'; /** * Initialize My Jetpack diff --git a/projects/packages/publicize/CHANGELOG.md b/projects/packages/publicize/CHANGELOG.md index 2c586a02fd1cb..bd10c2ffc35f4 100644 --- a/projects/packages/publicize/CHANGELOG.md +++ b/projects/packages/publicize/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.20.1] - 2023-03-08 +### Changed +- Updated package dependencies. [#29216] + ## [0.20.0] - 2023-02-28 ### Added - Add options panel for Social Image Generator to Jetpack Social sidebar. [#28737] @@ -235,6 +239,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated package dependencies. - Update package.json metadata. +[0.20.1]: https://github.com/Automattic/jetpack-publicize/compare/v0.20.0...v0.20.1 [0.20.0]: https://github.com/Automattic/jetpack-publicize/compare/v0.19.5...v0.20.0 [0.19.5]: https://github.com/Automattic/jetpack-publicize/compare/v0.19.4...v0.19.5 [0.19.4]: https://github.com/Automattic/jetpack-publicize/compare/v0.19.3...v0.19.4 diff --git a/projects/packages/publicize/changelog/renovate-concurrently-7.x b/projects/packages/publicize/changelog/renovate-concurrently-7.x deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/publicize/changelog/renovate-concurrently-7.x +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/publicize/changelog/renovate-wordpress-monorepo b/projects/packages/publicize/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/publicize/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/publicize/package.json b/projects/packages/publicize/package.json index 03170e69fb87f..d0742d4c2d0ef 100644 --- a/projects/packages/publicize/package.json +++ b/projects/packages/publicize/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-publicize", - "version": "0.20.1-alpha", + "version": "0.20.1", "description": "Publicize makes it easy to share your site’s posts on several social media networks automatically when you publish a new post.", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/publicize/#readme", "bugs": { diff --git a/projects/packages/search/CHANGELOG.md b/projects/packages/search/CHANGELOG.md index 3a066fadd58e9..1ab586df3b540 100644 --- a/projects/packages/search/CHANGELOG.md +++ b/projects/packages/search/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.33.2] - 2023-03-08 +### Changed +- Updated package dependencies. [#29216] + +### Fixed +- Fix bad check against isMultiSite resulting in extended search format not showing expected additional post details. [#29179] + ## [0.33.1] - 2023-02-28 ### Added - Search: Add JITM container [#29106] @@ -680,6 +687,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated package dependencies. - Update PHPUnit configs to include just what needs coverage rather than include everything then try to exclude stuff that doesn't. +[0.33.2]: https://github.com/Automattic/jetpack-search/compare/v0.33.1...v0.33.2 [0.33.1]: https://github.com/Automattic/jetpack-search/compare/v0.33.0...v0.33.1 [0.33.0]: https://github.com/Automattic/jetpack-search/compare/v0.32.0...v0.33.0 [0.32.0]: https://github.com/Automattic/jetpack-search/compare/v0.31.7...v0.32.0 diff --git a/projects/packages/search/changelog/renovate-concurrently-7.x b/projects/packages/search/changelog/renovate-concurrently-7.x deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/search/changelog/renovate-concurrently-7.x +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/search/changelog/renovate-jest-monorepo b/projects/packages/search/changelog/renovate-jest-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/search/changelog/renovate-jest-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/search/changelog/renovate-wordpress-monorepo b/projects/packages/search/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/search/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/search/changelog/update-add-date-to-search-results b/projects/packages/search/changelog/update-add-date-to-search-results deleted file mode 100644 index 83723d5b77ba0..0000000000000 --- a/projects/packages/search/changelog/update-add-date-to-search-results +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Fixes bad check against isMultiSite resulting in extended search format not showing expected additional post details. diff --git a/projects/packages/search/package.json b/projects/packages/search/package.json index 0f51654d5025d..563fec425422d 100644 --- a/projects/packages/search/package.json +++ b/projects/packages/search/package.json @@ -1,6 +1,6 @@ { "name": "jetpack-search", - "version": "0.33.2-alpha", + "version": "0.33.2", "description": "Package for Jetpack Search products", "main": "main.js", "directories": { diff --git a/projects/packages/search/src/class-package.php b/projects/packages/search/src/class-package.php index 920712f9838d9..8b54fd0a99f15 100644 --- a/projects/packages/search/src/class-package.php +++ b/projects/packages/search/src/class-package.php @@ -11,7 +11,7 @@ * Search package general information */ class Package { - const VERSION = '0.33.2-alpha'; + const VERSION = '0.33.2'; const SLUG = 'search'; /** diff --git a/projects/packages/videopress/CHANGELOG.md b/projects/packages/videopress/CHANGELOG.md index a40772d6db43d..467620487d50a 100644 --- a/projects/packages/videopress/CHANGELOG.md +++ b/projects/packages/videopress/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.12.0] - 2023-03-08 +### Added +- VideoPress: add caption control to video block toolbar [#29233] +- VideoPress: add chapters generation from description to dashboard [#29155] +- VideoPress: add story for the Banner component [#29296] +- VideoPress: add support to "private" as site default privacy on public Atomic sites. [#29104] +- VideoPress: Add video delete action to details page [#29161] +- VideoPress: Disable the video's privacy toggle on the VideoPress dashboard for private Atomic sites. [#29169] + +### Changed +- Add usePreview hook [#29164] +- Updated package dependencies. [#29216] +- VideoPress: rewrite player by using TypeScript [#29226] + +### Fixed +- VideoPress: fix requesting video data on Simple sites [#29261] + ## [0.11.0] - 2023-02-28 ### Added - Added support for the `preload` or `preloadcontent` attribute to the VideoPress shortcode. [#28865] @@ -744,6 +761,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Created empty package [#24952] +[0.12.0]: https://github.com/Automattic/jetpack-videopress/compare/v0.11.0...v0.12.0 [0.11.0]: https://github.com/Automattic/jetpack-videopress/compare/v0.10.12...v0.11.0 [0.10.12]: https://github.com/Automattic/jetpack-videopress/compare/v0.10.11...v0.10.12 [0.10.11]: https://github.com/Automattic/jetpack-videopress/compare/v0.10.10...v0.10.11 diff --git a/projects/packages/videopress/changelog/add-support-private-videos-on-public-atomic-site b/projects/packages/videopress/changelog/add-support-private-videos-on-public-atomic-site deleted file mode 100644 index 419ecfd84c520..0000000000000 --- a/projects/packages/videopress/changelog/add-support-private-videos-on-public-atomic-site +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -VideoPress: add support to "private" as site default privacy on public Atomic sites. diff --git a/projects/packages/videopress/changelog/add-videopress-dashboard-chapters b/projects/packages/videopress/changelog/add-videopress-dashboard-chapters deleted file mode 100644 index 6f40bab78d88c..0000000000000 --- a/projects/packages/videopress/changelog/add-videopress-dashboard-chapters +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -VideoPress: Add chapters generation from description to dashboard diff --git a/projects/packages/videopress/changelog/add-videopress-dashboard-delete b/projects/packages/videopress/changelog/add-videopress-dashboard-delete deleted file mode 100644 index 7a4ff54b1772e..0000000000000 --- a/projects/packages/videopress/changelog/add-videopress-dashboard-delete +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -VideoPress: Add video delete action to details page diff --git a/projects/packages/videopress/changelog/renovate-jest-monorepo b/projects/packages/videopress/changelog/renovate-jest-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/videopress/changelog/renovate-jest-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/videopress/changelog/renovate-wordpress-monorepo b/projects/packages/videopress/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/videopress/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/videopress/changelog/rnmobile-add-videopress-block-settings-toggle b/projects/packages/videopress/changelog/rnmobile-add-videopress-block-settings-toggle new file mode 100644 index 0000000000000..674c3087323f8 --- /dev/null +++ b/projects/packages/videopress/changelog/rnmobile-add-videopress-block-settings-toggle @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +VideoPress block: Add settings toggle to native block. diff --git a/projects/packages/videopress/changelog/update-disable-privacy-toggle-for-private-atomic-sites b/projects/packages/videopress/changelog/update-disable-privacy-toggle-for-private-atomic-sites deleted file mode 100644 index 6749c209496f4..0000000000000 --- a/projects/packages/videopress/changelog/update-disable-privacy-toggle-for-private-atomic-sites +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -VideoPress: Disable the video's privacy toggle on the VideoPress dashboard for private Atomic sites. diff --git a/projects/packages/videopress/changelog/update-videopress-add-poster-panel-story b/projects/packages/videopress/changelog/update-videopress-add-poster-panel-story new file mode 100644 index 0000000000000..813e8b6c10ba9 --- /dev/null +++ b/projects/packages/videopress/changelog/update-videopress-add-poster-panel-story @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +VideoPress: add story for PosterPanel component diff --git a/projects/packages/videopress/changelog/update-videopress-add-timestamp-control-cpm b/projects/packages/videopress/changelog/update-videopress-add-timestamp-control-cpm new file mode 100644 index 0000000000000..002989aa389b7 --- /dev/null +++ b/projects/packages/videopress/changelog/update-videopress-add-timestamp-control-cpm @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +VideoPress: first approach of TimestampControl component diff --git a/projects/packages/videopress/changelog/update-videopress-change-link-to-video-when-private b/projects/packages/videopress/changelog/update-videopress-change-link-to-video-when-private new file mode 100644 index 0000000000000..cc9b0c3a518dd --- /dev/null +++ b/projects/packages/videopress/changelog/update-videopress-change-link-to-video-when-private @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +VideoPress: set video URL based on the video privacy diff --git a/projects/packages/videopress/changelog/update-videopress-copy-url b/projects/packages/videopress/changelog/update-videopress-copy-url new file mode 100644 index 0000000000000..d2a2cc1a2eacb --- /dev/null +++ b/projects/packages/videopress/changelog/update-videopress-copy-url @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +VideoPress: Fix video URL available to copying in video details page diff --git a/projects/packages/videopress/changelog/update-videopress-fix-requesting-video-data-on-simple-sites b/projects/packages/videopress/changelog/update-videopress-fix-requesting-video-data-on-simple-sites deleted file mode 100644 index 84b87ddabe696..0000000000000 --- a/projects/packages/videopress/changelog/update-videopress-fix-requesting-video-data-on-simple-sites +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -VideoPress: fix requesting video data on Simple sites diff --git a/projects/packages/videopress/changelog/update-videopress-loading-ui b/projects/packages/videopress/changelog/update-videopress-loading-ui new file mode 100644 index 0000000000000..4f5f6b933c79f --- /dev/null +++ b/projects/packages/videopress/changelog/update-videopress-loading-ui @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +VideoPress: Separate loading states in video details page and disable redirect on save diff --git a/projects/packages/videopress/changelog/update-videopress-tscriptify-player b/projects/packages/videopress/changelog/update-videopress-tscriptify-player deleted file mode 100644 index da28205f851b3..0000000000000 --- a/projects/packages/videopress/changelog/update-videopress-tscriptify-player +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -VideoPress: rewrite player by using TypeScript diff --git a/projects/packages/videopress/changelog/update-videopress-video-caption b/projects/packages/videopress/changelog/update-videopress-video-caption deleted file mode 100644 index ab4b51f6de759..0000000000000 --- a/projects/packages/videopress/changelog/update-videopress-video-caption +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -VideoPress: add caption control to vidfeo block toolbar diff --git a/projects/packages/videopress/changelog/update-videpress-add-range-to-timestamp-control-cpm b/projects/packages/videopress/changelog/update-videpress-add-range-to-timestamp-control-cpm new file mode 100644 index 0000000000000..e1209dec342c1 --- /dev/null +++ b/projects/packages/videopress/changelog/update-videpress-add-range-to-timestamp-control-cpm @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +VideoPress: add Range control to the Timestamp control component diff --git a/projects/packages/videopress/composer.json b/projects/packages/videopress/composer.json index 0325521d7c478..eb723581eb4e4 100644 --- a/projects/packages/videopress/composer.json +++ b/projects/packages/videopress/composer.json @@ -61,7 +61,7 @@ "link-template": "https://github.com/Automattic/jetpack-videopress/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.11.x-dev" + "dev-trunk": "0.12.x-dev" }, "version-constants": { "::PACKAGE_VERSION": "src/class-package-version.php" diff --git a/projects/packages/videopress/package.json b/projects/packages/videopress/package.json index 91d57b702fe5b..99a2a2a57c8ae 100644 --- a/projects/packages/videopress/package.json +++ b/projects/packages/videopress/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-videopress", - "version": "0.11.1-alpha", + "version": "0.12.1-alpha", "description": "VideoPress package", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/videopress/#readme", "bugs": { diff --git a/projects/packages/videopress/src/class-package-version.php b/projects/packages/videopress/src/class-package-version.php index 446722b5c81f4..04a0ca027176b 100644 --- a/projects/packages/videopress/src/class-package-version.php +++ b/projects/packages/videopress/src/class-package-version.php @@ -11,7 +11,7 @@ * The Package_Version class. */ class Package_Version { - const PACKAGE_VERSION = '0.11.1-alpha'; + const PACKAGE_VERSION = '0.12.1-alpha'; const PACKAGE_SLUG = 'videopress'; diff --git a/projects/packages/videopress/src/client/admin/components/edit-video-details/index.tsx b/projects/packages/videopress/src/client/admin/components/edit-video-details/index.tsx index b4f6734bc58f2..c898aabc26aa2 100644 --- a/projects/packages/videopress/src/client/admin/components/edit-video-details/index.tsx +++ b/projects/packages/videopress/src/client/admin/components/edit-video-details/index.tsx @@ -58,13 +58,13 @@ const noop = () => { const Header = ( { saveDisabled = true, - busy = false, + disabled = false, onSaveChanges, onDelete, videoId, }: { saveDisabled?: boolean; - busy?: boolean; + disabled?: boolean; onSaveChanges: () => void; onDelete: () => void; videoId: string | number; @@ -83,10 +83,14 @@ const Header = ( { { __( 'Edit video details', 'jetpack-videopress-pkg' ) }
- - +
@@ -112,13 +116,15 @@ const Infos = ( { onChangeTitle, description, onChangeDescription, - loading, + loading = false, + disabled = false, }: { title: string; onChangeTitle: ( value: string ) => void; description: string; onChangeDescription: ( value: string ) => void; loading: boolean; + disabled: boolean; } ) => { const { hasIncompleteChapters } = useChaptersLiveParsing( description ); @@ -133,6 +139,7 @@ const Infos = ( { name="title" onChange={ onChangeTitle } onEnter={ noop } + disabled={ disabled } size="large" /> ) } @@ -147,6 +154,7 @@ const Infos = ( { name="description" onChange={ onChangeDescription } onEnter={ noop } + disabled={ disabled } type="textarea" size="large" rows={ 8 } @@ -184,6 +192,7 @@ const EditVideoDetails = () => { privacySetting, allowDownload, displayEmbed, + isPrivate, // Playback Token isFetchingPlaybackToken, // Page State/Actions @@ -232,11 +241,11 @@ const EditVideoDetails = () => { const { page } = useVideosQuery(); useEffect( () => { - if ( updated === true || deleted === true ) { + if ( deleted === true ) { const to = page > 1 ? `/?page=${ page }` : '/'; history.push( to ); } - }, [ updated, deleted ] ); + }, [ deleted ] ); if ( ! canPerformAction ) { history.push( '/' ); @@ -251,7 +260,7 @@ const EditVideoDetails = () => { } const isFetchingData = isFetching || isFetchingPlaybackToken; - const isBusy = isFetchingData || isDeleting || updating; + const isBusy = isDeleting || updating; const shortcode = `[videopress ${ guid }${ width ? ` w=${ width }` : '' }${ height ? ` h=${ height }` : '' @@ -280,7 +289,7 @@ const EditVideoDetails = () => { onSaveChanges={ handleSaveChanges } onDelete={ handleDelete } saveDisabled={ ! hasChanges } - busy={ isBusy } + disabled={ isBusy || isFetchingData } videoId={ id } /> @@ -294,92 +303,111 @@ const EditVideoDetails = () => { onChangeTitle={ setTitle } description={ description ?? '' } onChangeDescription={ setDescription } - loading={ isBusy } + loading={ isFetchingData } + disabled={ isBusy } />
: thumbnail } + thumbnail={ thumbnail } + loading={ isFetchingData } + processing={ processing } deleting={ isDeleting } + updating={ updating } duration={ duration } editable - processing={ processing } - loading={ isFetchingData } onSelectFromVideo={ handleOpenSelectFrame } onUploadImage={ selectPosterImageFromLibrary } />
- setPrivacySetting( value ) } - disabled={ isBusy } - prefix={ - // Casting for unknown since allowing only a string is a mistake - // at WP Components - ( ( -
- -
- ) as unknown ) as string - } - options={ [ - { - label: __( 'Site default', 'jetpack-videopress-pkg' ), - value: VIDEO_PRIVACY_LEVEL_SITE_DEFAULT, - }, - { - label: __( 'Public', 'jetpack-videopress-pkg' ), - value: VIDEO_PRIVACY_LEVEL_PUBLIC, - }, - { - label: __( 'Private', 'jetpack-videopress-pkg' ), - value: VIDEO_PRIVACY_LEVEL_PRIVATE, - }, - ] } - /> - - { __( 'Share', 'jetpack-videopress-pkg' ) } - - setDisplayEmbed( value ? 1 : 0 ) } - /> - - { __( 'Download', 'jetpack-videopress-pkg' ) } - - setAllowDownload( value ? 1 : 0 ) } - /> - { isBusy ? ( + { isFetchingData ? ( + + ) : ( + setPrivacySetting( value ) } + disabled={ isBusy } + prefix={ + // Casting for unknown since allowing only a string is a mistake + // at WP Components + ( ( +
+ +
+ ) as unknown ) as string + } + options={ [ + { + label: __( 'Site default', 'jetpack-videopress-pkg' ), + value: VIDEO_PRIVACY_LEVEL_SITE_DEFAULT, + }, + { + label: __( 'Public', 'jetpack-videopress-pkg' ), + value: VIDEO_PRIVACY_LEVEL_PUBLIC, + }, + { + label: __( 'Private', 'jetpack-videopress-pkg' ), + value: VIDEO_PRIVACY_LEVEL_PRIVATE, + }, + ] } + /> + ) } + { isFetchingData ? ( + + ) : ( + <> + + { __( 'Share', 'jetpack-videopress-pkg' ) } + + setDisplayEmbed( value ? 1 : 0 ) } + /> + + ) } + { isFetchingData ? ( + + ) : ( + <> + + { __( 'Download', 'jetpack-videopress-pkg' ) } + + setAllowDownload( value ? 1 : 0 ) } + /> + + ) } + { isBusy || isFetchingData ? ( // RadioControl does not support disabled state ) : ( diff --git a/projects/packages/videopress/src/client/admin/components/edit-video-details/use-edit-details.ts b/projects/packages/videopress/src/client/admin/components/edit-video-details/use-edit-details.ts index c30858fddb1fd..b9b36a4f73da7 100644 --- a/projects/packages/videopress/src/client/admin/components/edit-video-details/use-edit-details.ts +++ b/projects/packages/videopress/src/client/admin/components/edit-video-details/use-edit-details.ts @@ -109,6 +109,8 @@ export default () => { Number( videoId ) ); + const filename = video?.url?.split( '/' ).slice( -1 )[ 0 ]; + const { playbackToken, isFetchingPlaybackToken } = usePlaybackToken( video ); const [ libraryAttachment, setLibraryAttachment ] = useState( null ); @@ -293,6 +295,7 @@ export default () => { isFetchingPlaybackToken, ...video, ...formData, // formData is the local representation of the video + filename, hasChanges, posterImageSource, libraryAttachment, diff --git a/projects/packages/videopress/src/client/admin/components/video-details/index.tsx b/projects/packages/videopress/src/client/admin/components/video-details/index.tsx index f1074ea97a468..f6e743c7f54a7 100644 --- a/projects/packages/videopress/src/client/admin/components/video-details/index.tsx +++ b/projects/packages/videopress/src/client/admin/components/video-details/index.tsx @@ -4,6 +4,7 @@ import { Text } from '@automattic/jetpack-components'; import { gmdateI18n } from '@wordpress/date'; import { __ } from '@wordpress/i18n'; +import { getVideoUrlBasedOnPrivacy } from '../../../lib/url'; /** * Internal dependencies */ @@ -12,14 +13,27 @@ import Placeholder from '../placeholder'; import styles from './style.module.scss'; import { VideoDetailsProps } from './types'; -const VideoDetails = ( { filename, src, uploadDate, loading, shortcode }: VideoDetailsProps ) => { +const VideoDetails = ( { + filename, + uploadDate, + shortcode, + loading = false, + guid, + isPrivate, +}: VideoDetailsProps ) => { const formattedDate = uploadDate?.length ? gmdateI18n( 'F j, Y', uploadDate ) : false; + const videoLinkUrl = getVideoUrlBasedOnPrivacy( guid, isPrivate ); + return (
{ __( 'Link to video', 'jetpack-videopress-pkg' ) } - { loading ? : } + { loading ? ( + + ) : ( + + ) }
diff --git a/projects/packages/videopress/src/client/admin/components/video-details/stories/index.tsx b/projects/packages/videopress/src/client/admin/components/video-details/stories/index.tsx index 903d00e267641..ee3fa598212a6 100644 --- a/projects/packages/videopress/src/client/admin/components/video-details/stories/index.tsx +++ b/projects/packages/videopress/src/client/admin/components/video-details/stories/index.tsx @@ -16,6 +16,8 @@ const VideoDetailsTemplate: ComponentStory< typeof VideoDetails > = VideoDetails export const Default = VideoDetailsTemplate.bind( {} ); Default.args = { + guid: 'ezoR6kzb', filename: 'video-thumbnail.png', src: 'https://videos.files.wordpress.com/fx123456B/video-thumbnail.mov', + isPrivate: false, }; diff --git a/projects/packages/videopress/src/client/admin/components/video-details/types.ts b/projects/packages/videopress/src/client/admin/components/video-details/types.ts index e2e7b156b489e..c3b6943162ed2 100644 --- a/projects/packages/videopress/src/client/admin/components/video-details/types.ts +++ b/projects/packages/videopress/src/client/admin/components/video-details/types.ts @@ -1,4 +1,11 @@ +import type { VideoGUID } from '../../../block-editor/blocks/video/types'; + export type VideoDetailsProps = { + /** + * VideoPress GUID. + */ + guid?: VideoGUID; + /** * Video filename. */ @@ -7,7 +14,7 @@ export type VideoDetailsProps = { /** * Video source file URL. */ - src: string; + src?: string; /** * VideoPress embed shortcode. @@ -22,5 +29,7 @@ export type VideoDetailsProps = { /** * Loading mode */ - loading: boolean; + loading?: boolean; + + isPrivate?: boolean; }; diff --git a/projects/packages/videopress/src/client/admin/components/video-thumbnail/index.tsx b/projects/packages/videopress/src/client/admin/components/video-thumbnail/index.tsx index ae023f19c4424..623f5e62aa259 100644 --- a/projects/packages/videopress/src/client/admin/components/video-thumbnail/index.tsx +++ b/projects/packages/videopress/src/client/admin/components/video-thumbnail/index.tsx @@ -166,6 +166,7 @@ const VideoThumbnail = forwardRef< HTMLDivElement, VideoThumbnailProps >( uploading = false, processing = false, deleting = false, + updating = false, onUseDefaultThumbnail, onSelectFromVideo, onUploadImage, @@ -175,11 +176,11 @@ const VideoThumbnail = forwardRef< HTMLDivElement, VideoThumbnailProps >( ref ) => { const [ isSmall ] = useBreakpointMatch( 'sm' ); - const busy = loading || uploading || deleting; + const busy = loading || uploading || deleting || updating; // Mapping thumbnail (Ordered by priority) let thumbnail = defaultThumbnail; - thumbnail = loading || deleting ? : thumbnail; + thumbnail = loading ? : thumbnail; thumbnail = uploading ? ( ) : ( diff --git a/projects/packages/videopress/src/client/admin/components/video-thumbnail/types.ts b/projects/packages/videopress/src/client/admin/components/video-thumbnail/types.ts index 69764504ddc74..016d1add98715 100644 --- a/projects/packages/videopress/src/client/admin/components/video-thumbnail/types.ts +++ b/projects/packages/videopress/src/client/admin/components/video-thumbnail/types.ts @@ -68,6 +68,11 @@ export type VideoThumbnailProps = VideoThumbnailDropdownProps & { */ deleting?: boolean; + /** + * True when in updating mode. + */ + updating?: boolean; + /** * The video upload progress from 0 to 1. */ diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/components/banner/index.tsx b/projects/packages/videopress/src/client/block-editor/blocks/video/components/banner/index.tsx index c953dbd4c408d..4b85e54f0dc57 100644 --- a/projects/packages/videopress/src/client/block-editor/blocks/video/components/banner/index.tsx +++ b/projects/packages/videopress/src/client/block-editor/blocks/video/components/banner/index.tsx @@ -10,7 +10,7 @@ import type React from 'react'; import './style.scss'; -type BlockBannerProps = { +export type BlockBannerProps = { icon?: React.ReactNode; action?: React.ReactNode; children: React.ReactNode; diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/components/banner/stories/Banner.mdx b/projects/packages/videopress/src/client/block-editor/blocks/video/components/banner/stories/Banner.mdx new file mode 100644 index 0000000000000..5cdb6ff8d7a11 --- /dev/null +++ b/projects/packages/videopress/src/client/block-editor/blocks/video/components/banner/stories/Banner.mdx @@ -0,0 +1,41 @@ +import { Meta, Story, Canvas } from '@storybook/addon-docs'; +import Banner from '../index'; + + + +# Banner +A banner to show a message, with loading state control and customizable icon and call-to-action. + + + + + +## API + +### children + +- type: `ReactNode`; + +Component to render into the banner. + +### isLoading + +- type: `boolean` + +It shows a spinner icon when true. + +### icon + +- type: `ReactNode` +- default: `warning` core icon + +An optional icon. + +### action + +- type: `ReactNode`; + +Action component to render at the right of the banner. Usually, it's a button. \ No newline at end of file diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/components/banner/stories/index.tsx b/projects/packages/videopress/src/client/block-editor/blocks/video/components/banner/stories/index.tsx new file mode 100644 index 0000000000000..a746e55b56f4c --- /dev/null +++ b/projects/packages/videopress/src/client/block-editor/blocks/video/components/banner/stories/index.tsx @@ -0,0 +1,64 @@ +/** + * External dependencies + */ +import { Button } from '@wordpress/components'; +import * as allIcons from '@wordpress/icons'; +import React from 'react'; +import Banner, { BlockBannerProps } from '..'; +/** + * Internal dependencies + */ +import Doc from './Banner.mdx'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const { Icon, ...icons } = allIcons; + +type BannerStoryProps = BlockBannerProps & { + icon: string; + children?: React.ReactNode; +}; + +export default { + title: 'Packages/VideoPress/Block Editor/Banner', + component: Banner, + parameters: { + docs: { + page: Doc, + }, + }, + argTypes: { + icon: { + control: { + type: 'select', + options: [ 'none', ...Object.keys( icons ) ], + }, + }, + action: { + table: { + disable: true, + }, + }, + }, +}; + +const DefaultTemplate = ( args: BannerStoryProps ) => { + const props: BlockBannerProps = { + children: args.children, + isLoading: args.isLoading, + action: args.action, + }; + + const icon = args?.icon && args.icon !== 'none' ? icons[ args.icon ] : null; + if ( icon ) { + props.icon = icon; + } + + return ; +}; + +export const _default = DefaultTemplate.bind( {} ); +_default.args = { + children: 'Connect your site to WordPress.com to upload videos.', + action: , + isLoading: false, +}; diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/components/poster-panel/stories/PosterPanel.mdx b/projects/packages/videopress/src/client/block-editor/blocks/video/components/poster-panel/stories/PosterPanel.mdx new file mode 100644 index 0000000000000..42cda0f5a259a --- /dev/null +++ b/projects/packages/videopress/src/client/block-editor/blocks/video/components/poster-panel/stories/PosterPanel.mdx @@ -0,0 +1,13 @@ +import { Meta, Story, Canvas } from '@storybook/addon-docs'; +import PosterPanel from '../index'; + + + +# PosterPanel + + + + diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/components/poster-panel/stories/index.tsx b/projects/packages/videopress/src/client/block-editor/blocks/video/components/poster-panel/stories/index.tsx new file mode 100644 index 0000000000000..58fc133f1cb45 --- /dev/null +++ b/projects/packages/videopress/src/client/block-editor/blocks/video/components/poster-panel/stories/index.tsx @@ -0,0 +1,39 @@ +/** + * External dependencies + */ +import React from 'react'; +/** + * Internal dependencies + */ +import PosterPanel from '..'; +import Doc from './PosterPanel.mdx'; + +export default { + title: 'Packages/VideoPress/Block Editor/Poster Panel', + component: PosterPanel, + parameters: { + docs: { + page: Doc, + }, + }, + argTypes: {}, +}; + +const DefaultTemplate = args => { + const [ attributes, setAttributes ] = React.useState( { + poster: args.poster, + videoRatio: args.videoRatio, + } ); + + const setAttributesHandler = newAttributes => { + setAttributes( { ...attributes, ...newAttributes } ); + }; + + return ; +}; + +export const _default = DefaultTemplate.bind( {} ); +_default.args = { + poster: 'https://jetpackme.files.wordpress.com/2018/04/cropped-jetpack-favicon-2018.png', + videoRatio: 60, +}; diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/edit.native.tsx b/projects/packages/videopress/src/client/block-editor/blocks/video/edit.native.tsx index 0eb997a24f8bf..25c58d7273855 100644 --- a/projects/packages/videopress/src/client/block-editor/blocks/video/edit.native.tsx +++ b/projects/packages/videopress/src/client/block-editor/blocks/video/edit.native.tsx @@ -1,12 +1,13 @@ /** * WordPress dependencies */ -import { MediaPlaceholder } from '@wordpress/block-editor'; +import { MediaPlaceholder, InspectorControls } from '@wordpress/block-editor'; +import { PanelBody } from '@wordpress/components'; /** * External dependencies */ import React from 'react'; -import { View } from 'react-native'; +import { View, Text } from 'react-native'; /** * Internal dependencies */ @@ -20,9 +21,14 @@ import style from './style.scss'; * @param {object} props - Component props. * @param {object} props.attributes - Block attributes. * @param {Function} props.setAttributes - Function to set block attributes. + * @param {boolean} props.isSelected - Whether block is selected. * @returns {React.ReactNode} - React component. */ -export default function VideoPressEdit( { attributes, setAttributes } ): React.ReactNode { +export default function VideoPressEdit( { + attributes, + setAttributes, + isSelected, +} ): React.ReactNode { /** * TODO: The current components are intended to act as placeholders while block is in development. * They should eventually be edited or replaced to support VideoPress. @@ -53,6 +59,13 @@ export default function VideoPressEdit( { attributes, setAttributes } ): React.R return ( + { isSelected && ( + + + { 'Hello world!' } + + + ) } ); diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/edit.tsx b/projects/packages/videopress/src/client/block-editor/blocks/video/edit.tsx index b4e3166172415..c465226fc622d 100644 --- a/projects/packages/videopress/src/client/block-editor/blocks/video/edit.tsx +++ b/projects/packages/videopress/src/client/block-editor/blocks/video/edit.tsx @@ -11,7 +11,7 @@ import { import { createBlock } from '@wordpress/blocks'; import { Spinner, Placeholder, Button, withNotices, ToolbarButton } from '@wordpress/components'; import { store as coreStore } from '@wordpress/core-data'; -import { useSelect, useDispatch } from '@wordpress/data'; +import { useDispatch } from '@wordpress/data'; import { useEffect, useState, useCallback, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { caption as captionIcon } from '@wordpress/icons'; @@ -22,6 +22,7 @@ import debugFactory from 'debug'; */ import { isStandaloneActive, isVideoPressActive } from '../../../lib/connection'; import { buildVideoPressURL, getVideoPressUrl } from '../../../lib/url'; +import { usePreview } from '../../hooks/use-preview'; import { useSyncMedia } from '../../hooks/use-video-data-update'; import ConnectBanner from './components/banner/connect-banner'; import ColorPanel from './components/color-panel'; @@ -155,24 +156,7 @@ export default function VideoPressEdit( { const { filename, private_enabled_for_site: privateEnabledForSite } = videoData; // Get video preview status. - const defaultPreview = { html: null, scripts: [], width: null, height: null }; - const { preview, isRequestingEmbedPreview } = useSelect( - select => { - if ( ! videoPressUrl ) { - return { - preview: defaultPreview, - isRequestingEmbedPreview: false, - }; - } - - return { - preview: select( coreStore ).getEmbedPreview( videoPressUrl ) || defaultPreview, - isRequestingEmbedPreview: - select( coreStore ).isRequestingEmbedPreview( videoPressUrl ) || false, - }; - }, - [ videoPressUrl ] - ); + const { preview, isRequestingEmbedPreview } = usePreview( videoPressUrl ); // Pick video properties from preview. const { html: previewHtml, scripts, width: previewWidth, height: previewHeight } = preview; diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/types.ts b/projects/packages/videopress/src/client/block-editor/blocks/video/types.ts index 3aae24d02c4e3..58541be24976d 100644 --- a/projects/packages/videopress/src/client/block-editor/blocks/video/types.ts +++ b/projects/packages/videopress/src/client/block-editor/blocks/video/types.ts @@ -96,3 +96,10 @@ export type DetailsPanelProps = VideoControlProps & { updateError: object | null; isRequestingVideoData: boolean; }; + +export type VideoPreview = { + html?: string; + scripts: Array< string >; + width?: number; + height?: number; +}; diff --git a/projects/packages/videopress/src/client/block-editor/hooks/use-preview/Readme.md b/projects/packages/videopress/src/client/block-editor/hooks/use-preview/Readme.md new file mode 100644 index 0000000000000..d1b4a208fce18 --- /dev/null +++ b/projects/packages/videopress/src/client/block-editor/hooks/use-preview/Readme.md @@ -0,0 +1,20 @@ +# usePreview() + +React custom hook to fetch the video preview data. +Returns a `VideoPreview` object and the boolean `isRequestingEmbedPreview` + + +```jsx + +function VideoComponent( { videoPressUrl } ) { + const [ preview, isRequestingEmbedPreview ] = usePreview( videoPressUrl ); + + if ( isRequestingEmbedPreview ) { + return null; + } + + return ( + + ) +} +``` \ No newline at end of file diff --git a/projects/packages/videopress/src/client/block-editor/hooks/use-preview/index.ts b/projects/packages/videopress/src/client/block-editor/hooks/use-preview/index.ts new file mode 100644 index 0000000000000..2c5fe5d1eda2e --- /dev/null +++ b/projects/packages/videopress/src/client/block-editor/hooks/use-preview/index.ts @@ -0,0 +1,32 @@ +/* + * WordPress dependencies + */ +import { store as coreStore } from '@wordpress/core-data'; +import { useSelect } from '@wordpress/data'; +/* + * Types + */ +import type { VideoPreview } from '../../../block-editor/blocks/video/types'; + +export type UsePreviewResult = { + preview: VideoPreview; + isRequestingEmbedPreview: boolean; +}; + +const defaultPreview: VideoPreview = { html: null, scripts: [], width: null, height: null }; + +export const usePreview = ( videoPressUrl?: string ): UsePreviewResult => { + return useSelect( + select => { + if ( ! videoPressUrl ) { + return { preview: defaultPreview, isRequestingEmbedPreview: false }; + } + return { + preview: select( coreStore ).getEmbedPreview( videoPressUrl ) || defaultPreview, + isRequestingEmbedPreview: + select( coreStore ).isRequestingEmbedPreview( videoPressUrl ) || false, + }; + }, + [ videoPressUrl ] + ); +}; diff --git a/projects/packages/videopress/src/client/components/timestamp-control/index.tsx b/projects/packages/videopress/src/client/components/timestamp-control/index.tsx new file mode 100644 index 0000000000000..336846d16d6cc --- /dev/null +++ b/projects/packages/videopress/src/client/components/timestamp-control/index.tsx @@ -0,0 +1,204 @@ +/** + * External dependencies + */ +// eslint-disable-next-line wpcalypso/no-unsafe-wp-apis +import { __experimentalNumberControl as NumberControl, RangeControl } from '@wordpress/components'; +import { useDebounce } from '@wordpress/compose'; +import { useCallback } from '@wordpress/element'; +import classnames from 'classnames'; +/** + * Internal dependencies + */ +import styles from './style.module.scss'; +/** + * Types + */ +import { TimestampInputProps, TimestampControlProps } from './types'; +import type React from 'react'; + +const TimeDivider = (): React.ReactElement => { + return :; +}; + +const CHANGE = 'CHANGE'; +const COMMIT = 'COMMIT'; +const PRESS_DOWN = 'PRESS_DOWN'; +const PRESS_UP = 'PRESS_UP'; + +const buildPadInputStateReducer = ( pad: number ) => { + return ( state, action ) => { + const nextState = { ...state }; + if ( + action.type === COMMIT || + action.type === PRESS_UP || + action.type === PRESS_DOWN || + action.type === CHANGE + ) { + if ( nextState.value !== undefined ) { + nextState.value = nextState.value.toString().padStart( pad, '0' ); + } + } + return nextState; + }; +}; + +/** + * Return the time data based on the given value. + * + * @param {number} value - The value to be converted. + * @returns {object} The time data. + */ +function getTimeDataByValue( value ) { + const valueIsNaN = isNaN( value ); + + return { + hh: valueIsNaN ? 0 : Math.floor( ( value / ( 1000 * 60 * 60 ) ) % 24 ), + mm: valueIsNaN ? 0 : Math.floor( ( value / ( 1000 * 60 ) ) % 60 ), + ss: valueIsNaN ? 0 : Math.floor( ( value / 1000 ) % 60 ), + }; +} + +export const TimestampInput = ( { + value, + max, + onChange, +}: TimestampInputProps ): React.ReactElement => { + const time = { + value: getTimeDataByValue( value ), + }; + + // Check whether it should add hours input. + const hasHours = Math.floor( ( max / ( 1000 * 60 * 60 ) ) % 24 ); + + const computeTimeValue = ( unit: string ) => ( newValue: number ) => { + if ( typeof newValue === 'string' && ! isNaN( parseInt( newValue, 10 ) ) ) { + newValue = parseInt( newValue, 10 ); + } + + // Check if the newValue is valid + if ( + ( unit === 'hh' && newValue > 99 ) || + ( ( unit === 'mm' || unit === 'ss' ) && newValue > 59 ) + ) { + return; + } + + // Last check. If the newValue is not a number, bail out. + if ( typeof newValue === 'string' ) { + return; + } + + // Update time object data. + time.value = { ...getTimeDataByValue( value ), [ unit ]: newValue }; + + // Call onChange callback. + onChange?.( ( time.value.hh * 3600 + time.value.mm * 60 + time.value.ss ) * 1000 ); + }; + + return ( +
0, + } ) } + > + { hasHours > 0 && ( + <> + + + + ) } + + + + + + +
+ ); +}; + +/** + * TimestampControl component + * + * @param {TimestampControlProps} props - Component props. + * @returns {React.ReactElement} TimestampControl react component. + */ +export const TimestampControl = ( { + max, + value, + onChange, + onDebounceChange, + wait = 1000, +}: TimestampControlProps ): React.ReactElement => { + const debouncedOnChangeHandler = onDebounceChange ? useDebounce( onDebounceChange, wait ) : null; + + const onChangeHandler = useCallback( + ( newValue: number ) => { + debouncedOnChangeHandler && debouncedOnChangeHandler( newValue ); + onChange( newValue ); + }, + [ onChange, debouncedOnChangeHandler ] + ); + + return ( +
+ + + +
+ ); +}; + +export default TimestampControl; diff --git a/projects/packages/videopress/src/client/components/timestamp-control/stories/TimestampControl.mdx b/projects/packages/videopress/src/client/components/timestamp-control/stories/TimestampControl.mdx new file mode 100644 index 0000000000000..bf7e659dd7940 --- /dev/null +++ b/projects/packages/videopress/src/client/components/timestamp-control/stories/TimestampControl.mdx @@ -0,0 +1,50 @@ +import { Meta, Story, Canvas } from '@storybook/addon-docs'; +import TimestampControl from '../index'; + + + +# Timestamp Control + +React component to set a timestamp value. + + + + + +## TimestampControl API + +### value + +- type `number` + +The timestamp value in milliseconds + +### onChange + +- type: `Function` + +Use this property to pass a callback function, where the API provides the selected time in milliseconds. + +### onDebounceChange + +- type: `Function` + +Similar to `onChange` property, but the call is debouncing in time according to the `wait` property value. + +### wait + +- type: `Number` + +Time, in milliseconds, that the `onDebounceChange` function will be debounced. + +### + +### max + +- type: `number` + +Maximum time value, in milliseconds, expected by the component. +Also, if it's bigger than one hour, the hour input will be rendered into the Timestamp Input component. diff --git a/projects/packages/videopress/src/client/components/timestamp-control/stories/index.tsx b/projects/packages/videopress/src/client/components/timestamp-control/stories/index.tsx new file mode 100644 index 0000000000000..9b153a3a15505 --- /dev/null +++ b/projects/packages/videopress/src/client/components/timestamp-control/stories/index.tsx @@ -0,0 +1,52 @@ +/** + * External dependencies + */ +import { useState } from 'react'; +/** + * Internal dependencies + */ +import TimestampControl from '..'; +import Doc from './TimestampControl.mdx'; +/** + * Types + */ +import type { ComponentStory, ComponentMeta } from '@storybook/react'; + +export default { + title: 'Packages/VideoPress/Timestamp Control', + component: TimestampControl, + parameters: { + docs: { + page: Doc, + }, + }, +} as ComponentMeta< typeof TimestampControl >; + +const Template: ComponentStory< typeof TimestampControl > = args => { + const [ time, setTime ] = useState( args.value ); + return ( + { + setTime( newTime ); + args?.onChange( newTime ); + } } + /> + ); +}; + +export const _default = Template.bind( {} ); +_default.args = { + max: 3600 * 1000 * 2, // 2 hours + value: 236 * 1000, // 3:56 + wait: 100, + onChange: ( newTime: number ) => { + console.log( { newTime } ); // eslint-disable-line no-console + }, + onDebounceChange: ( newDebouncedTime: number ) => { + console.log( { newDebouncedTime } ); // eslint-disable-line no-console + }, +}; + +_default.storyName = 'Timestamp Control'; diff --git a/projects/packages/videopress/src/client/components/timestamp-control/style.module.scss b/projects/packages/videopress/src/client/components/timestamp-control/style.module.scss new file mode 100644 index 0000000000000..9a8c2fe122f51 --- /dev/null +++ b/projects/packages/videopress/src/client/components/timestamp-control/style.module.scss @@ -0,0 +1,43 @@ +.timestamp-input-wrapper { + display: flex; + align-items: center; + border-color: #949494; + border-style: solid; + justify-content: space-around; + border-width: 1px; + max-width: 58px; + padding: 0px; + + &.has-hours { + max-width: 83px; + } + + .timestamp-control-input { + width: 24px; + } + + :global { + .components-input-control__input { + padding-left: 0 !important; + padding-right: 0 !important; + text-align: center; + } + .components-input-control__backdrop { + border-style: none !important; + } + } +} + +.timestamp-control { + display: flex; + align-items: center; + gap: 8px; + + .timestamp-control-range { + flex-grow: 2; + + :global .components-base-control__field { + margin-bottom: 0; + } + } +} diff --git a/projects/packages/videopress/src/client/components/timestamp-control/types.ts b/projects/packages/videopress/src/client/components/timestamp-control/types.ts new file mode 100644 index 0000000000000..0b30a8c488662 --- /dev/null +++ b/projects/packages/videopress/src/client/components/timestamp-control/types.ts @@ -0,0 +1,10 @@ +export type TimestampInputProps = { + value: number; + max?: number; + onChange: ( ms: number ) => void; +}; + +export type TimestampControlProps = TimestampInputProps & { + wait: number; + onDebounceChange: ( ms: number ) => void; +}; diff --git a/projects/packages/videopress/src/client/lib/url/index.ts b/projects/packages/videopress/src/client/lib/url/index.ts index 7315c4e9b5a91..99046d15a6f01 100644 --- a/projects/packages/videopress/src/client/lib/url/index.ts +++ b/projects/packages/videopress/src/client/lib/url/index.ts @@ -141,3 +141,19 @@ export function buildVideoPressURL( export const removeFileNameExtension = ( name: string ) => { return name.replace( /\.[^/.]+$/, '' ); }; + +/** + * Return the VideoPress video URL + * based on the privacy of the video. + * + * @param {VideoGUID} guid - The VideoPress GUID. + * @param {boolean} isPrivate - Whether the video is private or not. + * @returns {string} VideoPress URL. + */ +export function getVideoUrlBasedOnPrivacy( guid: VideoGUID, isPrivate: boolean ) { + if ( isPrivate ) { + return `https://video.wordpress.com/v/${ guid }`; + } + + return `https://videopress.com/v/${ guid }`; +} diff --git a/projects/packages/waf/CHANGELOG.md b/projects/packages/waf/CHANGELOG.md index adb85bcd44881..5899e8d3ac598 100644 --- a/projects/packages/waf/CHANGELOG.md +++ b/projects/packages/waf/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.10.1] - 2023-03-08 +### Changed +- Minor internal updates. + ## [0.10.0] - 2023-02-28 ### Added - Added support for IP ranges in allow and block lists. [#29131] @@ -159,6 +163,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Core: do not ship .phpcs.dir.xml in production builds. +[0.10.1]: https://github.com/Automattic/jetpack-waf/compare/v0.10.0...v0.10.1 [0.10.0]: https://github.com/Automattic/jetpack-waf/compare/v0.9.3...v0.10.0 [0.9.3]: https://github.com/Automattic/jetpack-waf/compare/v0.9.2...v0.9.3 [0.9.2]: https://github.com/Automattic/jetpack-waf/compare/v0.9.1...v0.9.2 diff --git a/projects/packages/waf/src/class-rest-controller.php b/projects/packages/waf/src/class-rest-controller.php index 15fbce7c9c2b2..38e4d5615f463 100644 --- a/projects/packages/waf/src/class-rest-controller.php +++ b/projects/packages/waf/src/class-rest-controller.php @@ -17,6 +17,8 @@ class REST_Controller { /** * Register REST API endpoints. + * + * @return void */ public static function register_rest_routes() { register_rest_route( @@ -52,29 +54,29 @@ public static function register_rest_routes() { /** * Update rules endpoint + * + * @return WP_REST_Response|WP_Error */ public static function update_rules() { - $success = true; - $message = 'Rules updated succesfully'; - try { Waf_Rules_Manager::generate_automatic_rules(); Waf_Rules_Manager::generate_rules(); - } catch ( \Exception $e ) { - $success = false; - $message = $e->getMessage(); + } catch ( Waf_Exception $e ) { + return $e->get_wp_error(); } return rest_ensure_response( array( - 'success' => $success, - 'message' => $message, + 'success' => true, + 'message' => __( 'Rules updated succesfully', 'jetpack-waf' ), ) ); } /** * WAF Endpoint + * + * @return WP_REST_Response */ public static function waf() { return rest_ensure_response( Waf_Runner::get_config() ); @@ -84,7 +86,8 @@ public static function waf() { * Update WAF Endpoint * * @param WP_REST_Request $request The API request. - * @return WP_REST_Response + * + * @return WP_REST_Response|WP_Error */ public static function update_waf( $request ) { // Automatic Rules Enabled @@ -112,7 +115,11 @@ public static function update_waf( $request ) { update_option( Waf_Runner::SHARE_DATA_OPTION_NAME, (bool) $request[ Waf_Runner::SHARE_DATA_OPTION_NAME ] ); } - Waf_Runner::update_waf(); + try { + Waf_Runner::update_waf(); + } catch ( Waf_Exception $e ) { + return $e->get_wp_error(); + } return self::waf(); } diff --git a/projects/packages/waf/src/class-waf-initializer.php b/projects/packages/waf/src/class-waf-initializer.php index 3f3866506efe7..a119c9be7a41c 100644 --- a/projects/packages/waf/src/class-waf-initializer.php +++ b/projects/packages/waf/src/class-waf-initializer.php @@ -49,33 +49,37 @@ public static function init() { } /** - * On module activation set up waf mode + * Activate the WAF on module activation. * - * @return bool|WP_Error True of the WAF activation was successful, WP_Error otherwise. + * @return bool|WP_Error True if the WAF activation is successful, WP_Error otherwise. */ public static function on_activation() { update_option( Waf_Runner::MODE_OPTION_NAME, 'normal' ); add_option( Waf_Rules_Manager::AUTOMATIC_RULES_ENABLED_OPTION_NAME, false ); - $waf_activated = Waf_Runner::activate(); - if ( is_wp_error( $waf_activated ) ) { - return $waf_activated; - } - try { + Waf_Runner::activate(); ( new Waf_Standalone_Bootstrap() )->generate(); - } catch ( \Exception $e ) { - return new WP_Error( 'waf_activation_failed', $e->getMessage() ); + } catch ( Waf_Exception $e ) { + return $e->get_wp_error(); } return true; } /** - * On module deactivation, unset waf mode + * Deactivate the WAF on module deactivation. + * + * @return bool|WP_Error True if the WAF deactivation is successful, WP_Error otherwise. */ public static function on_deactivation() { - Waf_Runner::deactivate(); + try { + Waf_Runner::deactivate(); + } catch ( Waf_Exception $e ) { + return $e->get_wp_error(); + } + + return true; } /** @@ -131,23 +135,22 @@ public static function check_for_waf_update() { if ( ! method_exists( Waf_Constants::class, 'define_mode' ) ) { try { ( new Waf_Standalone_Bootstrap() )->generate(); - } catch ( \Exception $e ) { - return new WP_Error( 'waf_update_failed', $e->getMessage() ); + } catch ( Waf_Exception $e ) { + return $e->get_wp_error(); } - return true; } Waf_Constants::define_mode(); if ( ! Waf_Runner::is_allowed_mode( JETPACK_WAF_MODE ) ) { - return new WP_Error( 'waf_update_failed', 'Invalid firewall mode.' ); + return new WP_Error( 'waf_mode_invalid', 'Invalid firewall mode.' ); } try { Waf_Rules_Manager::generate_ip_rules(); Waf_Rules_Manager::generate_rules(); ( new Waf_Standalone_Bootstrap() )->generate(); - } catch ( \Exception $e ) { - return new WP_Error( 'waf_update_failed', $e->getMessage() ); + } catch ( Waf_Exception $e ) { + return $e->get_wp_error(); } } diff --git a/projects/packages/waf/src/class-waf-rules-manager.php b/projects/packages/waf/src/class-waf-rules-manager.php index 05fc2bfe1fe21..2c9f890179bf8 100644 --- a/projects/packages/waf/src/class-waf-rules-manager.php +++ b/projects/packages/waf/src/class-waf-rules-manager.php @@ -43,14 +43,14 @@ class Waf_Rules_Manager { */ public static function add_hooks() { // Re-activate the WAF any time an option is added or updated. - add_action( 'add_option_' . self::AUTOMATIC_RULES_ENABLED_OPTION_NAME, array( Waf_Runner::class, 'activate' ), 10, 0 ); - add_action( 'update_option_' . self::AUTOMATIC_RULES_ENABLED_OPTION_NAME, array( Waf_Runner::class, 'activate' ), 10, 0 ); - add_action( 'add_option_' . self::IP_LISTS_ENABLED_OPTION_NAME, array( Waf_Runner::class, 'activate' ), 10, 0 ); - add_action( 'update_option_' . self::IP_LISTS_ENABLED_OPTION_NAME, array( Waf_Runner::class, 'activate' ), 10, 0 ); - add_action( 'add_option_' . self::IP_ALLOW_LIST_OPTION_NAME, array( Waf_Runner::class, 'activate' ), 10, 0 ); - add_action( 'update_option_' . self::IP_ALLOW_LIST_OPTION_NAME, array( Waf_Runner::class, 'activate' ), 10, 0 ); - add_action( 'add_option_' . self::IP_BLOCK_LIST_OPTION_NAME, array( Waf_Runner::class, 'activate' ), 10, 0 ); - add_action( 'update_option_' . self::IP_BLOCK_LIST_OPTION_NAME, array( Waf_Runner::class, 'activate' ), 10, 0 ); + add_action( 'add_option_' . self::AUTOMATIC_RULES_ENABLED_OPTION_NAME, array( static::class, 'reactivate_on_rules_option_change' ), 10, 0 ); + add_action( 'update_option_' . self::AUTOMATIC_RULES_ENABLED_OPTION_NAME, array( static::class, 'reactivate_on_rules_option_change' ), 10, 0 ); + add_action( 'add_option_' . self::IP_LISTS_ENABLED_OPTION_NAME, array( static::class, 'reactivate_on_rules_option_change' ), 10, 0 ); + add_action( 'update_option_' . self::IP_LISTS_ENABLED_OPTION_NAME, array( static::class, 'reactivate_on_rules_option_change' ), 10, 0 ); + add_action( 'add_option_' . self::IP_ALLOW_LIST_OPTION_NAME, array( static::class, 'reactivate_on_rules_option_change' ), 10, 0 ); + add_action( 'update_option_' . self::IP_ALLOW_LIST_OPTION_NAME, array( static::class, 'reactivate_on_rules_option_change' ), 10, 0 ); + add_action( 'add_option_' . self::IP_BLOCK_LIST_OPTION_NAME, array( static::class, 'reactivate_on_rules_option_change' ), 10, 0 ); + add_action( 'update_option_' . self::IP_BLOCK_LIST_OPTION_NAME, array( static::class, 'reactivate_on_rules_option_change' ), 10, 0 ); // Register the cron job. add_action( 'jetpack_waf_rules_update_cron', array( static::class, 'update_rules_cron' ) ); } @@ -58,12 +58,14 @@ public static function add_hooks() { /** * Schedule the cron job to update the WAF rules. * - * @return void + * @return bool|WP_Error True if the event is scheduled, WP_Error on failure. */ public static function schedule_rules_cron() { if ( ! wp_next_scheduled( 'jetpack_waf_rules_update_cron' ) ) { - wp_schedule_event( time(), 'twicedaily', 'jetpack_waf_rules_update_cron' ); + return wp_schedule_event( time(), 'twicedaily', 'jetpack_waf_rules_update_cron', array(), true ); } + + return true; } /** @@ -74,59 +76,72 @@ public static function schedule_rules_cron() { public static function update_rules_cron() { Waf_Constants::define_mode(); if ( ! Waf_Runner::is_allowed_mode( JETPACK_WAF_MODE ) ) { - return new WP_Error( 'waf_cron_update_failed', 'Invalid firewall mode.' ); + return new WP_Error( 'waf_invalid_mode', 'Invalid firewall mode.' ); } try { self::generate_automatic_rules(); self::generate_ip_rules(); self::generate_rules(); - } catch ( \Exception $e ) { - return new WP_Error( 'waf_cron_update_failed', $e->getMessage() ); + } catch ( Waf_Exception $e ) { + return $e->get_wp_error(); } update_option( self::RULE_LAST_UPDATED_OPTION_NAME, time() ); return true; } + /** + * Re-activate the WAF any time an option is added or updated. + * + * @return bool|WP_Error True if re-activation is successful, WP_Error on failure. + */ + public static function reactivate_on_rules_option_change() { + try { + Waf_Runner::activate(); + } catch ( Waf_Exception $e ) { + return $e->get_wp_error(); + } + + return true; + } + /** * Updates the rule set if rules version has changed * - * @return bool|WP_Error True if rules update is successful, WP_Error on failure. + * @throws Waf_Exception If the firewall mode is invalid. + * @throws Waf_Exception If the rules update fails. + * + * @return void */ public static function update_rules_if_changed() { Waf_Constants::define_mode(); if ( ! Waf_Runner::is_allowed_mode( JETPACK_WAF_MODE ) ) { - return new WP_Error( 'waf_update_failed', 'Invalid firewall mode.' ); + throw new Waf_Exception( 'Invalid firewall mode.' ); } $version = get_option( self::VERSION_OPTION_NAME ); if ( self::RULES_VERSION !== $version ) { - update_option( self::VERSION_OPTION_NAME, self::RULES_VERSION ); + self::generate_automatic_rules(); + self::generate_ip_rules(); + self::generate_rules(); - try { - self::generate_automatic_rules(); - self::generate_ip_rules(); - self::generate_rules(); - } catch ( \Exception $e ) { - return new WP_Error( 'waf_update_failed', $e->getMessage() ); - } + update_option( self::VERSION_OPTION_NAME, self::RULES_VERSION ); } - - return true; } /** * Retrieve rules from the API * - * @throws \Exception If site is not registered. - * @throws \Exception If API did not respond 200. - * @throws \Exception If data is missing from response. + * @throws Waf_Exception If site is not registered. + * @throws Rules_API_Exception If API did not respond 200. + * @throws Rules_API_Exception If data is missing from response. + * * @return array */ public static function get_rules_from_api() { $blog_id = Jetpack_Options::get_option( 'id' ); if ( ! $blog_id ) { - throw new \Exception( 'Site is not registered' ); + throw new Waf_Exception( 'Site is not registered' ); } $response = Client::wpcom_json_api_request_as_blog( @@ -140,14 +155,14 @@ public static function get_rules_from_api() { $response_code = wp_remote_retrieve_response_code( $response ); if ( 200 !== $response_code ) { - throw new \Exception( 'API connection failed.', (int) $response_code ); + throw new Rules_API_Exception( 'API connection failed.', (int) $response_code ); } $rules_json = wp_remote_retrieve_body( $response ); $rules = json_decode( $rules_json, true ); if ( empty( $rules['data'] ) ) { - throw new \Exception( 'Data missing from response.' ); + throw new Rules_API_Exception( 'Data missing from response.' ); } return $rules['data']; @@ -158,6 +173,7 @@ public static function get_rules_from_api() { * * @param string $required_file The file to check if exists and require. * @param string $return_code The PHP code to execute if the file require returns true. Defaults to 'return;'. + * * @return string The wrapped require statement. */ private static function wrap_require( $required_file, $return_code = 'return;' ) { @@ -167,17 +183,15 @@ private static function wrap_require( $required_file, $return_code = 'return;' ) /** * Generates the rules.php script * - * @throws \Exception If file writing fails. + * @global \WP_Filesystem_Base $wp_filesystem WordPress filesystem abstraction. + * + * @throws File_System_Exception If file writing fails initializing rule files. + * @throws File_System_Exception If file writing fails writing to the rules entrypoint file. + * * @return void */ public static function generate_rules() { - /** - * WordPress filesystem abstraction. - * - * @var \WP_Filesystem_Base $wp_filesystem - */ global $wp_filesystem; - Waf_Runner::initialize_filesystem(); $rules = "is_file( $rule_file ) ) { if ( ! $wp_filesystem->put_contents( $rule_file, "put_contents( $entrypoint_file_path, $rules ) ) { - throw new \Exception( 'Failed writing rules file to: ' . $entrypoint_file_path ); + throw new File_System_Exception( 'Failed writing rules file to: ' . $entrypoint_file_path ); } } /** * Generates the automatic-rules.php script * - * @throws \Exception If rules cannot be generated and saved. + * @global \WP_Filesystem_Base $wp_filesystem WordPress filesystem abstraction. + * + * @throws Waf_Exception If rules cannot be fetched from the API. + * @throws File_System_Exception If file writing fails. + * * @return void */ public static function generate_automatic_rules() { - /** - * WordPress filesystem abstraction. - * - * @var \WP_Filesystem_Base $wp_filesystem - */ global $wp_filesystem; - Waf_Runner::initialize_filesystem(); $automatic_rules_file_path = Waf_Runner::get_waf_file_path( self::AUTOMATIC_RULES_FILE ); @@ -241,10 +253,10 @@ public static function generate_automatic_rules() { try { $rules = self::get_rules_from_api(); - } catch ( \Exception $exception ) { + } catch ( Waf_Exception $e ) { // Do not throw API exceptions for users who do not have access - if ( 401 !== $exception->getCode() ) { - throw $exception; + if ( 401 !== $e->getCode() ) { + throw $e; } } @@ -254,7 +266,7 @@ public static function generate_automatic_rules() { } if ( ! $wp_filesystem->put_contents( $automatic_rules_file_path, $rules ) ) { - throw new \Exception( 'Failed writing automatic rules file to: ' . $automatic_rules_file_path ); + throw new File_System_Exception( 'Failed writing automatic rules file to: ' . $automatic_rules_file_path ); } update_option( self::AUTOMATIC_RULES_LAST_UPDATED_OPTION_NAME, time() ); @@ -263,18 +275,15 @@ public static function generate_automatic_rules() { /** * Generates the rules.php script * - * @throws \Exception If filesystem is not available. - * @throws \Exception If file writing fails. + * @global \WP_Filesystem_Base $wp_filesystem WordPress filesystem abstraction. + * + * @throws File_System_Exception If writing to IP allow list file fails. + * @throws File_System_Exception If writing to IP block list file fails. + * * @return void */ public static function generate_ip_rules() { - /** - * WordPress filesystem abstraction. - * - * @var \WP_Filesystem_Base $wp_filesystem - */ global $wp_filesystem; - Waf_Runner::initialize_filesystem(); $allow_ip_file_path = Waf_Runner::get_waf_file_path( self::IP_ALLOW_RULES_FILE ); @@ -298,7 +307,7 @@ public static function generate_ip_rules() { $allow_rules_content .= 'return $waf->is_ip_in_array( $waf_allow_list );' . "\n"; if ( ! $wp_filesystem->put_contents( $allow_ip_file_path, "is_ip_in_array( $waf_block_list );' . "\n"; if ( ! $wp_filesystem->put_contents( $block_ip_file_path, "activate( self::WAF_MODULE_NAME, false, false ); @@ -136,6 +139,8 @@ public static function enable() { /** * Disabled the WAF module on the site. + * + * @return bool */ public static function disable() { return ( new Modules() )->deactivate( self::WAF_MODULE_NAME ); @@ -227,7 +232,7 @@ public static function run() { // phpcs:ignore include $rules_file_path; } -} catch ( \Exception $err ) { // phpcs:ignore + } catch ( \Exception $err ) { // phpcs:ignore // Intentionally doing nothing. } @@ -253,8 +258,9 @@ public static function errorHandler( $code, $message, $file, $line ) { // phpcs: /** * Initializes the WP filesystem and WAF directory structure. * + * @throws File_System_Exception If filesystem is unavailable. + * * @return void - * @throws \Exception If filesystem is unavailable. */ public static function initialize_filesystem() { if ( ! function_exists( '\\WP_Filesystem' ) ) { @@ -262,7 +268,7 @@ public static function initialize_filesystem() { } if ( ! \WP_Filesystem() ) { - throw new \Exception( 'No filesystem available.' ); + throw new File_System_Exception( 'No filesystem available.' ); } self::initialize_waf_directory(); @@ -271,12 +277,15 @@ public static function initialize_filesystem() { /** * Activates the WAF by generating the rules script and setting the version * - * @return bool|WP_Error True if the WAF was activated sucessfully, WP_Error if not. + * @throws Waf_Exception If the firewall mode is invalid. + * @throws Waf_Exception If the activation fails. + * + * @return void */ public static function activate() { Waf_Constants::define_mode(); if ( ! self::is_allowed_mode( JETPACK_WAF_MODE ) ) { - new WP_Error( 'waf_activation_failed', 'Invalid firewall mode.' ); + throw new Waf_Exception( 'Invalid firewall mode.' ); } $version = get_option( Waf_Rules_Manager::VERSION_OPTION_NAME ); @@ -286,24 +295,22 @@ public static function activate() { add_option( self::SHARE_DATA_OPTION_NAME, true ); - try { - self::initialize_filesystem(); - Waf_Rules_Manager::generate_automatic_rules(); - Waf_Rules_Manager::generate_ip_rules(); - self::create_blocklog_table(); - Waf_Rules_Manager::generate_rules(); - } catch ( \Exception $e ) { - return new WP_Error( 'waf_activation_failed', $e->getMessage() ); - } + self::initialize_filesystem(); - return true; + Waf_Rules_Manager::generate_automatic_rules(); + Waf_Rules_Manager::generate_ip_rules(); + Waf_Rules_Manager::generate_rules(); + + self::create_blocklog_table(); } /** * Ensures that the waf directory is created. * + * @throws File_System_Exception If filesystem is unavailable. + * @throws File_System_Exception If creating the directory fails. + * * @return void - * @throws \Exception In case there's a problem when creating the directory. */ public static function initialize_waf_directory() { WP_Filesystem(); @@ -311,12 +318,12 @@ public static function initialize_waf_directory() { global $wp_filesystem; if ( ! $wp_filesystem ) { - throw new \Exception( 'Can not work without the file system being initialized.' ); + throw new File_System_Exception( 'Can not work without the file system being initialized.' ); } if ( ! $wp_filesystem->is_dir( JETPACK_WAF_DIR ) ) { if ( ! $wp_filesystem->mkdir( JETPACK_WAF_DIR ) ) { - throw new \Exception( 'Failed creating WAF file directory: ' . JETPACK_WAF_DIR ); + throw new File_System_Exception( 'Failed creating WAF file directory: ' . JETPACK_WAF_DIR ); } } } @@ -348,15 +355,15 @@ public static function create_blocklog_table() { /** * Deactivates the WAF by deleting the relevant options and emptying rules file. * + * @throws File_System_Exception If file writing fails. + * * @return void - * @throws \Exception If file writing fails. */ public static function deactivate() { delete_option( self::MODE_OPTION_NAME ); delete_option( Waf_Rules_Manager::VERSION_OPTION_NAME ); global $wp_filesystem; - self::initialize_filesystem(); // If the rules file doesn't exist, there's nothing else to do. @@ -366,24 +373,21 @@ public static function deactivate() { // Empty the rules entrypoint file. if ( ! $wp_filesystem->put_contents( self::get_waf_file_path( Waf_Rules_Manager::RULES_ENTRYPOINT_FILE ), "generate(); - } catch ( \Exception $e ) { - return new WP_Error( 'waf_update_failed', $e->getMessage() ); - } - - return true; + ( new Waf_Standalone_Bootstrap() )->generate(); } /** @@ -404,7 +408,7 @@ public static function automatic_rules_available() { try { self::initialize_filesystem(); - } catch ( \Exception $e ) { + } catch ( Waf_Exception $e ) { return false; } diff --git a/projects/packages/waf/src/class-waf-standalone-bootstrap.php b/projects/packages/waf/src/class-waf-standalone-bootstrap.php index 116ebcc734e4b..597e31688fd17 100644 --- a/projects/packages/waf/src/class-waf-standalone-bootstrap.php +++ b/projects/packages/waf/src/class-waf-standalone-bootstrap.php @@ -8,7 +8,6 @@ namespace Automattic\Jetpack\Waf; use Composer\InstalledVersions; -use Exception; /** * Handles the bootstrap. @@ -17,6 +16,8 @@ class Waf_Standalone_Bootstrap { /** * Ensures that constants are initialized if this class is used. + * + * @return void */ public function __construct() { $this->guard_against_missing_abspath(); @@ -26,13 +27,14 @@ public function __construct() { /** * Ensures that this class is not used unless we are in the right context. * + * @throws Waf_Exception If we are outside of WordPress. + * * @return void - * @throws Exception If we are outside of WordPress. */ private function guard_against_missing_abspath() { if ( ! defined( 'ABSPATH' ) ) { - throw new Exception( 'Cannot generate the WAF bootstrap if we are not running in WordPress context.' ); + throw new Waf_Exception( 'Cannot generate the WAF bootstrap if we are not running in WordPress context.' ); } } @@ -65,8 +67,9 @@ protected function initialize_filesystem() { /** * Finds the path to the autoloader, which can then be used to require the autoloader in the generated boostrap file. * + * @throws Waf_Exception In case the autoloader file can not be found. + * * @return string|null - * @throws Exception In case the autoloader file can not be found. */ private function locate_autoloader_file() { global $jetpack_autoloader_loader; @@ -102,7 +105,7 @@ private function locate_autoloader_file() { // Check that the determined file actually exists. if ( ! file_exists( $autoload_file ) ) { - throw new Exception( 'Can not find autoloader, and the WAF standalone boostrap will not work without it.' ); + throw new Waf_Exception( 'Can not find autoloader, and the WAF standalone boostrap will not work without it.' ); } return $autoload_file; @@ -120,8 +123,11 @@ public function get_bootstrap_file_path() { /** * Generates the bootstrap file. * + * @throws File_System_Exception If the filesystem is not available. + * @throws File_System_Exception If the WAF directory can not be created. + * @throws File_System_Exception If the bootstrap file can not be created. + * * @return string Absolute path to the bootstrap file. - * @throws Exception In case the file can not be written. */ public function generate() { @@ -129,9 +135,11 @@ public function generate() { global $wp_filesystem; if ( ! $wp_filesystem ) { - throw new Exception( 'Can not work without the file system being initialized.' ); + throw new File_System_Exception( 'Can not work without the file system being initialized.' ); } + $autoloader_file = $this->locate_autoloader_file(); + $bootstrap_file = $this->get_bootstrap_file_path(); $mode_option = get_option( Waf_Runner::MODE_OPTION_NAME, false ); $share_data_option = get_option( Waf_Runner::SHARE_DATA_OPTION_NAME, false ); @@ -144,18 +152,18 @@ public function generate() { . sprintf( "define( 'JETPACK_WAF_SHARE_DATA', %s );\n", var_export( $share_data_option, true ) ) . sprintf( "define( 'JETPACK_WAF_DIR', %s );\n", var_export( JETPACK_WAF_DIR, true ) ) . sprintf( "define( 'JETPACK_WAF_WPCONFIG', %s );\n", var_export( JETPACK_WAF_WPCONFIG, true ) ) - . 'require_once ' . var_export( $this->locate_autoloader_file(), true ) . ";\n" + . 'require_once ' . var_export( $autoloader_file, true ) . ";\n" . "Automattic\Jetpack\Waf\Waf_Runner::initialize();\n"; // phpcs:enable if ( ! $wp_filesystem->is_dir( JETPACK_WAF_DIR ) ) { if ( ! $wp_filesystem->mkdir( JETPACK_WAF_DIR ) ) { - throw new Exception( 'Failed creating WAF standalone bootstrap file directory: ' . JETPACK_WAF_DIR ); + throw new File_System_Exception( 'Failed creating WAF standalone bootstrap file directory: ' . JETPACK_WAF_DIR ); } } if ( ! $wp_filesystem->put_contents( $bootstrap_file, $code ) ) { - throw new Exception( 'Failed writing WAF standalone bootstrap file to: ' . $bootstrap_file ); + throw new File_System_Exception( 'Failed writing WAF standalone bootstrap file to: ' . $bootstrap_file ); } return $bootstrap_file; diff --git a/projects/packages/waf/src/exceptions/class-file-system-exception.php b/projects/packages/waf/src/exceptions/class-file-system-exception.php new file mode 100644 index 0000000000000..ba59b98628d86 --- /dev/null +++ b/projects/packages/waf/src/exceptions/class-file-system-exception.php @@ -0,0 +1,24 @@ +getMessage() ); + } + +} diff --git a/projects/packages/waf/tests/php/integration/test-waf-activation.php b/projects/packages/waf/tests/php/integration/test-waf-activation.php index d87940c27522a..a88a02ef57930 100644 --- a/projects/packages/waf/tests/php/integration/test-waf-activation.php +++ b/projects/packages/waf/tests/php/integration/test-waf-activation.php @@ -49,6 +49,30 @@ public function return_sample_response() { ); } + /** + * Return a 503 wpcom rules response. + * + * @return array + */ + public function return_503_response() { + return array( + 'body' => '', + 'response' => array( + 'code' => 503, + 'message' => '', + ), + ); + } + + /** + * Return an invalid filesystem method. + * + * @return string + */ + public function return_invalid_filesystem_method() { + return 'Code is poetry.'; + } + /** * Test WAF activation. */ @@ -80,4 +104,79 @@ public function testActivation() { remove_filter( 'pre_http_request', array( $this, 'return_sample_response' ) ); } + /** + * Test WAF deactivation. + */ + public function testDeactivation() { + $deactivated = Waf_Initializer::on_deactivation(); + + // Ensure the WAF was deactivated successfully. + $this->assertTrue( $deactivated ); + + // Ensure the options were deleted. + $this->assertSame( get_option( Waf_Runner::SHARE_DATA_OPTION_NAME ), false ); + $this->assertSame( get_option( Waf_Runner::MODE_OPTION_NAME ), false ); + + // Ensure the rules entrypoint file was emptied. + $this->assertSame( file_get_contents( Waf_Runner::get_waf_file_path( Waf_Rules_Manager::RULES_ENTRYPOINT_FILE ) ), "assertTrue( is_wp_error( $activated ) ); + $this->assertSame( 'file_system_error', $activated->get_error_code() ); + + // Clean up. + remove_filter( 'pre_http_request', array( $this, 'return_sample_response' ) ); + remove_filter( 'filesystem_method', array( $this, 'return_invalid_filesystem_method' ) ); + } + + /** + * Test WAF deactivation when the filesystem is unavailable. + */ + public function testDeactivationWhenFilesystemUnavailable() { + // Break the filesystem. + add_filter( 'filesystem_method', array( $this, 'return_invalid_filesystem_method' ) ); + + // Deactivate the firewall. + $deactivated = Waf_Initializer::on_deactivation(); + + // Validate the error. + $this->assertTrue( is_wp_error( $deactivated ) ); + $this->assertSame( 'file_system_error', $deactivated->get_error_code() ); + + // Clean up. + remove_filter( 'filesystem_method', array( $this, 'return_invalid_filesystem_method' ) ); + } + + /** + * Test WAF activation when the rules API request fails. + */ + public function testActivationReturnsWpErrorWhenRulesApiRequestFails() { + // Mock the WPCOM request for retrieving the automatic rules. + add_filter( 'pre_http_request', array( $this, 'return_503_response' ) ); + + // Initialize the firewall. + $activated = Waf_Initializer::on_activation(); + + // Validate the error. + $this->assertTrue( is_wp_error( $activated ) ); + $this->assertSame( 'rules_api_error', $activated->get_error_code() ); + + // Clean up. + remove_filter( 'pre_http_request', array( $this, 'return_503_response' ) ); + } + } diff --git a/projects/packages/waf/tests/php/integration/test-waf-rest-api.php b/projects/packages/waf/tests/php/integration/test-waf-rest-api.php new file mode 100644 index 0000000000000..15a2dbce5f367 --- /dev/null +++ b/projects/packages/waf/tests/php/integration/test-waf-rest-api.php @@ -0,0 +1,179 @@ + " wp_json_encode( $sample_response ), + 'response' => array( + 'code' => 200, + 'message' => '', + ), + ); + } + + /** + * Return a 503 wpcom rules response. + * + * @return array + */ + public function return_503_response() { + return array( + 'body' => '', + 'response' => array( + 'code' => 503, + 'message' => '', + ), + ); + } + + /** + * Return an invalid filesystem method. + * + * @return string + */ + public function return_invalid_filesystem_method() { + return 'Code is poetry.'; + } + + /** + * Test /jetpack/v4/waf/update-rules. + */ + public function testUpdateRulesEndpoint() { + // Mock the WPCOM request for retrieving the automatic rules. + add_filter( 'pre_http_request', array( $this, 'return_sample_response' ) ); + + // Call /jetpack/v4/waf/update-rules. + $response = REST_Controller::update_rules(); + + // Validate the response. + $this->assertTrue( $response->data['success'] ); + + // Clean up. + remove_filter( 'pre_http_request', array( $this, 'return_sample_response' ) ); + } + + /** + * Test /jetpack/v4/waf/update-rules when the filesystem is unavailable. + */ + public function testUpdateRulesEndpointFilesystemUnavailable() { + // Mock the WPCOM request for retrieving the automatic rules. + add_filter( 'pre_http_request', array( $this, 'return_sample_response' ) ); + + // Break the filesystem. + add_filter( 'filesystem_method', array( $this, 'return_invalid_filesystem_method' ) ); + + // Call /jetpack/v4/waf/update-rules. + $response = REST_Controller::update_rules(); + + // Validate the response. + $this->assertTrue( is_wp_error( $response ) ); + $this->assertSame( 'file_system_error', $response->get_error_code() ); + + // Clean up. + remove_filter( 'pre_http_request', array( $this, 'return_sample_response' ) ); + remove_filter( 'filesystem_method', array( $this, 'return_invalid_filesystem_method' ) ); + } + + /** + * Test /jetpack/v4/waf/update-rules when the WPCOM request fails. + */ + public function testUpdateRulesEndpointWpcomRequestFails() { + // Mock the WPCOM request for retrieving the automatic rules. + add_filter( 'pre_http_request', array( $this, 'return_503_response' ) ); + + // Call /jetpack/v4/waf/update-rules. + $response = REST_Controller::update_rules(); + + // Validate the response. + $this->assertTrue( is_wp_error( $response ) ); + $this->assertSame( 'rules_api_error', $response->get_error_code() ); + + // Clean up. + remove_filter( 'pre_http_request', array( $this, 'return_503_response' ) ); + } + + /** + * Test /jetpack/v4/waf POST. + */ + public function testUpdateWaf() { + // Mock the request. + $request = new WP_REST_Request( 'POST', '/jetpack/v4/waf' ); + $request->set_header( 'content-type', 'application/json' ); + $request->set_body( + wp_json_encode( + array( + 'jetpack_waf_automatic_rules_enabled' => true, + ) + ) + ); + + // Call the endpoint. + $response = REST_Controller::update_waf( $request ); + + // Validate the response. + $this->assertFalse( is_wp_error( $response ) ); + } + + /** + * Test /jetpack/v4/waf POST when filesystem is unavailable. + */ + public function testUpdateWafFilesystemUnavailable() { + // Break the filesystem. + add_filter( 'filesystem_method', array( $this, 'return_invalid_filesystem_method' ) ); + + // Mock the request. + $request = new WP_REST_Request( 'POST', '/jetpack/v4/waf' ); + $request->set_header( 'content-type', 'application/json' ); + $request->set_body( + wp_json_encode( + array( + 'jetpack_waf_automatic_rules_enabled' => true, + ) + ) + ); + + // Call the endpoint. + $response = REST_Controller::update_waf( $request ); + + // Validate the response. + $this->assertTrue( is_wp_error( $response ) ); + $this->assertSame( 'file_system_error', $response->get_error_code() ); + + // Clean up. + remove_filter( 'filesystem_method', array( $this, 'return_invalid_filesystem_method' ) ); + } +} diff --git a/projects/packages/wordads/CHANGELOG.md b/projects/packages/wordads/CHANGELOG.md index 5cd75c1f13a66..ca60418fc231e 100644 --- a/projects/packages/wordads/CHANGELOG.md +++ b/projects/packages/wordads/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.34] - 2023-03-08 +### Changed +- Updated package dependencies. [#29216] + ## [0.2.33] - 2023-02-28 ### Changed - Updated package dependencies. @@ -165,6 +169,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - PHPCS: Fix `WordPress.Security.ValidatedSanitizedInput` - Updated package dependencies. +[0.2.34]: https://github.com/Automattic/jetpack-wordads/compare/v0.2.33...v0.2.34 [0.2.33]: https://github.com/Automattic/jetpack-wordads/compare/v0.2.32...v0.2.33 [0.2.32]: https://github.com/Automattic/jetpack-wordads/compare/v0.2.31...v0.2.32 [0.2.31]: https://github.com/Automattic/jetpack-wordads/compare/v0.2.30...v0.2.31 diff --git a/projects/packages/wordads/changelog/renovate-concurrently-7.x b/projects/packages/wordads/changelog/renovate-concurrently-7.x deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/wordads/changelog/renovate-concurrently-7.x +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/wordads/changelog/renovate-jest-monorepo b/projects/packages/wordads/changelog/renovate-jest-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/wordads/changelog/renovate-jest-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/wordads/changelog/renovate-wordpress-monorepo b/projects/packages/wordads/changelog/renovate-wordpress-monorepo deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/packages/wordads/changelog/renovate-wordpress-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/packages/wordads/package.json b/projects/packages/wordads/package.json index a5463d517444d..6385d6abb5629 100644 --- a/projects/packages/wordads/package.json +++ b/projects/packages/wordads/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-wordads", - "version": "0.2.34-alpha", + "version": "0.2.34", "description": "Earn income by allowing Jetpack to display high quality ads.", "main": "main.js", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/wordads/#readme", diff --git a/projects/packages/wordads/src/class-package.php b/projects/packages/wordads/src/class-package.php index 5c89072fc5b7f..9a2303e11cb0c 100644 --- a/projects/packages/wordads/src/class-package.php +++ b/projects/packages/wordads/src/class-package.php @@ -11,7 +11,7 @@ * WordAds package general information */ class Package { - const VERSION = '0.2.34-alpha'; + const VERSION = '0.2.34'; const SLUG = 'wordads'; /** diff --git a/projects/plugins/backup/CHANGELOG.md b/projects/plugins/backup/CHANGELOG.md index 9661f74746d08..252953f3373d0 100644 --- a/projects/plugins/backup/CHANGELOG.md +++ b/projects/plugins/backup/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## 1.5-beta - 2023-02-28 +## 1.5 - 2023-03-07 ### Changed - Backup: Update description headline, add video [#28890] - Updated package dependencies. [#28910] diff --git a/projects/plugins/debug-helper/changelog/renovate-yoast-phpunit-polyfills-1.x b/projects/plugins/backup/changelog/add-zendesk-chat-module similarity index 100% rename from projects/plugins/debug-helper/changelog/renovate-yoast-phpunit-polyfills-1.x rename to projects/plugins/backup/changelog/add-zendesk-chat-module diff --git a/projects/plugins/mu-wpcom-plugin/changelog/add-launchpad-rest-api-endpoint b/projects/plugins/backup/changelog/add-zendesk-chat-module#2 similarity index 100% rename from projects/plugins/mu-wpcom-plugin/changelog/add-launchpad-rest-api-endpoint rename to projects/plugins/backup/changelog/add-zendesk-chat-module#2 diff --git a/projects/plugins/backup/changelog/update-backport-11.9-social-backup b/projects/plugins/backup/changelog/update-backport-11.9-social-backup new file mode 100644 index 0000000000000..56edc9402397f --- /dev/null +++ b/projects/plugins/backup/changelog/update-backport-11.9-social-backup @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Backport release changelog and stable tag + + diff --git a/projects/plugins/backup/changelog/update-wp-tested-up-to b/projects/plugins/backup/changelog/update-wp-tested-up-to new file mode 100644 index 0000000000000..ad53b760f90b2 --- /dev/null +++ b/projects/plugins/backup/changelog/update-wp-tested-up-to @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +General: indicate full compatibility with the latest version of WordPress, 6.2. diff --git a/projects/plugins/backup/composer.lock b/projects/plugins/backup/composer.lock index 2e9bd649abce5..ee676ab0809d0 100644 --- a/projects/plugins/backup/composer.lock +++ b/projects/plugins/backup/composer.lock @@ -793,7 +793,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "5c154d90af8faaf107ce35a8aedbe1025e2313d4" + "reference": "d8bfc30fbd87ba4b6ba30c604bc551ea88f9234f" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -820,7 +820,7 @@ "link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}" }, "branch-alias": { - "dev-trunk": "2.7.x-dev" + "dev-trunk": "2.8.x-dev" }, "version-constants": { "::PACKAGE_VERSION": "src/class-initializer.php" diff --git a/projects/plugins/backup/readme.txt b/projects/plugins/backup/readme.txt index a93c8a4215cf0..68045da428068 100644 --- a/projects/plugins/backup/readme.txt +++ b/projects/plugins/backup/readme.txt @@ -3,8 +3,8 @@ Contributors: automattic, bjorsch, fgiannar, initsogar, jeherve, jwebbdev, kraft Tags: jetpack Requires at least: 6.0 Requires PHP: 5.6 -Tested up to: 6.1 -Stable tag: 1.4.4 +Tested up to: 6.2 +Stable tag: 1.5 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -162,10 +162,14 @@ No, Jetpack VaultPress Backup does not currently support split site or split hom 2. Your site backups are stored in multiple locations on our world-class cloud infrastructure so you can recover them at any moment. == Changelog == -### 1.4.4 - 2023-02-07 +### 1.5 - 2023-03-07 #### Changed +- Backup: Update description headline, add video - Updated package dependencies. +#### Fixed +- Fixes the plugin's versioning so it actually uses WordPress versioning + -------- [See the previous changelogs here](https://github.com/Automattic/jetpack/blob/trunk/projects/plugins/backup/CHANGELOG.md#changelog) diff --git a/projects/plugins/boost/.phpcsignore b/projects/plugins/boost/.phpcsignore deleted file mode 100644 index 4a50d6733770d..0000000000000 --- a/projects/plugins/boost/.phpcsignore +++ /dev/null @@ -1,2 +0,0 @@ -# For the time being, ignore page optimize refactor -/app/features/optimizations/minify/ diff --git a/projects/plugins/boost/README.md b/projects/plugins/boost/README.md index b0ec32c326b2e..89552ffafa725 100644 --- a/projects/plugins/boost/README.md +++ b/projects/plugins/boost/README.md @@ -6,6 +6,18 @@ Jetpack Boost gives your site the same performance advantages as the world’s l **If you are not planning on developing with Jetpack Boost, you should install Jetpack Boost from pre-built sources.** Details on that may be found [on this page](https://github.com/Automattic/jetpack-boost-production). +## Development + +### Live-reloading CSS + +The live-reload feature is configured to only reload CSS files. Currently the way our rollup/webpack combination is configured - every change results in a full rebuild of the JS files, so even style changes would trigger a full page refresh. + +If you want to use the live-reloading feature, you have to install the [Livereload extension](https://chrome.google.com/webstore/detail/livereload/jnihajbhpnppcggbcgedagnkighmdlei?hl=en) (for Chrome) and then run + +```sh +npm run devlive +``` + ### Installation from Git repo Please refer to the [Development guide](./docs/DEVELOPEMENT_GUIDE.md) section of Jetpack Boost documentation. diff --git a/projects/plugins/boost/app/features/optimizations/minify/Concatenate_CSS.php b/projects/plugins/boost/app/features/optimizations/minify/Concatenate_CSS.php index cb468c980180d..5d65c096332bc 100644 --- a/projects/plugins/boost/app/features/optimizations/minify/Concatenate_CSS.php +++ b/projects/plugins/boost/app/features/optimizations/minify/Concatenate_CSS.php @@ -4,13 +4,16 @@ use WP_Styles; +// Disable complaints about enqueuing stylesheets, as this class alters the way enqueuing them works. +// phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet + class Concatenate_CSS extends WP_Styles { private $dependency_path_mapping; private $old_styles; public $allow_gzip_compression; - function __construct( $styles ) { + public function __construct( $styles ) { if ( empty( $styles ) || ! ( $styles instanceof WP_Styles ) ) { $this->old_styles = new WP_Styles(); } else { @@ -32,7 +35,7 @@ function __construct( $styles ) { ); } - function do_items( $handles = false, $group = false ) { + public function do_items( $handles = false, $group = false ) { $handles = false === $handles ? $this->queue : (array) $handles; $stylesheets = array(); $siteurl = apply_filters( 'page_optimize_site_url', $this->base_url ); @@ -53,11 +56,11 @@ function do_items( $handles = false, $group = false ) { // http://core.trac.wordpress.org/attachment/ticket/16827/colors-hacked-fixed.diff // http://core.trac.wordpress.org/ticket/20729 $css_url = $obj->src; - if ( 'colors' == $obj->handle && true === $css_url ) { + if ( 'colors' === $obj->handle && true === $css_url ) { $css_url = wp_style_loader_src( $css_url, $obj->handle ); } - $css_url_parsed = parse_url( $obj->src ); + $css_url_parsed = wp_parse_url( $obj->src ); $extra = $obj->extra; // Don't concat by default @@ -141,9 +144,9 @@ function do_items( $handles = false, $group = false ) { unset( $this->to_do[ $key ] ); } - foreach ( $stylesheets as $idx => $stylesheets_group ) { + foreach ( $stylesheets as $_idx => $stylesheets_group ) { foreach ( $stylesheets_group as $media => $css ) { - if ( 'noconcat' == $media ) { + if ( 'noconcat' === $media ) { foreach ( $css as $handle ) { if ( $this->do_item( $handle, $group ) ) { $this->done[] = $handle; @@ -165,6 +168,7 @@ function do_items( $handles = false, $group = false ) { $path_str = "$path_str?m=$mtime"; if ( $this->allow_gzip_compression ) { + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode $path_64 = base64_encode( gzcompress( $path_str ) ); if ( strlen( $path_str ) > ( strlen( $path_64 ) + 1 ) ) { $path_str = '-' . $path_64; @@ -187,6 +191,7 @@ function do_items( $handles = false, $group = false ) { $style_tag = apply_filters( 'page_optimize_style_loader_tag', $style_tag, $handles, $href, $media ); $style_tag = apply_filters( 'style_loader_tag', $style_tag, $handles, $href, $media ); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $style_tag . "\n"; array_map( array( $this, 'print_inline_style' ), array_keys( $css ) ); @@ -196,19 +201,19 @@ function do_items( $handles = false, $group = false ) { return $this->done; } - function __isset( $key ) { + public function __isset( $key ) { return isset( $this->old_styles->$key ); } - function __unset( $key ) { + public function __unset( $key ) { unset( $this->old_styles->$key ); } - function &__get( $key ) { + public function &__get( $key ) { return $this->old_styles->$key; } - function __set( $key, $value ) { + public function __set( $key, $value ) { $this->old_styles->$key = $value; } } diff --git a/projects/plugins/boost/app/features/optimizations/minify/Concatenate_JS.php b/projects/plugins/boost/app/features/optimizations/minify/Concatenate_JS.php index 948a3994dc5ab..b9d023cd99533 100644 --- a/projects/plugins/boost/app/features/optimizations/minify/Concatenate_JS.php +++ b/projects/plugins/boost/app/features/optimizations/minify/Concatenate_JS.php @@ -4,13 +4,16 @@ use WP_Scripts; +// Disable complaints about enqueuing scripts, as this class alters the way enqueuing them works. +// phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript + class Concatenate_JS extends WP_Scripts { private $dependency_path_mapping; private $old_scripts; public $allow_gzip_compression; - function __construct( $scripts ) { + public function __construct( $scripts ) { if ( empty( $scripts ) || ! ( $scripts instanceof WP_Scripts ) ) { $this->old_scripts = new WP_Scripts(); } else { @@ -52,7 +55,9 @@ protected function has_inline_content( $handle ) { return false; } - function do_items( $handles = false, $group = false ) { + public function do_items( $handles = false, $group = false ) { + global $wp_filesystem; + $handles = false === $handles ? $this->queue : (array) $handles; $javascripts = array(); $siteurl = apply_filters( 'page_optimize_site_url', $this->base_url ); @@ -62,7 +67,7 @@ function do_items( $handles = false, $group = false ) { $using_strict = false; foreach ( $this->to_do as $key => $handle ) { $script_is_strict = false; - if ( in_array( $handle, $this->done ) || ! isset( $this->registered[ $handle ] ) ) { + if ( in_array( $handle, $this->done, true ) || ! isset( $this->registered[ $handle ] ) ) { continue; } @@ -85,7 +90,7 @@ function do_items( $handles = false, $group = false ) { $obj = $this->registered[ $handle ]; $js_url = $obj->src; - $js_url_parsed = parse_url( $js_url ); + $js_url_parsed = wp_parse_url( $js_url ); // Don't concat by default $do_concat = false; @@ -131,7 +136,7 @@ function do_items( $handles = false, $group = false ) { } $do_concat = false; $script_is_strict = true; - } elseif ( $do_concat && preg_match_all( '/^[\',"]use strict[\',"];/Uims', file_get_contents( $js_realpath ), $matches ) ) { + } elseif ( $do_concat && preg_match_all( '/^[\',"]use strict[\',"];/Uims', $wp_filesystem->get_contents( $js_realpath ), $matches ) ) { // Skip third-party scripts that use Strict Mode if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { echo sprintf( "\n\n", esc_html( $handle ) ); @@ -196,11 +201,11 @@ function do_items( $handles = false, $group = false ) { } foreach ( $javascripts as $js_array ) { - if ( 'do_item' == $js_array['type'] ) { + if ( 'do_item' === $js_array['type'] ) { if ( $this->do_item( $js_array['handle'], $group ) ) { $this->done[] = $js_array['handle']; } - } elseif ( 'concat' == $js_array['type'] ) { + } elseif ( 'concat' === $js_array['type'] ) { array_map( array( $this, 'print_extra_script' ), $js_array['handles'] ); if ( isset( $js_array['paths'] ) && count( $js_array['paths'] ) > 1 ) { @@ -218,6 +223,7 @@ function do_items( $handles = false, $group = false ) { $path_str = "$path_str?m=$mtime"; if ( $this->allow_gzip_compression ) { + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode $path_64 = base64_encode( gzcompress( $path_str ) ); if ( strlen( $path_str ) > ( strlen( $path_64 ) + 1 ) ) { $path_str = '-' . $path_64; @@ -234,6 +240,7 @@ function do_items( $handles = false, $group = false ) { // Print before/after scripts from wp_inline_scripts() and concatenated script tag if ( isset( $js_array['extras']['before'] ) ) { foreach ( $js_array['extras']['before'] as $inline_before ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $inline_before; } } @@ -257,11 +264,13 @@ function do_items( $handles = false, $group = false ) { $tag = apply_filters( 'script_loader_tag', $tag, $js_array['handles'][0], $href ); } + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $tag; } if ( isset( $js_array['extras']['after'] ) ) { foreach ( $js_array['extras']['after'] as $inline_after ) { + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $inline_after; } } @@ -273,19 +282,19 @@ function do_items( $handles = false, $group = false ) { return $this->done; } - function __isset( $key ) { + public function __isset( $key ) { return isset( $this->old_scripts->$key ); } - function __unset( $key ) { + public function __unset( $key ) { unset( $this->old_scripts->$key ); } - function &__get( $key ) { + public function &__get( $key ) { return $this->old_scripts->$key; } - function __set( $key, $value ) { + public function __set( $key, $value ) { $this->old_scripts->$key = $value; } } diff --git a/projects/plugins/boost/app/features/optimizations/minify/Dependency_Path_Mapping.php b/projects/plugins/boost/app/features/optimizations/minify/Dependency_Path_Mapping.php index 1c9de4f811c34..8adeede37d18a 100644 --- a/projects/plugins/boost/app/features/optimizations/minify/Dependency_Path_Mapping.php +++ b/projects/plugins/boost/app/features/optimizations/minify/Dependency_Path_Mapping.php @@ -2,8 +2,6 @@ namespace Automattic\Jetpack_Boost\Features\Optimizations\Minify; -use Automattic\Jetpack_Boost\Features\Optimizations\Minify\Config; - /** * This is a class to map script and style URLs to local filesystem paths. * This is necessary when we are deciding what we can concatenate and when @@ -21,7 +19,7 @@ class Dependency_Path_Mapping { public $plugin_uri_path = null; public $plugin_dir = null; - function __construct( + public function __construct( // Expose URLs and DIRs for unit test $site_url = null, // default site URL is determined dynamically $site_dir = null, @@ -39,18 +37,18 @@ function __construct( } $site_url = trailingslashit( $site_url ); $this->site_url = $site_url; - $this->site_uri_path = parse_url( $site_url, PHP_URL_PATH ); + $this->site_uri_path = wp_parse_url( $site_url, PHP_URL_PATH ); $this->site_dir = trailingslashit( $site_dir ); // Only resolve content URLs if they are under the site URL if ( $this->is_internal_uri( $content_url ) ) { - $this->content_uri_path = parse_url( trailingslashit( $content_url ), PHP_URL_PATH ); + $this->content_uri_path = wp_parse_url( trailingslashit( $content_url ), PHP_URL_PATH ); $this->content_dir = trailingslashit( $content_dir ); } // Only resolve plugin URLs if they are under the site URL if ( $this->is_internal_uri( $plugin_url ) ) { - $this->plugin_uri_path = parse_url( trailingslashit( $plugin_url ), PHP_URL_PATH ); + $this->plugin_uri_path = wp_parse_url( trailingslashit( $plugin_url ), PHP_URL_PATH ); $this->plugin_dir = trailingslashit( $plugin_dir ); } } @@ -58,14 +56,14 @@ function __construct( /** * Given the full URL of a script/style dependency, return its local filesystem path. */ - function dependency_src_to_fs_path( $src ) { + public function dependency_src_to_fs_path( $src ) { if ( ! $this->is_internal_uri( $src ) ) { // If a URI is not internal, we can have no confidence // we are resolving to the correct file. return false; } - $src_parts = parse_url( $src ); + $src_parts = wp_parse_url( $src ); if ( false === $src_parts ) { return false; } @@ -89,7 +87,7 @@ function dependency_src_to_fs_path( $src ) { /** * Given a URI path of a script/style resource, return its local filesystem path. */ - function uri_path_to_fs_path( $uri_path ) { + public function uri_path_to_fs_path( $uri_path ) { if ( 1 === preg_match( '#(?:^|/)\.\.?(?:/|$)#', $uri_path ) ) { // Reject relative paths return false; @@ -118,7 +116,7 @@ function uri_path_to_fs_path( $uri_path ) { * * This method helps ensure we only resolve to local FS paths. */ - function is_internal_uri( $uri ) { + public function is_internal_uri( $uri ) { if ( jetpack_boost_page_optimize_starts_with( '/', $uri ) && ! jetpack_boost_page_optimize_starts_with( '//', $uri ) ) { // Absolute paths are internal because they are based on the site dir (typically ABSPATH), // and this looks like an absolute path. @@ -135,7 +133,7 @@ function is_internal_uri( $uri ) { * * Does not handle relative paths. */ - static function is_descendant_uri( $dir_path, $candidate ) { + public static function is_descendant_uri( $dir_path, $candidate ) { // Ensure a trailing slash to avoid false matches like // "/wp-content/resource" being judged a descendant of "/wp". $dir_path = trailingslashit( $dir_path ); diff --git a/projects/plugins/boost/app/features/optimizations/minify/Minify.php b/projects/plugins/boost/app/features/optimizations/minify/Minify.php index 6011e8b220ec7..a54693d7547c7 100644 --- a/projects/plugins/boost/app/features/optimizations/minify/Minify.php +++ b/projects/plugins/boost/app/features/optimizations/minify/Minify.php @@ -3,16 +3,19 @@ namespace Automattic\Jetpack_Boost\Features\Optimizations\Minify; use Automattic\Jetpack_Boost\Contracts\Feature; -use Automattic\Jetpack_Boost\Features\Optimizations\Minify\Config; + +// Allow overriding WordPress globals, as that is necessary to taking over script output. +// phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited class Minify implements Feature { // @todo - handle PHP constants. public function setup() { - require 'functions-helpers.php'; + require __DIR__ . '/functions-helpers.php'; // TODO: Make concat URL dir configurable - if ( isset( $_SERVER['REQUEST_URI'] ) && '/_static/' === substr( $_SERVER['REQUEST_URI'], 0, 9 ) ) { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + if ( isset( $_SERVER['REQUEST_URI'] ) && '/_static/' === substr( wp_unslash( $_SERVER['REQUEST_URI'] ), 0, 9 ) ) { require_once __DIR__ . '/service.php'; exit; } @@ -46,6 +49,8 @@ public function init_concatenate() { return; } + jetpack_boost_init_filesystem(); + if ( jetpack_boost_page_optimize_should_concat_js() || jetpack_boost_page_optimize_load_mode_js() ) { global $wp_scripts; diff --git a/projects/plugins/boost/app/features/optimizations/minify/functions-helpers.php b/projects/plugins/boost/app/features/optimizations/minify/functions-helpers.php index 0d4910ac68228..e69d20ace21a6 100644 --- a/projects/plugins/boost/app/features/optimizations/minify/functions-helpers.php +++ b/projects/plugins/boost/app/features/optimizations/minify/functions-helpers.php @@ -32,7 +32,7 @@ function jetpack_boost_page_optimize_cache_cleanup( $cache_folder = false, $file } if ( ( time() - $file_age ) > filemtime( $cache_file ) ) { - unlink( $cache_file ); + wp_delete_file( $cache_file ); } } } @@ -43,7 +43,7 @@ function jetpack_boost_page_optimize_deactivate() { jetpack_boost_page_optimize_cache_cleanup( $cache_folder, 0 /* max file age in seconds */ ); - wp_clear_scheduled_hook( Config::get_cron_cache_cleanup_hook(), [ $cache_folder ] ); + wp_clear_scheduled_hook( Config::get_cron_cache_cleanup_hook(), array( $cache_folder ) ); } function jetpack_boost_page_optimize_uninstall() { @@ -57,17 +57,25 @@ function jetpack_boost_page_optimize_uninstall() { // CSS delete_option( 'page_optimize-css' ); delete_option( 'page_optimize-css-exclude' ); - } -function jetpack_boost_page_optimize_get_text_domain() { - return 'page-optimize'; +/** + * Ensure that WP_Filesystem is ready to use. + */ +function jetpack_boost_init_filesystem() { + global $wp_filesystem; + + if ( empty( $wp_filesystem ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + \WP_Filesystem(); + } } function jetpack_boost_page_optimize_should_concat_js() { // Support query param for easy testing - if ( isset( $_GET['concat-js'] ) ) { - return $_GET['concat-js'] !== '0'; + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( isset( $_GET['concat-js'] ) && $_GET['concat-js'] !== '0' ) { + return true; } return (bool) get_option( 'page_optimize-js', jetpack_boost_page_optimize_js_default() ); @@ -75,20 +83,25 @@ function jetpack_boost_page_optimize_should_concat_js() { // TODO: Support JS load mode regardless of whether concat is enabled function jetpack_boost_page_optimize_load_mode_js() { - // Support query param for easy testing - if ( ! empty( $_GET['load-mode-js'] ) ) { - $load_mode = jetpack_boost_page_optimize_sanitize_js_load_mode( $_GET['load-mode-js'] ); - } else { - $load_mode = jetpack_boost_page_optimize_sanitize_js_load_mode( get_option( 'page_optimize-load-mode', jetpack_boost_page_optimize_js_load_mode_default() ) ); + $load_mode_arg = jetpack_boost_page_optimize_sanitize_js_load_mode( + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + empty( $_GET['load-mode-js'] ) ? '' : filter_var( wp_unslash( $_GET['load-mode-js'] ) ) + ); + + if ( ! empty( $load_mode_arg ) ) { + return $load_mode_arg; } - return $load_mode; + return jetpack_boost_page_optimize_sanitize_js_load_mode( + get_option( 'page_optimize-load-mode', jetpack_boost_page_optimize_js_load_mode_default() ) + ); } function jetpack_boost_page_optimize_should_concat_css() { // Support query param for easy testing - if ( isset( $_GET['concat-css'] ) ) { - return $_GET['concat-css'] !== '0'; + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( isset( $_GET['concat-css'] ) && $_GET['concat-css'] !== '0' ) { + return true; } return (bool) get_option( 'page_optimize-css', jetpack_boost_page_optimize_css_default() ); @@ -225,7 +238,7 @@ function jetpack_boost_page_optimize_remove_concat_base_prefix( $original_fs_pat function jetpack_boost_page_optimize_schedule_cache_cleanup() { $cache_folder = Config::get_cache_dir_path(); - $args = array( $cache_folder ); + $args = array( $cache_folder ); $cache_cleanup_hook = Config::get_cron_cache_cleanup_hook(); @@ -244,11 +257,13 @@ function jetpack_boost_page_optimize_bail() { } // Bail if Divi theme is active, and we're in the Divi Front End Builder + // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_GET['et_fb'] ) && 'Divi' === wp_get_theme()->get_template() ) { return true; } // Bail if we're editing pages in Brizy Editor + // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( class_exists( 'Brizy_Editor' ) && method_exists( 'Brizy_Editor', 'prefix' ) && ( isset( $_GET[ Brizy_Editor::prefix( '-edit-iframe' ) ] ) || isset( $_GET[ Brizy_Editor::prefix( '-edit' ) ] ) ) ) { return true; } @@ -266,7 +281,7 @@ function jetpack_boost_page_optimize_cache_bust_mtime( $path, $siteurl ) { return $url; } - $parts = parse_url( $url ); + $parts = wp_parse_url( $url ); if ( ! isset( $parts['path'] ) || empty( $parts['path'] ) ) { return $url; } diff --git a/projects/plugins/boost/app/features/optimizations/minify/service.php b/projects/plugins/boost/app/features/optimizations/minify/service.php index 75774286da462..34493ba163ab0 100644 --- a/projects/plugins/boost/app/features/optimizations/minify/service.php +++ b/projects/plugins/boost/app/features/optimizations/minify/service.php @@ -12,45 +12,58 @@ function jetpack_boost_page_optimize_types() { } function jetpack_boost_page_optimize_service_request() { + global $wp_filesystem; + + jetpack_boost_init_filesystem(); + $cache_dir = Config::get_cache_dir_path(); $use_cache = ! empty( $cache_dir ); - if ( $use_cache && ! is_dir( $cache_dir ) && ! mkdir( $cache_dir, 0775, true ) ) { + if ( $use_cache && ! is_dir( $cache_dir ) && ! $wp_filesystem->mkdir( $cache_dir, 0775, true ) ) { $use_cache = false; - error_log( - sprintf( - /* translators: a filesystem path to a directory */ - __( "Disabling page-optimize cache. Unable to create cache directory '%s'.", jetpack_boost_page_optimize_get_text_domain() ), - $cache_dir - ) - ); + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( + sprintf( + /* translators: a filesystem path to a directory */ + __( "Disabling page-optimize cache. Unable to create cache directory '%s'.", 'jetpack-boost' ), + $cache_dir + ) + ); + } } - if ( $use_cache && ( ! is_dir( $cache_dir ) || ! is_writable( $cache_dir ) || ! is_executable( $cache_dir ) ) ) { + if ( $use_cache && ( ! is_dir( $cache_dir ) || ! $wp_filesystem->is_writable( $cache_dir ) || ! is_executable( $cache_dir ) ) ) { $use_cache = false; - error_log( - sprintf( - /* translators: a filesystem path to a directory */ - __( "Disabling page-optimize cache. Unable to write to cache directory '%s'.", jetpack_boost_page_optimize_get_text_domain() ), - $cache_dir - ) - ); + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log + error_log( + sprintf( + /* translators: a filesystem path to a directory */ + __( "Disabling page-optimize cache. Unable to write to cache directory '%s'.", 'jetpack-boost' ), + $cache_dir + ) + ); + } } if ( $use_cache ) { - $request_uri_hash = md5( $_SERVER['REQUEST_URI'] ); + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : ''; + $request_uri_hash = md5( $request_uri ); $cache_file = $cache_dir . "/page-optimize-cache-$request_uri_hash"; $cache_file_meta = $cache_dir . "/page-optimize-cache-meta-$request_uri_hash"; if ( file_exists( $cache_file ) ) { if ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) { - if ( strtotime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) < filemtime( $cache_file ) ) { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + if ( strtotime( wp_unslash( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) < filemtime( $cache_file ) ) { header( 'HTTP/1.1 304 Not Modified' ); exit; } } if ( file_exists( $cache_file_meta ) ) { - $meta = json_decode( file_get_contents( $cache_file_meta ) ); + $meta = json_decode( $wp_filesystem->get_contents( $cache_file_meta ) ); if ( null !== $meta && isset( $meta->headers ) ) { foreach ( $meta->headers as $header ) { header( $header ); @@ -58,13 +71,13 @@ function jetpack_boost_page_optimize_service_request() { } } - $etag = '"' . md5( file_get_contents( $cache_file ) ) . '"'; + $etag = '"' . md5( $wp_filesystem->get_contents( $cache_file ) ) . '"'; header( 'X-Page-Optimize: cached' ); header( 'Cache-Control: max-age=' . 31536000 ); header( 'ETag: ' . $etag ); - echo file_get_contents( $cache_file ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- We need to trust this unfortunately. + echo $wp_filesystem->get_contents( $cache_file ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- We need to trust this unfortunately. die(); } } @@ -83,29 +96,35 @@ function jetpack_boost_page_optimize_service_request() { echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- We need to trust this unfortunately. if ( $use_cache ) { - file_put_contents( $cache_file, $content ); - file_put_contents( $cache_file_meta, json_encode( array( 'headers' => $headers ) ) ); + $wp_filesystem->put_contents( $cache_file, $content ); + $wp_filesystem->put_contents( $cache_file_meta, wp_json_encode( array( 'headers' => $headers ) ) ); } die(); } function jetpack_boost_page_optimize_build_output() { + global $wp_filesystem; + $jetpack_boost_page_optimize_types = jetpack_boost_page_optimize_types(); - /* Config */ + // Config $concat_max_files = 150; $concat_unique = true; - /* Main() */ - if ( ! in_array( $_SERVER['REQUEST_METHOD'], array( 'GET', 'HEAD' ) ) ) { + // Main + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $method = isset( $_SERVER['REQUEST_METHOD'] ) ? wp_unslash( $_SERVER['REQUEST_METHOD'] ) : 'GET'; + if ( ! in_array( $method, array( 'GET', 'HEAD' ), true ) ) { jetpack_boost_page_optimize_status_exit( 400 ); } // /_static/??/foo/bar.css,/foo1/bar/baz.css?m=293847g // or // /_static/??-eJzTT8vP109KLNJLLi7W0QdyDEE8IK4CiVjn2hpZGluYmKcDABRMDPM= - $args = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_QUERY ); + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : ''; + $args = wp_parse_url( $request_uri, PHP_URL_QUERY ); if ( ! $args || false === strpos( $args, '?' ) ) { jetpack_boost_page_optimize_status_exit( 400 ); } @@ -115,7 +134,8 @@ function jetpack_boost_page_optimize_build_output() { // /foo/bar.css,/foo1/bar/baz.css?m=293847g // or // -eJzTT8vP109KLNJLLi7W0QdyDEE8IK4CiVjn2hpZGluYmKcDABRMDPM= - if ( '-' == $args[0] ) { + if ( '-' === $args[0] ) { + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode $args = @gzuncompress( base64_decode( substr( $args, 1 ) ) ); // Invalid data, abort! @@ -136,15 +156,15 @@ function jetpack_boost_page_optimize_build_output() { jetpack_boost_page_optimize_status_exit( 400 ); } - // array( '/foo/bar.css', '/foo1/bar/baz.css' ) - if ( 0 == count( $args ) || count( $args ) > $concat_max_files ) { + // args contain something like array( '/foo/bar.css', '/foo1/bar/baz.css' ) + if ( 0 === count( $args ) || count( $args ) > $concat_max_files ) { jetpack_boost_page_optimize_status_exit( 400 ); } // If we're in a subdirectory context, use that as the root. // We can't assume that the root serves the same content as the subdir. $subdir_path_prefix = ''; - $request_path = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH ); + $request_path = wp_parse_url( $request_uri, PHP_URL_PATH ); $_static_index = strpos( $request_path, '/_static/' ); if ( $_static_index > 0 ) { $subdir_path_prefix = substr( $request_path, 0, $_static_index ); @@ -169,7 +189,7 @@ function jetpack_boost_page_optimize_build_output() { } $mime_type = jetpack_boost_page_optimize_get_mime_type( $fullpath ); - if ( ! in_array( $mime_type, $jetpack_boost_page_optimize_types ) ) { + if ( ! in_array( $mime_type, $jetpack_boost_page_optimize_types, true ) ) { jetpack_boost_page_optimize_status_exit( 400 ); } @@ -178,7 +198,7 @@ function jetpack_boost_page_optimize_build_output() { $last_mime_type = $mime_type; } - if ( $last_mime_type != $mime_type ) { + if ( $last_mime_type !== $mime_type ) { jetpack_boost_page_optimize_status_exit( 400 ); } } @@ -192,21 +212,22 @@ function jetpack_boost_page_optimize_build_output() { $last_modified = $stat['mtime']; } - $buf = file_get_contents( $fullpath ); + $buf = $wp_filesystem->get_contents( $fullpath ); if ( false === $buf ) { jetpack_boost_page_optimize_status_exit( 500 ); } - if ( 'text/css' == $mime_type ) { + if ( 'text/css' === $mime_type ) { $dirpath = '/' . ltrim( $subdir_path_prefix . dirname( $uri ), '/' ); // url(relative/path/to/file) -> url(/absolute/and/not/relative/path/to/file) $buf = jetpack_boost_page_optimize_relative_path_replace( $buf, $dirpath ); - // AlphaImageLoader(...src='relative/path/to/file'...) -> AlphaImageLoader(...src='/absolute/path/to/file'...) + // phpcs:ignore Squiz.PHP.CommentedOutCode.Found + // This regex changes things like AlphaImageLoader(...src='relative/path/to/file'...) to AlphaImageLoader(...src='/absolute/path/to/file'...) $buf = preg_replace( '/(Microsoft.AlphaImageLoader\s*\([^\)]*src=(?:\'|")?)([^\/\'"\s\)](?:(? url(/absolute/and/not/relative/path/to/file) $buf = preg_replace( '/(:?\s*url\s*\()\s*(?:\'|")?\s*([^\/\'"\s\)](?:(? $post->post_type ), 'objects' ); - if ( empty( $post_types ) || ! isset( $post_types['post'] ) || $post_types['post']->public !== true ) { + // Ignore changes to any post which is not published. + if ( 'publish' !== $post->post_status ) { + return; + } + + // Ignore changes to post types which do not affect the front-end UI + if ( ! $this->is_post_type_invalidating( $post->post_type ) ) { return; } @@ -55,4 +60,22 @@ public function handle_plugin_change() { public function do_action( $is_major_change, $change_type ) { do_action( 'handle_environment_change', $is_major_change, $change_type ); } + + /** + * Given a post_type, return true if this post type affects the front end of + * the site - i.e.: should cause cached optimizations to be invalidated. + * + * @param string $post_type The post type to check + * @return bool True if this post type affects the front end of the site. + */ + private function is_post_type_invalidating( $post_type ) { + // Special cases: items which are not viewable, but affect the UI. + if ( in_array( $post_type, array( 'wp_template', 'wp_template_part' ), true ) ) { + return true; + } + + if ( is_post_type_viewable( $post_type ) ) { + return true; + } + } } diff --git a/projects/plugins/search/changelog/add-license-key-selector b/projects/plugins/boost/changelog/add-zendesk-chat-module similarity index 100% rename from projects/plugins/search/changelog/add-license-key-selector rename to projects/plugins/boost/changelog/add-zendesk-chat-module diff --git a/projects/plugins/search/changelog/add-license-verification b/projects/plugins/boost/changelog/add-zendesk-chat-module#2 similarity index 100% rename from projects/plugins/search/changelog/add-license-verification rename to projects/plugins/boost/changelog/add-zendesk-chat-module#2 diff --git a/projects/plugins/boost/changelog/boost-fix-minify-lint b/projects/plugins/boost/changelog/boost-fix-minify-lint new file mode 100644 index 0000000000000..5706c66977552 --- /dev/null +++ b/projects/plugins/boost/changelog/boost-fix-minify-lint @@ -0,0 +1,5 @@ +Significance: patch +Type: fixed +Comment: Cleaned up linting issues in Boost's new minify feature + + diff --git a/projects/plugins/boost/changelog/boost-update-livereload b/projects/plugins/boost/changelog/boost-update-livereload new file mode 100644 index 0000000000000..84e4b29d3481a --- /dev/null +++ b/projects/plugins/boost/changelog/boost-update-livereload @@ -0,0 +1,5 @@ +Significance: patch +Type: added +Comment: Livereload for development + + diff --git a/projects/plugins/boost/changelog/fix-react-to-all-visible-changes b/projects/plugins/boost/changelog/fix-react-to-all-visible-changes new file mode 100644 index 0000000000000..f51b2e8e520ae --- /dev/null +++ b/projects/plugins/boost/changelog/fix-react-to-all-visible-changes @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Ensure changes to visible posts / other post types which affect the ui get caught diff --git a/projects/plugins/boost/changelog/fix-safari-lazy-load b/projects/plugins/boost/changelog/fix-safari-lazy-load new file mode 100644 index 0000000000000..4ac4ef322b7be --- /dev/null +++ b/projects/plugins/boost/changelog/fix-safari-lazy-load @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Fixed images sometimes failing to Lazy-load in Safari. diff --git a/projects/plugins/boost/changelog/update-wp-tested-up-to b/projects/plugins/boost/changelog/update-wp-tested-up-to new file mode 100644 index 0000000000000..ad53b760f90b2 --- /dev/null +++ b/projects/plugins/boost/changelog/update-wp-tested-up-to @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +General: indicate full compatibility with the latest version of WordPress, 6.2. diff --git a/projects/plugins/boost/composer.lock b/projects/plugins/boost/composer.lock index 6ae690ac50e49..bd4700c119935 100644 --- a/projects/plugins/boost/composer.lock +++ b/projects/plugins/boost/composer.lock @@ -701,7 +701,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "5c154d90af8faaf107ce35a8aedbe1025e2313d4" + "reference": "d8bfc30fbd87ba4b6ba30c604bc551ea88f9234f" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -728,7 +728,7 @@ "link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}" }, "branch-alias": { - "dev-trunk": "2.7.x-dev" + "dev-trunk": "2.8.x-dev" }, "version-constants": { "::PACKAGE_VERSION": "src/class-initializer.php" diff --git a/projects/plugins/boost/package.json b/projects/plugins/boost/package.json index 221e948f7a429..3936429801e40 100644 --- a/projects/plugins/boost/package.json +++ b/projects/plugins/boost/package.json @@ -31,6 +31,7 @@ "@types/jquery": "3.5.14", "@wordpress/i18n": "4.28.0", "concurrently": "7.6.0", + "livereload": "0.9.3", "postcss": "8.4.21", "prettier-plugin-svelte": "2.8.1", "react": "18.2.0", @@ -56,6 +57,7 @@ "compile-ts": "tsc --pretty", "dev-serve": "rollup -c -w --environment SERVE", "dev": "pnpm run clear-dist && rollup -c -w", + "devlive": "concurrently --kill-others 'pnpm run dev' 'livereload app/assets/dist --exts css'", "reformat-files": "../../../tools/js-tools/node_modules/.bin/prettier --ignore-path .eslintignore --write --plugin-search-dir=. ./**/*.{svelte,js,ts,json}", "lint": "pnpm run reformat-files && echo 'Running eslint...' && pnpm eslint app/assets/src/js tests/e2e --fix && echo '✔ prettier and eslint ran successfully.'", "clear-dist": "rm -rf app/assets/dist/*", diff --git a/projects/plugins/boost/readme.txt b/projects/plugins/boost/readme.txt index 7eb506ce012df..7768c272414fa 100644 --- a/projects/plugins/boost/readme.txt +++ b/projects/plugins/boost/readme.txt @@ -3,7 +3,7 @@ Contributors: automattic, xwp, adnan007, bjorsch, danwalmsley, davidlonjon, ebin Donate link: https://automattic.com Tags: performance, speed, pagespeed, web vitals, critical css, optimize, defer Requires at least: 5.5 -Tested up to: 6.1 +Tested up to: 6.2 Requires PHP: 7.0 Stable tag: 1.6.0 License: GPLv2 or later diff --git a/projects/plugins/crm/changelog/fix-crm-edit-add-form-title b/projects/plugins/crm/changelog/fix-crm-edit-add-form-title new file mode 100644 index 0000000000000..ceef242e0c77d --- /dev/null +++ b/projects/plugins/crm/changelog/fix-crm-edit-add-form-title @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +CRM: swapping edit and new form titles to correctly reflect page. diff --git a/projects/plugins/crm/changelog/update-wp-tested-up-to b/projects/plugins/crm/changelog/update-wp-tested-up-to new file mode 100644 index 0000000000000..ad53b760f90b2 --- /dev/null +++ b/projects/plugins/crm/changelog/update-wp-tested-up-to @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +General: indicate full compatibility with the latest version of WordPress, 6.2. diff --git a/projects/plugins/crm/includes/class-learn-menu.php b/projects/plugins/crm/includes/class-learn-menu.php index d272073d424d0..e47a5606cf3b4 100644 --- a/projects/plugins/crm/includes/class-learn-menu.php +++ b/projects/plugins/crm/includes/class-learn-menu.php @@ -291,10 +291,10 @@ private function get_slug() { } // Forms - if ( zeroBSCRM_is_form_new_page() ){ - $slug = 'editform'; - } elseif ( zeroBSCRM_is_form_edit_page() ){ + if ( zeroBSCRM_is_form_new_page() ) { $slug = 'formnew'; + } elseif ( zeroBSCRM_is_form_edit_page() ) { + $slug = 'editform'; } // profile page diff --git a/projects/plugins/crm/readme.txt b/projects/plugins/crm/readme.txt index c65225efe4d4c..7fb8c29d21ec2 100644 --- a/projects/plugins/crm/readme.txt +++ b/projects/plugins/crm/readme.txt @@ -1,7 +1,7 @@ === Jetpack CRM - Clients, Leads, Invoices, Billing, Email Marketing, & Automation === Contributors: automattic, woodyhayday, mikemayhem3030 Tags: CRM, Invoice, Woocommerce CRM, Clients, Lead Generation, contacts, customers, billing, email marketing, Marketing Automation, contact form, automations -Tested up to: 6.1 +Tested up to: 6.2 Stable tag: 5.5.3 Requires at least: 5.0 Requires PHP: 7.2 @@ -63,7 +63,7 @@ From reading this to understanding Jetpack CRM will probably take you 10 minutes The Jetpack CRM plugin installs in seconds, (with a super-quick welcome wizard to get you started). Adding a contact is straightforward. Creating a quote, or invoice, even simpler. Give it a go, you'll see!
-= ⭐ Mastering Jetpack CRM = += ⭐ Mastering Jetpack CRM = 1. Install this WordPress CRM plugin 2. Step through the guided Welcome Wizard 3. Add your first contact (lead or customer) @@ -82,7 +82,7 @@ Need a helping hand? No worries. Just Twilio (for SMS Sending)
  • Plus many more...
  • -
    + ## 🥼 Try Jetpack CRM Today! @@ -172,7 +172,7 @@ We've added so much value to our new v4.0 that it's hard to give you a list of a * 🚀 **Modern. Lean. Accessible** * [Mobile Ready](https://jetpackcrm.com/feature/mobile-ready/) - * [International - Languages & Currencies](https://jetpackcrm.com/feature/multi-language-your-currency/): Translation ready + * [International - Languages & Currencies](https://jetpackcrm.com/feature/multi-language-your-currency/): Translation ready * [DIY CRM](https://jetpackcrm.com/feature/diy-crm/) * 🇪🇺 Make sure you are GDPR compliant by knowing where your data is stored! @@ -201,7 +201,7 @@ We've added so much value to our new v4.0 that it's hard to give you a list of a * 🛡️ **Backed by Automattic** * Jetpack CRM is part of the Automattic family * Superb as a WooCommerce CRM - * Consistent development and improvements + * Consistent development and improvements * 🏷️ **[White-Label CRM](https://jetpackcrm.com/reseller/)** * [Rebranding Engine](https://jetpackcrm.com/feature/rebrandr/): Brand your CRM with your company name or a customers (and logo!) @@ -216,7 +216,7 @@ We've added so much value to our new v4.0 that it's hard to give you a list of a * See all customer activity at-a-glance from the customer record, (useful for accounting) * **NEW:** [Automations](https://jetpackcrm.com/product/automations/) extension provides automatic actions on events! -* ➕ **Too much more to mention here...** +* ➕ **Too much more to mention here...** * [PDF Invoicing](https://jetpackcrm.com/feature/invoices/), billing statements and more client tools * Invoice builder with line items, hourly or item rates, email invoices and get paid via customer portal! * Tax Table management - assign multiple local/national taxes to Invoices or Transactions @@ -229,7 +229,7 @@ We've added so much value to our new v4.0 that it's hard to give you a list of a * [See all features](https://jetpackcrm.com/features/#features) ## 👔 SME, Small Business, & WordPress Entrepreneurs -Built for business managers and bootstrapped entrepreneurs, Jetpack CRM is the perfect start-up tool to manage your business essentials. We've added just enough billing and accounting for freelancers and small teams, without getting lost in the potential feature bloat of full accounting and erp software. We didn't add HR tools, but we do have user management. Customer Relationship Management is meant to make lead management easy. Start with a contact form builder and lead generation, track contacts through funnel analytics then use newsletters and email marketing to grow your profits by selling to contacts at companies. +Built for business managers and bootstrapped entrepreneurs, Jetpack CRM is the perfect start-up tool to manage your business essentials. We've added just enough billing and accounting for freelancers and small teams, without getting lost in the potential feature bloat of full accounting and erp software. We didn't add HR tools, but we do have user management. Customer Relationship Management is meant to make lead management easy. Start with a contact form builder and lead generation, track contacts through funnel analytics then use newsletters and email marketing to grow your profits by selling to contacts at companies. Jetpack CRM is the perfect first step to improving your small business. [Try it for free](https://jetpackcrm.com/download/)! @@ -298,7 +298,7 @@ For more documentation, please see the CRM knowledge base: [https://kb.jetpackcr There are absolutely no limits in Jetpack CRM, apart from the usual ones applied by your host (database size etc.), you can create as many clients or invoices as you'd like! -= Is Jetpack CRM really free? = += Is Jetpack CRM really free? = Totally. The core is a solid, useful, Customer Relationship Manager right out of the box. You don't need anything else to get started managing your leads, and customers. We do build extensions, (because we need them), but they're not at all essential. Further, they're cheap in comparison to the pay-per-month CRM options. @@ -306,11 +306,11 @@ Totally. The core is a solid, useful, Customer Relationship Manager right out of Of course. Please reach out to us via the in-plugin feedback page if you want to, we'll help you get started, or check out our [Developer resources](https://jetpackcrm.com/feature/developer-ready/). -= What is next on the CRM roadmap? = += What is next on the CRM roadmap? = We haven't published our roadmap since v3, but we may do in the future. You can vote on future extensions on our [Coming Soon](https://jetpackcrm.com/coming-soon/) page. -= Where can I see the CRM extensions? = += Where can I see the CRM extensions? = You can see all of the CRM power-ups here: https://jetpackcrm.com/extensions/ @@ -322,7 +322,7 @@ Yes, Jetpack CRM comes with a search feature that allows you to run a customer s Data privacy, control, extensibility. GDPR compliance. You're probably already paying for hosting too, so you'll save there. SaaS has its place, but there's so much value in hosting your own (e.g. for GDPR rules you need to know where your customer data is kept)! -= How do I get my existing customers into Jetpack CRM? = += How do I get my existing customers into Jetpack CRM? = The free core is fully integrated with WooCommerce using the built-in WooSync module. It also has a basic CSV Importer which works well and allows you to import contacts and import customer data. You can also use our paid extensions for additional features: CSV Importer Pro, PayPal Sync, or Stripe Sync - these automatically pull up all your customer data (including transactions) and then keep it up to date for you! @@ -330,11 +330,11 @@ The free core is fully integrated with WooCommerce using the built-in WooSync mo Most CRM providers allow you to export customer data in CSV format, which can then be used to upload into Jetpack CRM. The CSV Importer Pro extension can then be used to import contacts and customer data. CRM is a very broad landscape, so each CRM may have different ways of storing data. See our [CRM Knowledgebase](https://kb.jetpackcrm.com) for more information, or ask in the forums! -= Do you have a B2B mode? = += Do you have a B2B mode? = Yes, Jetpack CRM has a [Business to Business mode](https://jetpackcrm.com/feature/b2b-mode/). Enabling the B2B CRM Extension adds a "Companies" layer, which lets you have contacts under a company. -= How does B2B mode (Companies) work in Jetpack CRM? = += How does B2B mode (Companies) work in Jetpack CRM? =

    Jetpack CRM allows you to manage contacts at companies from within the CRM. This adds a hierarchy to your CRM, letting you add a contact to a company using our contact manager. Currently you can bill (invoice) companies or contacts respectively, but in the future we want to make this much more integrated.

    For now, this works as follows:

    @@ -350,7 +350,7 @@ Yes, Jetpack CRM has a [Business to Business mode](https://jetpackcrm.com/featur Jetpack CRM allows you to create and manage quotes, invoices, and transactions against contacts. While this setup may be used in some senses like an ecommerce platform, really the aim with Jetpack CRM is to effectively manage a contact list, including manually billing them (e.g. getting invoices paid online). This works well, via our client portal, but if you want a fuller ecommerce setup, we recommend using WooCommerce, which we integrate with, (it's really great). -= Can I use this as a WooCommerce CRM? = += Can I use this as a WooCommerce CRM? = Yes, Jetpack CRM is actually ideal for using with WooCommerce. You can read more about [Jetpack as a WooCommerce CRM here](https://jetpackcrm.com/woocommerce/). @@ -363,11 +363,11 @@ Yes, Jetpack CRM is actually ideal for using with WooCommerce. You can read more Yes, Jetpack CRM allows you to easily capture leads using our [lead capture forms](https://jetpackcrm.com/feature/forms/). These could be added into posts, pages or widgets - when leads fill them out their information goes straight into the CRM. -= What are my options for lead generation forms? = += What are my options for lead generation forms? = Jetpack CRM ships with its own form builder, which lets you insert forms into your posts and pages via shortcode forms or iframe forms. For users that need more advanced form features, we offer support for Jetpack Forms out of the box, and have integrations with Contact Form 7 and Gravity Forms via paid CRM extensions. -= Can Jetpack CRM help me pay sales commissions? = += Can Jetpack CRM help me pay sales commissions? =

    We occasionally get asked if Jetpack CRM can help calculate and pay sales commissions. We do have some users who are using the CRM for this, but there are no direct features out of the box which will provide this functionality, these users are simply totting up the transactions assigned to contacts based on their 'owners' in Jetpack CRM, then paying these commissions manually. We may in future expand our reporting to offer something along the lines of a sales summary per agent.

    @@ -379,21 +379,21 @@ To send out email broadcasts such as newsletters or other email marketing you wi Jetpack CRM has it's own [Automations extension](https://jetpackcrm.com/product/automations/) which uses Triggers, Conditions, and Actions to automate workflows based on CRM events. This can be used in lead generation, email marketing, or internal business admin. If you have an idea for a workflow or automation you'd like to see, please do [submit it as a feature request](https://kb.jetpackcrm.com/submit-a-ticket/). -= What add-ons are available? = += What add-ons are available? =

    We have over 30 premium extensions available in our CRM extension store. These are totally optional, but supercharge some aspects of the CRM, (e.g. StripeSync automatically pulls in your customer and transaction data from Stripe).

    Most users tend to purchase bundles of extensions, which allows them to 'make their perfect CRM':

    View CRM Bundles

    -= When do I need a license key? = += When do I need a license key? =

    You can now add your license key into your Jetpack CRM settings page (under 'CRM License'). This is only required if you wish to use paid extensions. To get started with premium CRM extensions, please visit our extension store.

    After you've purchased you'll be guided to your new license key and extension plugin download.

    -= How do I purchase more than one license? = += How do I purchase more than one license? = -We have many agencies using Jetpack CRM, so this is a common question. +We have many agencies using Jetpack CRM, so this is a common question. To get stared please see our bundles page - if you're likely to need 10~ licenses, the best bet is to get started with the Reseller package. If you want to build up more slowly, please purchase the Entrepreneur bundle and let us know via a support ticket if you would like to add additional licenses to your account. -= What is the refund policy? = += What is the refund policy? = We offer a full, no-hassle refund within 14 days. You can read more about that, and how to request a refund, on this page. diff --git a/projects/plugins/debug-helper/CHANGELOG.md b/projects/plugins/debug-helper/CHANGELOG.md index 7894db9574207..c8747ade86562 100644 --- a/projects/plugins/debug-helper/CHANGELOG.md +++ b/projects/plugins/debug-helper/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.5.0] - 2023-03-08 +### Added +- Add "Cookie State Faker" tool. [#28371] +- Add a button to set the current primary user. [#26562] +- Added a helper module for Jetpack Scan. [#25641] +- Added threat descriptions. [#25266] +- Mocker tool: add runner to add rows in the WAF log DB table for blocked requests [#25645] +- Replace "XML-RPC errors" with "connection errors", add error type ("xml-rpc" or "rest") to generated errors. [#25694] + +### Changed +- Remove pre-defined prefix in the REST API tool. [#26521] +- Updated package dependencies. +- Updated Protect Helper to use newly added data source constant. [#26069] + +### Fixed +- Prevented the threat tester from being identified as a threat due to containing the Akismet suspicious link URL. [#26192] + ## [1.4.0] - 2022-07-06 ### Added - Added the Autoloader debugger helper to the Debug tool. [#23726] @@ -60,6 +77,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial version. +[1.5.0]: https://github.com/Automattic/jetpack-debug-helper/compare/v1.4.0...v1.5.0 [1.4.0]: https://github.com/Automattic/jetpack-debug-helper/compare/v1.3.0...v1.4.0 [1.3.0]: https://github.com/Automattic/jetpack-debug-helper/compare/v1.2.0...v1.3.0 [1.2.0]: https://github.com/Automattic/jetpack-debug-helper/compare/v1.1.0...v1.2.0 diff --git a/projects/plugins/debug-helper/changelog/add-debug-helper-scan-results b/projects/plugins/debug-helper/changelog/add-debug-helper-scan-results deleted file mode 100644 index b1fe85e5513e3..0000000000000 --- a/projects/plugins/debug-helper/changelog/add-debug-helper-scan-results +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Added a helper module for Jetpack Scan. diff --git a/projects/plugins/debug-helper/changelog/add-debug-helper-set-primary-user b/projects/plugins/debug-helper/changelog/add-debug-helper-set-primary-user deleted file mode 100644 index 05a0f6c790be5..0000000000000 --- a/projects/plugins/debug-helper/changelog/add-debug-helper-set-primary-user +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Add a button to set the current primary user. diff --git a/projects/plugins/debug-helper/changelog/add-debug-helper-waf-mocker b/projects/plugins/debug-helper/changelog/add-debug-helper-waf-mocker deleted file mode 100644 index 3cd9455ea72d0..0000000000000 --- a/projects/plugins/debug-helper/changelog/add-debug-helper-waf-mocker +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -Mocker tool: add runner to add rows in the WAF log DB table for blocked requests diff --git a/projects/plugins/debug-helper/changelog/add-jetpack-debug-state b/projects/plugins/debug-helper/changelog/add-jetpack-debug-state deleted file mode 100644 index 95caaf0a9b91e..0000000000000 --- a/projects/plugins/debug-helper/changelog/add-jetpack-debug-state +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Add "Cookie State Faker" tool. diff --git a/projects/plugins/debug-helper/changelog/add-protect-scan-api-data-source b/projects/plugins/debug-helper/changelog/add-protect-scan-api-data-source deleted file mode 100644 index d8e1adf2c90a2..0000000000000 --- a/projects/plugins/debug-helper/changelog/add-protect-scan-api-data-source +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: changed - -Updated Protect Helper to use newly added data source constant. diff --git a/projects/plugins/debug-helper/changelog/add-protect-threat-descriptions b/projects/plugins/debug-helper/changelog/add-protect-threat-descriptions deleted file mode 100644 index 6ce572a7cfeab..0000000000000 --- a/projects/plugins/debug-helper/changelog/add-protect-threat-descriptions +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -Added threat descriptions. diff --git a/projects/plugins/debug-helper/changelog/add-verify-rest-api-errors b/projects/plugins/debug-helper/changelog/add-verify-rest-api-errors deleted file mode 100644 index f4716f1a5db47..0000000000000 --- a/projects/plugins/debug-helper/changelog/add-verify-rest-api-errors +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Replace "XML-RPC errors" with "connection errors", add error type ("xml-rpc" or "rest") to generated errors. diff --git a/projects/plugins/debug-helper/changelog/changelogger-merge b/projects/plugins/debug-helper/changelog/changelogger-merge deleted file mode 100644 index 6eb3e9f9f8fa1..0000000000000 --- a/projects/plugins/debug-helper/changelog/changelogger-merge +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: added -Comment: Minor update to changelog package that supports git merge strategy. Should not affect shipped code. - - diff --git a/projects/plugins/debug-helper/changelog/fix-debug-helper-suspicious-link-concat b/projects/plugins/debug-helper/changelog/fix-debug-helper-suspicious-link-concat deleted file mode 100644 index 43aecabc087e9..0000000000000 --- a/projects/plugins/debug-helper/changelog/fix-debug-helper-suspicious-link-concat +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Prevented the threat tester from being identified as a threat due to containing the Akismet suspicious link URL. diff --git a/projects/plugins/debug-helper/changelog/remove-remnants-of-automated-code-coverage b/projects/plugins/debug-helper/changelog/remove-remnants-of-automated-code-coverage deleted file mode 100644 index 085e2e863ddb4..0000000000000 --- a/projects/plugins/debug-helper/changelog/remove-remnants-of-automated-code-coverage +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: removed -Comment: Remove `test-coverage` scripts and other remnants of automated code coverage. - - diff --git a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance b/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#2 b/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#2 deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#2 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#3 b/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#3 deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#3 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#4 b/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#4 deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#4 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#5 b/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#5 deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#5 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#6 b/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#6 deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#6 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#7 b/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#7 deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#7 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#8 b/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#8 deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#8 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#9 b/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#9 deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/debug-helper/changelog/renovate-lock-file-maintenance#9 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/debug-helper/changelog/renovate-major-symfony b/projects/plugins/debug-helper/changelog/renovate-major-symfony deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/debug-helper/changelog/renovate-major-symfony +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/debug-helper/changelog/update-build-action-version-handling b/projects/plugins/debug-helper/changelog/update-build-action-version-handling deleted file mode 100644 index c47cb18e82997..0000000000000 --- a/projects/plugins/debug-helper/changelog/update-build-action-version-handling +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Updated package dependencies. diff --git a/projects/plugins/debug-helper/changelog/update-rest-api-tester-prefix b/projects/plugins/debug-helper/changelog/update-rest-api-tester-prefix deleted file mode 100644 index 43368482ae60b..0000000000000 --- a/projects/plugins/debug-helper/changelog/update-rest-api-tester-prefix +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: changed - -Remove pre-defined prefix in the REST API tool. diff --git a/projects/plugins/debug-helper/changelog/update-wpcs b/projects/plugins/debug-helper/changelog/update-wpcs deleted file mode 100644 index 211a1cc7d9e1d..0000000000000 --- a/projects/plugins/debug-helper/changelog/update-wpcs +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fixed -Comment: PHPCS fixes for new WPCS sniffs. Should be no user-visible changes. - - diff --git a/projects/plugins/debug-helper/changelog/update-wpcs-pre-phpcbf b/projects/plugins/debug-helper/changelog/update-wpcs-pre-phpcbf deleted file mode 100644 index 8ae821ee1a3df..0000000000000 --- a/projects/plugins/debug-helper/changelog/update-wpcs-pre-phpcbf +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fixed -Comment: PHPCS: Fix most auto-fixable sniffs coming in new WPCS snapshot. No user-visible change to the project. - - diff --git a/projects/plugins/debug-helper/plugin.php b/projects/plugins/debug-helper/plugin.php index 714e9e7c5e05b..29e690b93d86f 100644 --- a/projects/plugins/debug-helper/plugin.php +++ b/projects/plugins/debug-helper/plugin.php @@ -3,7 +3,7 @@ * Plugin Name: Jetpack Debug Tools * Description: Give me a Jetpack connection, and I'll break it every way possible. * Author: Automattic - Jetpack Crew - * Version: 1.5.0-alpha + * Version: 1.5.0 * Text Domain: jetpack * * @package automattic/jetpack-debug-helper. @@ -33,7 +33,7 @@ * The plugin version. * Increase that if you do any edits to ensure refreshing the cached assets. */ -define( 'JETPACK_DEBUG_HELPER_VERSION', '1.5.0-alpha' ); +define( 'JETPACK_DEBUG_HELPER_VERSION', '1.5.0' ); /** * Include file names from the modules directory here. diff --git a/projects/plugins/jetpack/CHANGELOG.md b/projects/plugins/jetpack/CHANGELOG.md index d1e54bbfd86dd..8a91c4876e20c 100644 --- a/projects/plugins/jetpack/CHANGELOG.md +++ b/projects/plugins/jetpack/CHANGELOG.md @@ -2,33 +2,66 @@ ### This is a list detailing changes for all Jetpack releases. -## 11.9-beta - 2023-02-28 +## 12.0-a.1 - 2023-03-08 +### Enhancements +- Admin: fix submenu positioning in admin menu. [#28355] +- Blocks (beta): add a new beta Cookie Consent block to display a GDPR-compliant cookie consent widget on your site for your visitors. [#29197] +- SSO: add message to logout notice when SSO is enabled that gives a heads up to also log out of WordPress.com if they are on a shared computer. [#29235] +- Stats: updates the layout of the loading and some sections on the Stats page. [#29221] + +### Other changes +- Allow getting posts by ids in GET /posts response using include array [#29149] +- Blocks: register VideoPress video block based on the filtered extensions [#29207] +- Forms: move search into state, fix double fetch on search and paging [#29336] +- Forms: add form responses app and state into package [#29007] +- Pre-escape the ampersand in the default content of the cookie consent block [#29251] +- Adds basic ui to beta blogging-prompt block [#29189] +- Jetpack Forms: Added week/monthly props to sent message tracking [#28999] +- media summary: write to memo when no images found [#29326] +- Paid newsletters access panel would disappear on posts that have been previously published. This fixes the issue. [#29247] +- Removes unused conversation and dialogue blocks that never left beta [#29210] +- Sharing: remove unused variable [#29275] +- Shortcodes: removed jQuery dependency from Crowdsignal shortcodes [#29307] +- Temp disable Gutenberg subscribe block test until #29113 is fixed [#29280] +- Updated package dependencies. [#29216] +- VideoPress: pick and convert core/video VideoPress instances also from inner blocks [#29339] +- Writing prompts: marks prompt as answered when using a writing prompt block [#29214] + +## [11.9] - 2023-03-07 ### Enhancements - Assistant: add new card to highlight VaultPress Backup. [#28741] - Form block: add form field style synchronization for input fields. [#28988] - Related Posts: add support for font family in Related Posts block. [#29097] - Sharing: add Mastodon sharing button. [#28694] +- Stats: show new Jetpack Stats dashboard design by default. ### Improved compatibility -- Stats: add upgrade notice for Odyssey Stats. [#28828] +- Sharing: add spacebar as an option to open the "More" button overlay. [#29232] - VideoPress: add support for the `preload` or `preloadcontent` attribute to the VideoPress shortcode. [#28865] ### Bug fixes - Connection: revise Jetpack connection agreement text to comply with our User Agreement. [#28403] - Custom CSS: ensure the link to enable Custom CSS works in all languages. [#29202] +- Sharing: fix broken Tumblr button inside "More" button overlay. [#29231] +- Sharing: fix a JS error and adjust margin on Pinterest official button. [#29279] - Form block: increase form fields padding based on user-defined border-radius. [#28820] +- Form block: improve multiple choice field styles for the Twenty Twenty theme. [#29325] +- Form block: move field width settings, and remove placeholder field from multiple and single choice fields. [#29292] - Form block: remove body font normalization in contact-form module and package. [#29166] +- Form block: set defaults for Jetpack Forms CSS variables. [#29236] +- Form block: update form-styles script to run in the context of the Form block. [#29178] - Presentation shortcode: always add presentation container. [#29073] - Recommendations: avoid applying coupon codes from the Assistant on products with trial prices. [#29139] - Sharing buttons: fix display issues when choosing the icon-only option. [#29090] ### Other changes -- API: add wpcom/v2/form-responses endpoint, mapped from .com [#29043] -- API: fix a bug in list user endpoint when include_viewers is true. [#29068] - Admin Page: update link in Jetpack App card to include external link icon. [#29048] - Admin Page: use external icons for external links in support card. [#29050] +- API: add wpcom/v2/form-responses endpoint, mapped from .com [#29043] +- API: fix a bug in list user endpoint when include_viewers is true. [#29068] - Blocks: update scaffolding. [#29201] - Social: add groundwork for Social Image Generator. [#28737] +- Social: show Jetpack Social Advanced products under My Plan. [#29276] - Stats: moved new stats toggle logic to stats-admin. [#29064] - Tests: adapted the Sync test to WordPress Core changes in post deletion mechanics. [#29154] - Updated package dependencies. [#29117] @@ -36,6 +69,7 @@ - VideoPress: tidy registering VideoPress video block. [#29084] - Widget Visibility: switch to shared Analytics implementation. [#29181] - WPcom: add `is_wpcom_staging_site`, `wpcom_production_blog_id`, and `wpcom_staging_blog_ids` attributes to the site object. [#29192] +- WPcom: add wpcom/v3/blogging-prompts endpoint to support the upcoming writing prompts block. [#29182] - WPcom: consolidate selector logic in the launchpad save modal. [#29134] - WPcom: make sure the email field in the subscribe block is required. [#28995] - WPcom: prevent launchpad modal from rendering on top of the first post published modal. [#28989] @@ -7806,6 +7840,7 @@ Other bugfixes and enhancements at https://github.com/Automattic/jetpack/commits - Initial release [11.6]: https://wp.me/p1moTy-PLI +[11.9]: https://wp.me/p1moTy-RdX [11.8]: https://wp.me/p1moTy-QEM [11.7]: https://wp.me/p1moTy-Q9t [11.5]: https://wp.me/p1moTy-Ppq diff --git a/projects/plugins/jetpack/_inc/blogging-prompts.php b/projects/plugins/jetpack/_inc/blogging-prompts.php index 0690af331668c..3b3ce0d0fcc2a 100644 --- a/projects/plugins/jetpack/_inc/blogging-prompts.php +++ b/projects/plugins/jetpack/_inc/blogging-prompts.php @@ -47,6 +47,52 @@ function jetpack_setup_blogging_prompt_response( $post_id ) { add_action( 'wp_insert_post', 'jetpack_setup_blogging_prompt_response' ); +/** + * When a published posts answers a blogging prompt, store the prompt id in the post meta. + * + * @param int $post_id Post ID. + * @param WP_Post $post Post object. + * @param bool $update Whether this is an existing post being updated. + * @param null|WP_Post $post_before Null for new posts, the WP_Post object prior + * to the update for updated posts. + */ +function jetpack_mark_if_post_answers_blogging_prompt( $post_id, $post, $update, $post_before ) { + if ( ! $post instanceof WP_Post ) { + return; + } + + $post_type = isset( $post->post_type ) ? $post->post_type : null; + $post_content = isset( $post->post_content ) ? $post->post_content : null; + + if ( 'post' !== $post_type || ! $post_content ) { + return; + } + + $new_status = isset( $post->post_status ) ? $post->post_status : null; + $old_status = $post_before && isset( $post_before->post_status ) ? $post_before->post_status : null; + + // Make sure we are publishing a post, and it's not already published. + if ( 'publish' !== $new_status || 'publish' === $old_status ) { + return; + } + + $blocks = parse_blocks( $post->post_content ); + foreach ( $blocks as $block ) { + if ( 'jetpack/blogging-prompt' === $block['blockName'] ) { + $prompt_id = isset( $block['attrs']['promptId'] ) ? absint( $block['attrs']['promptId'] ) : null; + $has_prompt_tag = has_tag( 'dailyprompt', $post ) || ( $prompt_id && has_tag( "dailyprompt-{$prompt_id}", $post ) ); + + if ( $prompt_id && $has_prompt_tag && count( $blocks ) > 1 ) { + update_post_meta( $post->ID, '_jetpack_blogging_prompt_key', $prompt_id ); + } + + break; + } + } +} + +add_action( 'wp_after_insert_post', 'jetpack_mark_if_post_answers_blogging_prompt', 10, 4 ); + /** * Utility functions. */ diff --git a/projects/plugins/jetpack/_inc/crowdsignal-shortcode.js b/projects/plugins/jetpack/_inc/crowdsignal-shortcode.js index 62daea8b5635e..798a7220914c5 100644 --- a/projects/plugins/jetpack/_inc/crowdsignal-shortcode.js +++ b/projects/plugins/jetpack/_inc/crowdsignal-shortcode.js @@ -1,18 +1,22 @@ -( function ( d, c, j ) { - var crowdsignal_shortcode_options; +( function ( w, d, c, j ) { if ( - crowdsignal_shortcode_options && - crowdsignal_shortcode_options.script_url && + w.crowdsignal_shortcode_options && + w.crowdsignal_shortcode_options.script_url && ! d.getElementById( j ) ) { var pd = d.createElement( c ), s; pd.id = j; pd.async = true; - pd.src = crowdsignal_shortcode_options.script_url; + pd.src = w.crowdsignal_shortcode_options.script_url; s = d.getElementsByTagName( c )[ 0 ]; s.parentNode.insertBefore( pd, s ); - } else if ( typeof jQuery !== 'undefined' ) { - jQuery( d.body ).trigger( 'pd-script-load' ); + } else { + // In environments where jQuery is present, dispatch with jQuery. + if ( typeof w.jQuery !== 'undefined' ) { + w.jQuery( d.body ).trigger( 'pd-script-load' ); + } else { + d.body.dispatchEvent( new Event( 'pd-script-load' ) ); + } } -} )( document, 'script', 'pd-polldaddy-loader' ); +} )( window, document, 'script', 'pd-polldaddy-loader' ); diff --git a/projects/plugins/jetpack/_inc/lib/class-jetpack-ai-helper.php b/projects/plugins/jetpack/_inc/lib/class-jetpack-ai-helper.php index 9f3f03c46d7ee..d968a77b34a70 100644 --- a/projects/plugins/jetpack/_inc/lib/class-jetpack-ai-helper.php +++ b/projects/plugins/jetpack/_inc/lib/class-jetpack-ai-helper.php @@ -21,7 +21,7 @@ class Jetpack_AI_Helper { * * @var int */ - public static $text_completion_cooldown_seconds = 60; + public static $text_completion_cooldown_seconds = 15; /** * Cache images for a prompt for a month. @@ -154,14 +154,26 @@ public static function get_gpt_completion( $content, $post_id ) { \require_lib( 'openai' ); } - $result = ( new OpenAI( 'openai', array( 'post_id' => $post_id ) ) )->request_gpt_completion( $content ); + // Set the content for chatGPT endpoint + $data = array( + array( + 'role' => 'user', + 'content' => $content, + ), + ); + + $result = ( new OpenAI( 'openai', array( 'post_id' => $post_id ) ) )->request_chat_completion( $data ); + if ( is_wp_error( $result ) ) { return $result; } + + $response = $result->choices[0]->message->content; + // In case of Jetpack we are setting a transient on the WPCOM and not the remote site. I think the 'get_current_user_id' may default for the connection owner at this point but we'll deal with this later. - set_transient( self::transient_name_for_completion(), $result, self::$text_completion_cooldown_seconds ); + set_transient( self::transient_name_for_completion(), $response, self::$text_completion_cooldown_seconds ); self::mark_post_as_ai_assisted( $post_id ); - return $result; + return $response; } $response = Client::wpcom_json_api_request_as_user( diff --git a/projects/plugins/jetpack/_inc/lib/class.media-summary.php b/projects/plugins/jetpack/_inc/lib/class.media-summary.php index 63af8c29bb498..f2bd152a52565 100644 --- a/projects/plugins/jetpack/_inc/lib/class.media-summary.php +++ b/projects/plugins/jetpack/_inc/lib/class.media-summary.php @@ -96,6 +96,7 @@ public static function get( $post_id, $blog_id = 0, $args = array() ) { if ( $switched ) { restore_current_blog(); } + self::$cache[ $cache_key ] = $return; return $return; } diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v3-endpoint-blogging-prompts.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v3-endpoint-blogging-prompts.php index 7471323daae7b..a84473edc7a0e 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v3-endpoint-blogging-prompts.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v3-endpoint-blogging-prompts.php @@ -299,16 +299,14 @@ public function get_collection_params() { ), ); - $args['categories'] = $parent_args['categories']; - $args['categories_exclude'] = $parent_args['categories_exclude']; - $args['exclude'] = $parent_args['exclude']; - $args['include'] = $parent_args['include']; - $args['page'] = $parent_args['page']; - $args['per_page'] = $parent_args['per_page']; - $args['order'] = $parent_args['order']; - $args['order']['default'] = 'asc'; - $args['orderby'] = $parent_args['orderby']; - $args['search'] = $parent_args['search']; + $args['exclude'] = $parent_args['exclude']; + $args['include'] = $parent_args['include']; + $args['page'] = $parent_args['page']; + $args['per_page'] = $parent_args['per_page']; + $args['order'] = $parent_args['order']; + $args['order']['default'] = 'asc'; + $args['orderby'] = $parent_args['orderby']; + $args['search'] = $parent_args['search']; return $args; } @@ -400,7 +398,16 @@ public function proxy_request_to_wpcom( $request, $path = '' ) { return $response; } - return json_decode( wp_remote_retrieve_body( $response ) ); + $response_status = wp_remote_retrieve_response_code( $response ); + $response_body = json_decode( wp_remote_retrieve_body( $response ) ); + + if ( $response_status >= 400 ) { + $code = isset( $response_body->code ) ? $response_body->code : 'unknown_error'; + $message = isset( $response_body->message ) ? $response_body->message : __( 'An unknown error occurred.', 'jetpack' ); + return new WP_Error( $code, $message, array( 'status' => $response_status ) ); + } + + return $response_body; } /** diff --git a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php index b72143c64804b..3af51309109a5 100644 --- a/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php +++ b/projects/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php @@ -49,7 +49,7 @@ public function register_routes() { 'type' => 'string', 'required' => false, 'validate_callback' => function ( $param ) { - return in_array( $param, array( 'calypso', 'earn', 'gutenberg', 'gutenberg-wpcom' ), true ); + return in_array( $param, array( 'calypso', 'earn', 'earn-newsletter', 'gutenberg', 'gutenberg-wpcom' ), true ); }, ), 'is_editable' => array( diff --git a/projects/plugins/jetpack/_inc/polldaddy-shortcode.js b/projects/plugins/jetpack/_inc/polldaddy-shortcode.js index 73fc0e3a36ce5..204d6da2161af 100644 --- a/projects/plugins/jetpack/_inc/polldaddy-shortcode.js +++ b/projects/plugins/jetpack/_inc/polldaddy-shortcode.js @@ -1,74 +1,94 @@ -( function ( $ ) { - window.polldaddyshortcode = { +( function ( w, d ) { + w.polldaddyshortcode = { render: function () { - var ratings = $( 'div.pd-rating[data-settings]' ); - var polls = $( 'div.PDS_Poll[data-settings]' ); + const ratings = d.querySelectorAll( 'div.pd-rating[data-settings]' ); + const polls = d.querySelectorAll( 'div.PDS_Poll[data-settings]' ); - if ( polls ) { - $.each( polls, function () { - var poll = $( this ).data( 'settings' ); + polls.forEach( pollEl => { + const isInitialized = pollEl.getAttribute( 'data-pd-init-done' ); - if ( poll ) { - var poll_url = document.createElement( 'a' ); - poll_url.href = poll[ 'url' ]; - if ( - poll_url.hostname != 'secure.polldaddy.com' && - poll_url.hostname != 'static.polldaddy.com' - ) { - return false; - } - var pathname = poll_url.pathname; - if ( ! /\/?p\/\d+\.js/.test( pathname ) ) { - return false; - } - var wp_pd_js = document.createElement( 'script' ); - wp_pd_js.type = 'text/javascript'; - wp_pd_js.src = poll[ 'url' ]; - wp_pd_js.charset = 'utf-8'; - wp_pd_js.async = true; - document.getElementsByTagName( 'head' )[ 0 ].appendChild( wp_pd_js ); + if ( isInitialized ) { + return; + } + + pollEl.setAttribute( 'data-pd-init-done', '1' ); + const settings = pollEl.getAttribute( 'data-settings' ); + const poll = settings ? JSON.parse( settings ) : null; + + if ( poll ) { + let poll_url; + try { + poll_url = new URL( poll.url, 'https://invalid.tld' ); + } catch ( error ) { + return false; } - } ); - } + if ( + poll_url.hostname !== 'secure.polldaddy.com' && + poll_url.hostname !== 'static.polldaddy.com' + ) { + return false; + } + const pathname = poll_url.pathname; + if ( ! /\/?p\/\d+\.js/.test( pathname ) ) { + return false; + } + const wp_pd_js = d.createElement( 'script' ); + wp_pd_js.src = poll.url; + wp_pd_js.async = true; + d.head.appendChild( wp_pd_js ); + } + } ); - if ( ratings ) { - var script = ''; + if ( ratings.length ) { + let scriptContents = ''; - $.each( ratings, function () { - var rating = $( this ).data( 'settings' ); + ratings.forEach( ratingEl => { + const isInitialized = ratingEl.getAttribute( 'data-pd-init-done' ); + + if ( isInitialized ) { + return; + } + + ratingEl.setAttribute( 'data-pd-init-done', '1' ); + + const settings = ratingEl.getAttribute( 'data-settings' ); + const rating = settings ? JSON.parse( settings ) : null; if ( rating ) { - script += - 'PDRTJS_settings_' + - rating[ 'id' ] + - rating[ 'item_id' ] + - '=' + - rating[ 'settings' ] + - "; if ( typeof PDRTJS_RATING !== 'undefined' ){ if ( typeof PDRTJS_" + - rating[ 'id' ] + - rating[ 'item_id' ] + - "=='undefined' ){PDRTJS_" + - rating[ 'id' ] + - rating[ 'item_id' ] + - '= new PDRTJS_RATING( PDRTJS_settings_' + - rating[ 'id' ] + - rating[ 'item_id' ] + - ' );}}'; + scriptContents += ` + PDRTJS_settings_${ rating.id }${ rating.item_id } = ${ rating.settings }; + if ( typeof PDRTJS_RATING !== 'undefined' ) { + if ( typeof PDRTJS_${ rating.id }${ rating.item_id } === 'undefined' ) { + PDRTJS_${ rating.id }${ rating.item_id } = + new PDRTJS_RATING( PDRTJS_settings_${ rating.id }${ rating.item_id } ); + } + } + `; } } ); - if ( script.length > 0 ) - $( '#polldaddyRatings' ).after( - "' - ); + if ( scriptContents ) { + const anchorEl = d.querySelector( '#polldaddyRatings' ); + if ( anchorEl ) { + const script = d.createElement( 'script' ); + script.id = 'polldaddyDynamicRatings'; + script.text = scriptContents; + + anchorEl.after( script ); + } + } } }, }; - $( 'body' ).on( 'post-load pd-script-load', function () { - window.polldaddyshortcode.render(); - } ); - $( 'body' ).trigger( 'pd-script-load' ); -} )( jQuery ); + d.body.addEventListener( 'is.post-load', () => w.polldaddyshortcode.render() ); + + // In environments where jQuery is present, listen and dispatch with jQuery. + if ( typeof w.jQuery !== 'undefined' ) { + w.jQuery( d.body ).on( 'pd-script-load', () => w.polldaddyshortcode.render() ); + w.jQuery( d.body ).trigger( 'pd-script-load' ); + } else { + d.body.addEventListener( 'pd-script-load', () => w.polldaddyshortcode.render() ); + d.body.dispatchEvent( new Event( 'pd-script-load' ) ); + } +} )( window, document ); diff --git a/projects/plugins/jetpack/changelog/add-add-additional-string-param-for-newsletters b/projects/plugins/jetpack/changelog/add-add-additional-string-param-for-newsletters new file mode 100644 index 0000000000000..4af5880b8a496 --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-add-additional-string-param-for-newsletters @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Add additonal valid param to membership status endpoint for paid newsletters diff --git a/projects/plugins/jetpack/changelog/fix-out-of-bounds-submenu b/projects/plugins/jetpack/changelog/add-ai_use_chat_gpt similarity index 50% rename from projects/plugins/jetpack/changelog/fix-out-of-bounds-submenu rename to projects/plugins/jetpack/changelog/add-ai_use_chat_gpt index 8f1a8b1aab116..4801e63f88f56 100644 --- a/projects/plugins/jetpack/changelog/fix-out-of-bounds-submenu +++ b/projects/plugins/jetpack/changelog/add-ai_use_chat_gpt @@ -1,4 +1,4 @@ Significance: patch Type: enhancement -Fix submenus from being out of bounds +USe chat GPT API diff --git a/projects/plugins/jetpack/changelog/add-blogging-prompts-v3-endpoint b/projects/plugins/jetpack/changelog/add-blogging-prompts-v3-endpoint deleted file mode 100644 index b64b995dca705..0000000000000 --- a/projects/plugins/jetpack/changelog/add-blogging-prompts-v3-endpoint +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -Adds wpcom/v3/blogging-prompts endpoint to support the upcoming writing prompts block diff --git a/projects/plugins/jetpack/changelog/add-cookie-consent-block b/projects/plugins/jetpack/changelog/add-cookie-consent-block deleted file mode 100644 index 7916d41041c94..0000000000000 --- a/projects/plugins/jetpack/changelog/add-cookie-consent-block +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: enhancement - -Blocks: add a new Cookie Consent block. Display a GDPR-compliant cookie consent widget on your site for your visitors. diff --git a/projects/plugins/jetpack/changelog/add-response-inbox-store-endpoint#2 b/projects/plugins/jetpack/changelog/add-jetpack-forms-dashboard-view-switch similarity index 100% rename from projects/plugins/jetpack/changelog/add-response-inbox-store-endpoint#2 rename to projects/plugins/jetpack/changelog/add-jetpack-forms-dashboard-view-switch diff --git a/projects/plugins/jetpack/changelog/add-newsletter-prevent-visibility-caveats b/projects/plugins/jetpack/changelog/add-newsletter-prevent-visibility-caveats new file mode 100644 index 0000000000000..2093f401043e7 --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-newsletter-prevent-visibility-caveats @@ -0,0 +1,5 @@ +Significance: patch +Type: enhancement + +Add some UI to to prevent user to be confused when changing post visibility regarding Newsletter settings. +Add an intro for users. diff --git a/projects/plugins/jetpack/changelog/add-response-inbox-store-endpoint b/projects/plugins/jetpack/changelog/add-response-inbox-store-endpoint deleted file mode 100644 index a11069ef3b537..0000000000000 --- a/projects/plugins/jetpack/changelog/add-response-inbox-store-endpoint +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -Add form responses app and state into package (out of plugin) diff --git a/projects/plugins/jetpack/changelog/add-social-advanced b/projects/plugins/jetpack/changelog/add-social-advanced deleted file mode 100644 index 63a3ad633ec05..0000000000000 --- a/projects/plugins/jetpack/changelog/add-social-advanced +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -This change makes Jetpack Social Advanced products to be shown under my plans. diff --git a/projects/plugins/jetpack/changelog/add-tracking_of_forms_csv_exports b/projects/plugins/jetpack/changelog/add-tracking_of_forms_csv_exports new file mode 100644 index 0000000000000..eb3aac71ffbb9 --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-tracking_of_forms_csv_exports @@ -0,0 +1,4 @@ +Significance: minor +Type: other + +Added tracking of Jetpack Forms exports to CSV files. diff --git a/projects/plugins/jetpack/changelog/add-week_month_props_on_sent_tracks b/projects/plugins/jetpack/changelog/add-week_month_props_on_sent_tracks deleted file mode 100644 index 403fb363fc341..0000000000000 --- a/projects/plugins/jetpack/changelog/add-week_month_props_on_sent_tracks +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: other - -Jetpack Forms: Added week/monthly props to sent message tracking diff --git a/projects/plugins/search/changelog/update-move-delete-connection-owner-notice#2 b/projects/plugins/jetpack/changelog/add-zendesk-chat-module similarity index 79% rename from projects/plugins/search/changelog/update-move-delete-connection-owner-notice#2 rename to projects/plugins/jetpack/changelog/add-zendesk-chat-module index 9aa70e3ec1f75..a1c1831fa1ef7 100644 --- a/projects/plugins/search/changelog/update-move-delete-connection-owner-notice#2 +++ b/projects/plugins/jetpack/changelog/add-zendesk-chat-module @@ -1,5 +1,5 @@ Significance: patch -Type: changed +Type: other Comment: Updated composer.lock. diff --git a/projects/plugins/search/changelog/update-my-jetpack-remove-videopress-hybrid b/projects/plugins/jetpack/changelog/add-zendesk-chat-module#2 similarity index 79% rename from projects/plugins/search/changelog/update-my-jetpack-remove-videopress-hybrid rename to projects/plugins/jetpack/changelog/add-zendesk-chat-module#2 index 9aa70e3ec1f75..a1c1831fa1ef7 100644 --- a/projects/plugins/search/changelog/update-my-jetpack-remove-videopress-hybrid +++ b/projects/plugins/jetpack/changelog/add-zendesk-chat-module#2 @@ -1,5 +1,5 @@ Significance: patch -Type: changed +Type: other Comment: Updated composer.lock. diff --git a/projects/plugins/jetpack/changelog/change-forms-actions-component b/projects/plugins/jetpack/changelog/change-forms-actions-component new file mode 100644 index 0000000000000..a13265a55443d --- /dev/null +++ b/projects/plugins/jetpack/changelog/change-forms-actions-component @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Move BulkActionsMenu component inside Inbox, too tailored to be reused diff --git a/projects/plugins/jetpack/changelog/change-forms-responses-decouple-action-bar b/projects/plugins/jetpack/changelog/change-forms-responses-decouple-action-bar new file mode 100644 index 0000000000000..9be9f1c129dd0 --- /dev/null +++ b/projects/plugins/jetpack/changelog/change-forms-responses-decouple-action-bar @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Move action bar components out of inbox diff --git a/projects/plugins/jetpack/changelog/disable-subscribe-block-e2e b/projects/plugins/jetpack/changelog/disable-subscribe-block-e2e deleted file mode 100644 index 01b62948cb146..0000000000000 --- a/projects/plugins/jetpack/changelog/disable-subscribe-block-e2e +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: other - -Temp disable Gutenberg subscribe block test until #29113 is fixed diff --git a/projects/plugins/jetpack/changelog/enhancement-more-button-a11y b/projects/plugins/jetpack/changelog/enhancement-more-button-a11y deleted file mode 100644 index dba3a6dafebf6..0000000000000 --- a/projects/plugins/jetpack/changelog/enhancement-more-button-a11y +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -Added space bar as an option to open "More" button overlay diff --git a/projects/plugins/jetpack/changelog/fix-blogging-prompt-multiple-requests b/projects/plugins/jetpack/changelog/fix-blogging-prompt-multiple-requests new file mode 100644 index 0000000000000..854221ee252af --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-blogging-prompt-multiple-requests @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Writing Prompt block: prevent multiple requests when fetching prompt or tags diff --git a/projects/plugins/jetpack/changelog/fix-blogging-prompt-post-meta b/projects/plugins/jetpack/changelog/fix-blogging-prompt-post-meta new file mode 100644 index 0000000000000..5a992213c44fa --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-blogging-prompt-post-meta @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Fix writing prompt post meta not saving when post immediately published diff --git a/projects/plugins/jetpack/changelog/fix-contact-form-style-defaults b/projects/plugins/jetpack/changelog/fix-contact-form-style-defaults deleted file mode 100644 index a4aa7ec6f5b1c..0000000000000 --- a/projects/plugins/jetpack/changelog/fix-contact-form-style-defaults +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: bugfix - -Added defaults for Jetpack Forms CSS variables. diff --git a/projects/plugins/jetpack/changelog/fix-cookie-consent-block b/projects/plugins/jetpack/changelog/fix-cookie-consent-block deleted file mode 100644 index 569af6c1a13c0..0000000000000 --- a/projects/plugins/jetpack/changelog/fix-cookie-consent-block +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: bugfix - -Pre-escape the ampersand in the default content of the cookie consent block diff --git a/projects/plugins/jetpack/changelog/fix-forms-inbox-empty-results b/projects/plugins/jetpack/changelog/fix-forms-inbox-empty-results new file mode 100644 index 0000000000000..6e4f0c1e3043f --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-forms-inbox-empty-results @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Better handling for loading state and empty results diff --git a/projects/plugins/jetpack/changelog/fix-more-modal-spacing b/projects/plugins/jetpack/changelog/fix-more-modal-spacing deleted file mode 100644 index c8d84ea40a6b7..0000000000000 --- a/projects/plugins/jetpack/changelog/fix-more-modal-spacing +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: bugfix - -Removing awkward spacer after every second button within the sharing more button overlay diff --git a/projects/plugins/jetpack/changelog/fix-more-tumblr-button b/projects/plugins/jetpack/changelog/fix-more-tumblr-button deleted file mode 100644 index 7e07b08ceab8f..0000000000000 --- a/projects/plugins/jetpack/changelog/fix-more-tumblr-button +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: bugfix - -Fix broken Tumblr button inside "More" button overlay diff --git a/projects/plugins/jetpack/changelog/fix-notices-blogging-prompt b/projects/plugins/jetpack/changelog/fix-notices-blogging-prompt new file mode 100644 index 0000000000000..ecfdf94232a60 --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-notices-blogging-prompt @@ -0,0 +1,4 @@ +Significance: patch +Type: bugfix + +Blogging Prompts: avoid PHP notices with non-existing REST query paarameters. diff --git a/projects/plugins/jetpack/changelog/fix-pinterest-official-button b/projects/plugins/jetpack/changelog/fix-pinterest-official-button deleted file mode 100644 index 60ae6bdffc93d..0000000000000 --- a/projects/plugins/jetpack/changelog/fix-pinterest-official-button +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: bugfix - -Fix JS Error and adjust marginRight of Pinterest official button instead of width diff --git a/projects/plugins/jetpack/changelog/fix-remove-settings-jetpack-submenu-atomic b/projects/plugins/jetpack/changelog/fix-remove-settings-jetpack-submenu-atomic deleted file mode 100644 index b8148d687cae6..0000000000000 --- a/projects/plugins/jetpack/changelog/fix-remove-settings-jetpack-submenu-atomic +++ /dev/null @@ -1,3 +0,0 @@ -Significance: patch -Type: bugfix -Comment: Remove Jetpack submenu under Settings in Calypso on Atomic sites diff --git a/projects/plugins/jetpack/changelog/fix-sharing-button-height b/projects/plugins/jetpack/changelog/fix-sharing-button-height deleted file mode 100644 index a2ed68ac85158..0000000000000 --- a/projects/plugins/jetpack/changelog/fix-sharing-button-height +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: compat - -Update sharing button height from 34px to 32px diff --git a/projects/plugins/jetpack/changelog/fix-slideshow-icon b/projects/plugins/jetpack/changelog/fix-slideshow-icon new file mode 100644 index 0000000000000..1437647f9f74d --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-slideshow-icon @@ -0,0 +1,4 @@ +Significance: patch +Type: bugfix + +Make slideshow block's play and pause icons visible. diff --git a/projects/plugins/jetpack/changelog/init-release-cycle b/projects/plugins/jetpack/changelog/init-release-cycle index f5388cc81140d..af3a48652f757 100644 --- a/projects/plugins/jetpack/changelog/init-release-cycle +++ b/projects/plugins/jetpack/changelog/init-release-cycle @@ -1,5 +1,5 @@ Significance: patch Type: other -Comment: Init 12.0-a.0 +Comment: Init 12.0-a.2 diff --git a/projects/plugins/jetpack/changelog/remove-conversation-dialogue-blocks b/projects/plugins/jetpack/changelog/remove-conversation-dialogue-blocks deleted file mode 100644 index e2b793ccd6452..0000000000000 --- a/projects/plugins/jetpack/changelog/remove-conversation-dialogue-blocks +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: other - -Removes unused conversation and dialogue blocks that never left beta diff --git a/projects/plugins/jetpack/changelog/renovate-concurrently-7.x b/projects/plugins/jetpack/changelog/renovate-concurrently-7.x deleted file mode 100644 index 1eaea6a769e84..0000000000000 --- a/projects/plugins/jetpack/changelog/renovate-concurrently-7.x +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: other - -Updated package dependencies. diff --git a/projects/plugins/jetpack/changelog/renovate-jest-monorepo b/projects/plugins/jetpack/changelog/renovate-jest-monorepo deleted file mode 100644 index 1eaea6a769e84..0000000000000 --- a/projects/plugins/jetpack/changelog/renovate-jest-monorepo +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: other - -Updated package dependencies. diff --git a/projects/plugins/jetpack/changelog/stats-update-legacy-layouts b/projects/plugins/jetpack/changelog/stats-update-legacy-layouts deleted file mode 100644 index a02c9a2e6383a..0000000000000 --- a/projects/plugins/jetpack/changelog/stats-update-legacy-layouts +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -Updates the layout of the loading and "no JS" sections on the legacy Stats page. diff --git a/projects/plugins/jetpack/changelog/update-blogging-prompts-block-ui b/projects/plugins/jetpack/changelog/update-blogging-prompts-block-ui deleted file mode 100644 index cc6b3890ebede..0000000000000 --- a/projects/plugins/jetpack/changelog/update-blogging-prompts-block-ui +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: other - -Adds basic ui to beta blogging-prompt block diff --git a/projects/plugins/jetpack/changelog/update-blogging-prompts-error-handling b/projects/plugins/jetpack/changelog/update-blogging-prompts-error-handling new file mode 100644 index 0000000000000..7892b515cdc80 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-blogging-prompts-error-handling @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Adding better error handling to Writing Prompt block diff --git a/projects/plugins/jetpack/changelog/renovate-wordpress-monorepo b/projects/plugins/jetpack/changelog/update-clean-jetpack12.0-a.1-readme similarity index 52% rename from projects/plugins/jetpack/changelog/renovate-wordpress-monorepo rename to projects/plugins/jetpack/changelog/update-clean-jetpack12.0-a.1-readme index 1eaea6a769e84..9bf5d54ac0c02 100644 --- a/projects/plugins/jetpack/changelog/renovate-wordpress-monorepo +++ b/projects/plugins/jetpack/changelog/update-clean-jetpack12.0-a.1-readme @@ -1,4 +1,4 @@ Significance: patch Type: other -Updated package dependencies. +Readme fixup. diff --git a/projects/plugins/jetpack/changelog/update-form-fields-sidebar b/projects/plugins/jetpack/changelog/update-form-fields-sidebar deleted file mode 100644 index 58339601d1a44..0000000000000 --- a/projects/plugins/jetpack/changelog/update-form-fields-sidebar +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -Forms: Move field width settings and remove placeholder field from MC/SC fields diff --git a/projects/plugins/jetpack/changelog/update-form-styles-script b/projects/plugins/jetpack/changelog/update-form-styles-script deleted file mode 100644 index 35175ef307af1..0000000000000 --- a/projects/plugins/jetpack/changelog/update-form-styles-script +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -Forms: Update form-styles script to run in the context of the Form block diff --git a/projects/plugins/jetpack/changelog/update-getting-posts-by-ids b/projects/plugins/jetpack/changelog/update-getting-posts-by-ids deleted file mode 100644 index 27f2d87c4b952..0000000000000 --- a/projects/plugins/jetpack/changelog/update-getting-posts-by-ids +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -Allowing getting posts by ids in GET /posts response using include array diff --git a/projects/plugins/jetpack/changelog/update-jetpack-11.9-editorial b/projects/plugins/jetpack/changelog/update-jetpack-11.9-editorial deleted file mode 100644 index 2cbb1c53512e3..0000000000000 --- a/projects/plugins/jetpack/changelog/update-jetpack-11.9-editorial +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: other -Comment: Jetpack: 11.9-beta editorial - - diff --git a/projects/plugins/jetpack/changelog/update-mutiple-single-choice-field b/projects/plugins/jetpack/changelog/update-mutiple-single-choice-field new file mode 100644 index 0000000000000..5abdc617d3fa4 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-mutiple-single-choice-field @@ -0,0 +1,4 @@ +Significance: minor +Type: enhancement + +Forms: Multiple Choice and Single Choice fields redesign diff --git a/projects/plugins/jetpack/changelog/update-sharing-rm-count-var b/projects/plugins/jetpack/changelog/update-sharing-rm-count-var deleted file mode 100644 index 50608405e7b64..0000000000000 --- a/projects/plugins/jetpack/changelog/update-sharing-rm-count-var +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: other - -Sharing: remove unused variable diff --git a/projects/plugins/jetpack/changelog/update-sso-logout-heads-up-warning-notice b/projects/plugins/jetpack/changelog/update-sso-logout-heads-up-warning-notice deleted file mode 100644 index 24b5af55c0de6..0000000000000 --- a/projects/plugins/jetpack/changelog/update-sso-logout-heads-up-warning-notice +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -SSO: Add message to logout notice when SSO is enabled that gives a heads up to also log out of wpcom if they are on a shared computer. diff --git a/projects/plugins/jetpack/changelog/update-videopress-register-videopress-video-based-on-filtered-extensions b/projects/plugins/jetpack/changelog/update-videopress-register-videopress-video-based-on-filtered-extensions deleted file mode 100644 index 55a991fd9e4e4..0000000000000 --- a/projects/plugins/jetpack/changelog/update-videopress-register-videopress-video-based-on-filtered-extensions +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: enhancement - -Jetpack: register VideoPress video block based on the filtered extensions diff --git a/projects/plugins/jetpack/changelog/update-wp-tested-up-to b/projects/plugins/jetpack/changelog/update-wp-tested-up-to new file mode 100644 index 0000000000000..c76fb2decd582 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-wp-tested-up-to @@ -0,0 +1,4 @@ +Significance: patch +Type: compat + +General: indicate full compatibility with the latest version of WordPress, 6.2. diff --git a/projects/plugins/jetpack/composer.json b/projects/plugins/jetpack/composer.json index 5f7637ce4eb66..c851c572fcba3 100644 --- a/projects/plugins/jetpack/composer.json +++ b/projects/plugins/jetpack/composer.json @@ -100,7 +100,7 @@ "platform": { "ext-intl": "0.0.0" }, - "autoloader-suffix": "f11009ded9fc4592b6a05b61ce272b3c_jetpackⓥ12_0_a_0", + "autoloader-suffix": "f11009ded9fc4592b6a05b61ce272b3c_jetpackⓥ12_0_a_2", "allow-plugins": { "automattic/jetpack-autoloader": true, "automattic/jetpack-composer-plugin": true diff --git a/projects/plugins/jetpack/composer.lock b/projects/plugins/jetpack/composer.lock index e10ee13a9d898..e3f8562002d60 100644 --- a/projects/plugins/jetpack/composer.lock +++ b/projects/plugins/jetpack/composer.lock @@ -875,7 +875,7 @@ "dist": { "type": "path", "url": "../../packages/forms", - "reference": "69b0956e664edfbbc78b45665c652462d9d3a8de" + "reference": "ec6c161155b3cef7e43d45c3d715e0ef0417f6a2" }, "require": { "automattic/jetpack-assets": "@dev", @@ -897,7 +897,7 @@ "link-template": "https://github.com/automattic/jetpack-forms/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.7.x-dev" + "dev-trunk": "0.8.x-dev" }, "textdomain": "jetpack-forms", "version-constants": { @@ -1065,7 +1065,7 @@ "dist": { "type": "path", "url": "../../packages/import", - "reference": "dbd1e38ec065af14ccfc194b72d170f1aa90209a" + "reference": "37125a5f15779eea6046766a6a78b12d9de6b810" }, "require": { "automattic/jetpack-connection": "@dev" @@ -1083,7 +1083,7 @@ }, "autotagger": true, "branch-alias": { - "dev-trunk": "0.1.x-dev" + "dev-trunk": "0.2.x-dev" }, "textdomain": "jetpack-import", "version-constants": { @@ -1408,7 +1408,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "5c154d90af8faaf107ce35a8aedbe1025e2313d4" + "reference": "d8bfc30fbd87ba4b6ba30c604bc551ea88f9234f" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -1435,7 +1435,7 @@ "link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}" }, "branch-alias": { - "dev-trunk": "2.7.x-dev" + "dev-trunk": "2.8.x-dev" }, "version-constants": { "::PACKAGE_VERSION": "src/class-initializer.php" @@ -2278,7 +2278,7 @@ "dist": { "type": "path", "url": "../../packages/videopress", - "reference": "62b58b5e1fe678849ef0751ed05a6e71cedfa82a" + "reference": "94b32518050a387b8c926214809c806fd64d7dbb" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -2300,7 +2300,7 @@ "link-template": "https://github.com/Automattic/jetpack-videopress/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.11.x-dev" + "dev-trunk": "0.12.x-dev" }, "version-constants": { "::PACKAGE_VERSION": "src/class-package-version.php" diff --git a/projects/plugins/jetpack/extensions/blocks/ai-paragraph/edit.js b/projects/plugins/jetpack/extensions/blocks/ai-paragraph/edit.js index 26bfafa884782..eba7e709b699c 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-paragraph/edit.js +++ b/projects/plugins/jetpack/extensions/blocks/ai-paragraph/edit.js @@ -10,7 +10,7 @@ import { sprintf, __ } from '@wordpress/i18n'; import { name as aiParagraphBlockName } from './index'; // Maximum number of characters we send from the content -export const MAXIMUM_NUMBER_OF_CHARACTERS_SENT_FROM_CONTENT = 240; +export const MAXIMUM_NUMBER_OF_CHARACTERS_SENT_FROM_CONTENT = 1024; // Creates the prompt that will eventually be sent to OpenAI. It uses the current post title, content (before the actual AI block) - or a slice of it if too long, and tags + categories names export const createPrompt = ( @@ -39,11 +39,11 @@ export const createPrompt = ( if ( postTitle ) { prompt = sprintf( /** translators: This will be the beginning of a prompt that will be sent to OpenAI based on the post title. */ - __( "This is a post titled '%1$s' ", 'jetpack' ), + __( "Please help me write a short piece of a blog post titled '%1$s'", 'jetpack' ), postTitle ); } else { - prompt = __( 'This is a post', 'jetpack' ); + prompt = __( 'Please help me write a short piece of a blog post', 'jetpack' ); } if ( categoriesNames ) { @@ -56,9 +56,11 @@ export const createPrompt = ( prompt += sprintf( __( " and tagged '%1$s'", 'jetpack' ), tagsNames ); } + prompt += __( '. Please only output generated content ready for publishing.', 'jetpack' ); + if ( shorter_content ) { /** translators: This will be the end of a prompt that will be sent to OpenAI with the last MAXIMUM_NUMBER_OF_CHARACTERS_SENT_FROM_CONTENT characters of content.*/ - prompt += sprintf( __( ':\n\n … %s', 'jetpack' ), shorter_content ); // eslint-disable-line @wordpress/i18n-no-collapsible-whitespace + prompt += sprintf( __( ' Please continue from here:\n\n … %s', 'jetpack' ), shorter_content ); // eslint-disable-line @wordpress/i18n-no-collapsible-whitespace } return prompt.trim(); @@ -207,7 +209,7 @@ export default function Edit( { attributes, setAttributes, clientId } ) { data: data, } ) .then( res => { - const result = res.prompts[ 0 ].text.trim().replaceAll( '\n', '
    ' ); + const result = res.trim().replaceAll( '\n', '
    ' ); setAttributes( { content: result } ); setIsLoadingCompletion( false ); } ) diff --git a/projects/plugins/jetpack/extensions/blocks/ai-paragraph/test/edit.js b/projects/plugins/jetpack/extensions/blocks/ai-paragraph/test/edit.js index f94d27f7e7823..c73acbd2af05c 100644 --- a/projects/plugins/jetpack/extensions/blocks/ai-paragraph/test/edit.js +++ b/projects/plugins/jetpack/extensions/blocks/ai-paragraph/test/edit.js @@ -17,38 +17,42 @@ describe( 'AIParagraphEdit', () => { expect( createPrompt( '', [], '', '' ) ).toBeFalsy(); // Test contents - expect( createPrompt( 'title', [], '', '' ) ).toBe( "This is a post titled 'title'" ); + expect( createPrompt( 'title', [], '', '' ) ).toBe( + "Please help me write a short piece of a blog post titled 'title'. Please only output generated content ready for publishing." + ); expect( createPrompt( 'title', [ fakeBlock, { attributes: { whatever: 'content' } } ], '', '' ) - ).toBe( "This is a post titled 'title' :\n\n … content" ); + ).toBe( + "Please help me write a short piece of a blog post titled 'title'. Please only output generated content ready for publishing. Please continue from here:\n\n … content" + ); // Test that
    are being translated. And content trimmed expect( createPrompt( 'title', [ fakeBlockWithBr ], '', '' ) ).toBe( - "This is a post titled 'title' :\n\n … content\ncontent2" + "Please help me write a short piece of a blog post titled 'title'. Please only output generated content ready for publishing. Please continue from here:\n\n … content\ncontent2" ); expect( createPrompt( 'title', [ fakeBlock, fakeBlock ], 'cat 1', '' ) ).toBe( - "This is a post titled 'title' , published in categories 'cat 1':\n\n … content\ncontent" + "Please help me write a short piece of a blog post titled 'title', published in categories 'cat 1'. Please only output generated content ready for publishing. Please continue from here:\n\n … content\ncontent" ); // Test MAX content length expect( createPrompt( 'title', [ fakeBlockWithVeryLongContent ] ) ).toBe( - "This is a post titled 'title' :\n\n … " + + "Please help me write a short piece of a blog post titled 'title'. Please only output generated content ready for publishing. Please continue from here:\n\n … " + longContent.slice( -MAXIMUM_NUMBER_OF_CHARACTERS_SENT_FROM_CONTENT ) ); // Test only cats expect( createPrompt( '', [ fakeBlock ], 'cat1', 'tag1' ) ).toBe( - "This is a post, published in categories 'cat1' and tagged 'tag1':\n\n … content" + "Please help me write a short piece of a blog post, published in categories 'cat1' and tagged 'tag1'. Please only output generated content ready for publishing. Please continue from here:\n\n … content" ); expect( createPrompt( '', [ fakeBlock ], 'cat1, cat2', 'tag1' ) ).toBe( - "This is a post, published in categories 'cat1, cat2' and tagged 'tag1':\n\n … content" + "Please help me write a short piece of a blog post, published in categories 'cat1, cat2' and tagged 'tag1'. Please only output generated content ready for publishing. Please continue from here:\n\n … content" ); expect( createPrompt( '', [], 'cat1, cat2', 'tag1' ) ).toBe( - "This is a post, published in categories 'cat1, cat2' and tagged 'tag1'" + "Please help me write a short piece of a blog post, published in categories 'cat1, cat2' and tagged 'tag1'. Please only output generated content ready for publishing." ); expect( createPrompt( 'title', [], 'cat1, cat2', 'tag1' ) ).toBe( - "This is a post titled 'title' , published in categories 'cat1, cat2' and tagged 'tag1'" + "Please help me write a short piece of a blog post titled 'title', published in categories 'cat1, cat2' and tagged 'tag1'. Please only output generated content ready for publishing." ); } ); } ); diff --git a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/attributes.js b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/attributes.js index d90ba99f31f5a..23aa24a8666b8 100644 --- a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/attributes.js +++ b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/attributes.js @@ -2,7 +2,7 @@ export default { gravatars: { type: 'array', source: 'query', - selector: '.jetpack-blogging-prompts__answers-gravatar', + selector: '.jetpack-blogging-prompt__answers-gravatar', query: { url: { type: 'string', @@ -14,7 +14,7 @@ export default { prompt: { type: 'text', source: 'html', - selector: '.jetpack-blogging-prompts__prompt', + selector: '.jetpack-blogging-prompt__prompt', }, promptId: { type: 'number', @@ -27,4 +27,8 @@ export default { type: 'boolean', default: true, }, + tagsAdded: { + type: 'boolean', + default: false, + }, }; diff --git a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/blogging-prompt.php b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/blogging-prompt.php index 45ece826b2c59..047648ab1ccf2 100644 --- a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/blogging-prompt.php +++ b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/blogging-prompt.php @@ -1,13 +1,13 @@ __NAMESPACE__ . '\load_assets' ) - ); + if ( ( defined( 'IS_WPCOM' ) && IS_WPCOM ) || \Jetpack::is_connection_ready() ) { + Blocks::jetpack_register_block( + BLOCK_NAME, + array( 'render_callback' => __NAMESPACE__ . '\load_assets' ) + ); + } } add_action( 'init', __NAMESPACE__ . '\register_block' ); /** - * Blogging Prompts block registration/dependency declaration. + * Blogging Prompt block registration/dependency declaration. * - * @param array $attr Array containing the Blogging Prompts block attributes. - * @param string $content String containing the Blogging Prompts block content. + * @param array $attr Array containing the Blogging Prompt block attributes. + * @param string $content String containing the Blogging Prompt block content. * * @return string */ diff --git a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/edit.js b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/edit.js index 0b3a40954ef8a..4bf65ff75e0f5 100644 --- a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/edit.js +++ b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/edit.js @@ -1,22 +1,67 @@ import apiFetch from '@wordpress/api-fetch'; import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; -import { PanelBody, Spinner, ToggleControl, withNotices } from '@wordpress/components'; -import { useEffect, useState } from '@wordpress/element'; -import { __, _x } from '@wordpress/i18n'; +import { Button, PanelBody, Spinner, ToggleControl, withNotices } from '@wordpress/components'; +import { useEffect, useRef, useState } from '@wordpress/element'; +import { __, _x, sprintf } from '@wordpress/i18n'; import './editor.scss'; import icon from './icon'; +import { usePromptTags } from './use-prompt-tags'; -function BloggingPromptsBetaEdit( { attributes, noticeOperations, noticeUI, setAttributes } ) { +function BloggingPromptEdit( { attributes, noticeOperations, noticeUI, setAttributes } ) { + const fetchedPromptRef = useRef( false ); const [ isLoading, setLoading ] = useState( true ); - const { gravatars, prompt, promptId, showLabel, showResponses } = attributes; - const blockProps = useBlockProps( { className: 'jetpack-blogging-prompts' } ); + const { gravatars, prompt, promptId, showLabel, showResponses, tagsAdded } = attributes; + const blockProps = useBlockProps( { className: 'jetpack-blogging-prompt' } ); + const setTagsAdded = state => setAttributes( { tagsAdded: state } ); + + // Add the prompt tags to the post, if they haven't already been added. + usePromptTags( promptId, tagsAdded, setTagsAdded ); + + // Fetch the prompt by id, if present, otherwise the get the prompt for today. useEffect( () => { - // If not initially rendering the block, don't fetch new data. - if ( ! isLoading ) { + // Only fetch the prompt once. + if ( fetchedPromptRef.current ) { return; } + const retryPrompt = () => { + setLoading( true ); + fetchedPromptRef.current = false; + noticeOperations.removeAllNotices(); + }; + + const resetPrompt = () => { + setAttributes( { promptId: null } ); + retryPrompt(); + }; + + const errorMessage = message => ( + <> + { sprintf( + /* translators: %s is the error message. */ + __( 'Error while fetching prompt: %s', 'jetpack' ), + message + ) }{ ' ' } + + + ); + + const notFoundMessage = pId => ( + <> + { sprintf( + /* translators: %d is the prompt id. */ + __( 'Prompt with id %d not found.', 'jetpack' ), + pId + ) }{ ' ' } + + + ); + let path = '/wpcom/v3/blogging-prompts'; if ( promptId ) { @@ -31,6 +76,7 @@ function BloggingPromptsBetaEdit( { attributes, noticeOperations, noticeUI, setA path += `?after=--${ month }-${ day }&order=desc`; } + fetchedPromptRef.current = true; apiFetch( { path } ) .then( prompts => { const promptData = promptId ? prompts : prompts[ 0 ]; @@ -44,10 +90,14 @@ function BloggingPromptsBetaEdit( { attributes, noticeOperations, noticeUI, setA } ) .catch( error => { setLoading( false ); + const message = + error.code === 'rest_post_invalid_id' && promptId + ? notFoundMessage( promptId ) + : errorMessage( error.message ); noticeOperations.removeAllNotices(); - noticeOperations.createErrorNotice( error.message ); + noticeOperations.createErrorNotice( message ); } ); - }, [ isLoading, noticeOperations, promptId, setAttributes, setLoading ] ); + }, [ fetchedPromptRef, isLoading, noticeOperations, promptId, setAttributes, setLoading ] ); const onShowLabelChange = newValue => { setAttributes( { showLabel: newValue } ); @@ -79,23 +129,23 @@ function BloggingPromptsBetaEdit( { attributes, noticeOperations, noticeUI, setA const renderPrompt = () => ( <> { showLabel && ( -
    +
    { icon } { __( 'Daily writing prompt', 'jetpack' ) }
    ) } -
    { prompt }
    +
    { prompt }
    - { showResponses && ( -
    + { showResponses && promptId && ( +
    { gravatars && gravatars.slice( 0, 3 ).map( ( { url } ) => { return ( url && ( // eslint-disable-next-line jsx-a11y/alt-text { __( 'View all responses', 'jetpack' ) } @@ -123,7 +173,7 @@ function BloggingPromptsBetaEdit( { attributes, noticeOperations, noticeUI, setA { renderControls() } { isLoading ? ( -
    +
    ) : ( @@ -133,4 +183,4 @@ function BloggingPromptsBetaEdit( { attributes, noticeOperations, noticeUI, setA ); } -export default withNotices( BloggingPromptsBetaEdit ); +export default withNotices( BloggingPromptEdit ); diff --git a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/editor.js b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/editor.js index 3a91269f534ec..715ef970e850a 100644 --- a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/editor.js +++ b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/editor.js @@ -1,6 +1,3 @@ -/** - * Internal dependencies - */ import registerJetpackBlock from '../../shared/register-jetpack-block'; import { name, settings } from '.'; import './style.scss'; diff --git a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/editor.scss b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/editor.scss index 60c36e56ab7e8..53c736624dd3b 100644 --- a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/editor.scss +++ b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/editor.scss @@ -1,5 +1,5 @@ /** - * Editor styles for Blogging Prompts + * Editor styles for Blogging Prompt block. */ -.wp-block-jetpack-blogging-prompts-beta { } +.wp-block-jetpack-blogging-prompt { } diff --git a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/save.js b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/save.js index 48ce1e5073e98..1716f5f2fa87d 100644 --- a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/save.js +++ b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/save.js @@ -2,27 +2,27 @@ import { useBlockProps } from '@wordpress/block-editor'; import { __ } from '@wordpress/i18n'; import icon from './icon'; -function BloggingPromptsSave( { attributes } ) { +function BloggingPromptSave( { attributes } ) { const { gravatars, prompt, promptId, showLabel, showResponses } = attributes; - const blockProps = useBlockProps.save( { className: 'jetpack-blogging-prompts' } ); + const blockProps = useBlockProps.save( { className: 'jetpack-blogging-prompt' } ); return (
    { showLabel && ( -
    +
    { icon } { __( 'Daily writing prompt', 'jetpack' ) }
    ) } -
    { prompt }
    - { showResponses && ( -
    +
    { prompt }
    + { showResponses && promptId && ( +
    { gravatars.map( ( { url } ) => { return ( url && ( // eslint-disable-next-line jsx-a11y/alt-text { __( 'View all responses', 'jetpack' ) } @@ -43,4 +45,4 @@ function BloggingPromptsSave( { attributes } ) { ); } -export default BloggingPromptsSave; +export default BloggingPromptSave; diff --git a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/style.scss b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/style.scss index 8430ff3765440..4502929b012a7 100644 --- a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/style.scss +++ b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/style.scss @@ -1,15 +1,17 @@ -.jetpack-blogging-prompts { - border: 1px solid #dcdcde; +@import '@automattic/jetpack-base-styles/gutenberg-base-styles'; + +.jetpack-blogging-prompt { + border: 1px solid $gray-300; border-radius: 2px; padding: 24px; &__label { font-size: 14px; - margin-bottom: 16px; + margin-bottom: $grid-unit-20; svg { - height: 24px; - width: 24px; + height: $icon-size; + width: $icon-size; margin-right: 6px; vertical-align: bottom; } @@ -27,11 +29,11 @@ &__answers { font-size: 16px; - margin-top: 16px; + margin-top: $grid-unit-20; } &__answers-gravatar { - border: 2px solid white; + border: 2px solid $white; border-radius: 50%; height: 24px; vertical-align: middle; diff --git a/projects/plugins/jetpack/extensions/blocks/blogging-prompt/use-prompt-tags.js b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/use-prompt-tags.js new file mode 100644 index 0000000000000..1ee9e8a3d0d19 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/blogging-prompt/use-prompt-tags.js @@ -0,0 +1,120 @@ +import apiFetch from '@wordpress/api-fetch'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useEffect, useRef } from '@wordpress/element'; +import { escapeHTML } from '@wordpress/escape-html'; + +// Tries to create a tag or fetch it if it already exists. +// @link https://github.com/WordPress/gutenberg/blob/98b58d9042eda7590659c6cce2cf7916ba99aaa1/packages/editor/src/components/post-taxonomies/flat-term-selector.js#L55 +function findOrCreateTag( tagName ) { + const escapedTagName = escapeHTML( tagName ); + + return apiFetch( { + path: `/wp/v2/tags`, + method: 'POST', + data: { name: escapedTagName }, + } ).catch( error => { + if ( error.code !== 'term_exists' ) { + return Promise.reject( error ); + } + + return Promise.resolve( { + id: error.data.term_id, + name: tagName, + } ); + } ); +} + +export function usePromptTags( promptId, tagsAdded, setTagsAdded ) { + const { editPost } = useDispatch( 'core/editor' ); + + // Track the status of requests to create or fetch tags. + // Statuses are 'not-started', 'pending', 'fulfilled', or 'rejected'. + const promptTagRequestStatus = useRef( 'not-started' ); + + // Get information about tags for the edited post. + const { postType, postsSupportTags, tags, tagIds, tagsHaveResolved } = useSelect( select => { + const { getEditedPostAttribute } = select( 'core/editor' ); + const { getEntityRecords, getPostType, hasFinishedResolution } = select( 'core' ); + const _termIds = getEditedPostAttribute( 'tags' ); + + const query = { + _fields: 'id,name', + context: 'view', + include: _termIds?.join( ',' ), + per_page: -1, + }; + + return { + postType: getEditedPostAttribute( 'type' ), + postsSupportTags: getPostType( 'post' )?.taxonomies.includes( 'post_tag' ), + tagIds: _termIds || [], + tags: _termIds && _termIds.length ? getEntityRecords( 'taxonomy', 'post_tag', query ) : [], + tagsHaveResolved: + _termIds && _termIds.length + ? hasFinishedResolution( 'getEntityRecords', [ 'taxonomy', 'post_tag', query ] ) + : true, + }; + }, [] ); + + // Add the related prompt tags, if we're able and they haven't been added already. + useEffect( () => { + if ( + // We're only interested in tagging posts as writing prompt answers. + 'post' !== postType || + // Make sure tag support hasn't been disabled for posts. + ! postsSupportTags || + // Prompt tags are only added once (they can be removed by the user, if desired). + tagsAdded || + // Tags for the post have resolved. + ! tagsHaveResolved || + // We successfully fetched the prompt, otherwise there's no point in adding tags to the post. + ! promptId || + ! Array.isArray( tags ) + ) { + return; + } + + // Make sure we only try fetch prompt tag ids one time, even though the hook reruns on every component render. + if ( promptTagRequestStatus.current === 'not-started' ) { + // Add the prompt tags, if any are missing. + if ( + ! tags.some( t => t.name && t.name === 'dailyprompt' ) || + ! tags.some( t => t.name && t.name === `dailyprompt-${ promptId }` ) + ) { + promptTagRequestStatus.current = 'pending'; + + Promise.all( [ + findOrCreateTag( 'dailyprompt' ), + findOrCreateTag( `dailyprompt-${ promptId }` ), + ] ) + .then( tagResponses => { + const promptTagIds = tagResponses.map( tagResponse => tagResponse.id ); + editPost( { tags: [ ...tagIds, ...promptTagIds ] } ); + promptTagRequestStatus.current = 'fulfilled'; + } ) + .catch( error => { + console.error( error ); // eslint-disable-line no-console + promptTagRequestStatus.current = 'rejected'; + } ); + } else { + // Otherwise mark the tag request as finished. + promptTagRequestStatus.current = 'fulfilled'; + } + } + + if ( promptTagRequestStatus.current === 'fulfilled' ) { + setTagsAdded( true ); + } + }, [ + editPost, + postsSupportTags, + postType, + promptId, + promptTagRequestStatus, + setTagsAdded, + tags, + tagIds, + tagsAdded, + tagsHaveResolved, + ] ); +} diff --git a/projects/plugins/jetpack/extensions/blocks/contact-form/child-blocks.js b/projects/plugins/jetpack/extensions/blocks/contact-form/child-blocks.js index f7be51e816f98..4e8886b4f32f4 100644 --- a/projects/plugins/jetpack/extensions/blocks/contact-form/child-blocks.js +++ b/projects/plugins/jetpack/extensions/blocks/contact-form/child-blocks.js @@ -1,7 +1,9 @@ +import { InnerBlocks } from '@wordpress/block-editor'; import { createBlock, getBlockType } from '@wordpress/blocks'; import { Path } from '@wordpress/components'; import { Fragment } from '@wordpress/element'; import { __, _x } from '@wordpress/i18n'; +import { filter, isEmpty, map, trim } from 'lodash'; import { getIconColor } from '../../shared/block-icons'; import renderMaterialIcon from '../../shared/render-material-icon'; import JetpackField from './components/jetpack-field'; @@ -9,6 +11,7 @@ import JetpackFieldCheckbox from './components/jetpack-field-checkbox'; import JetpackFieldConsent from './components/jetpack-field-consent'; import JetpackDropdown from './components/jetpack-field-dropdown'; import JetpackFieldMultiple from './components/jetpack-field-multiple'; +import { JetpackFieldOptionEdit } from './components/jetpack-field-option'; import JetpackFieldTextarea from './components/jetpack-field-textarea'; import { useFormWrapper } from './util/form'; @@ -32,7 +35,7 @@ const FieldDefaults = { }, options: { type: 'array', - default: [ '' ], + default: [], }, defaultValue: { type: 'string', @@ -161,6 +164,49 @@ const FieldDefaults = { example: {}, }; +const OptionFieldDefaults = { + category: 'contact-form', + edit: JetpackFieldOptionEdit, + attributes: { + label: { + type: 'string', + }, + fieldType: { + enum: [ 'checkbox', 'radio' ], + default: 'checkbox', + }, + }, + supports: { + reusable: false, + html: false, + }, +}; + +const multiFieldV1 = fieldType => ( { + attributes: { + ...FieldDefaults.attributes, + label: { + type: 'string', + default: fieldType === 'checkbox' ? 'Choose several options' : 'Choose one option', + }, + }, + migrate: attributes => { + const blockName = `jetpack/field-option-${ fieldType }`; + const nonEmptyOptions = filter( attributes.options, o => ! isEmpty( trim( o ) ) ); + const newInnerBlocks = map( nonEmptyOptions, option => + createBlock( blockName, { + label: option, + } ) + ); + + attributes.options = []; + + return [ attributes, newInnerBlocks ]; + }, + isEligible: attr => attr.options && filter( attr.options, o => ! isEmpty( trim( o ) ) ).length, + save: () => null, +} ); + const getFieldLabel = ( { attributes, name: blockName } ) => { return null === attributes.label ? getBlockType( blockName ).title : attributes.label; }; @@ -508,6 +554,36 @@ export const childBlocks = [ edit: EditConsent, }, }, + { + name: 'field-option-checkbox', + settings: { + ...OptionFieldDefaults, + parent: [ 'jetpack/field-checkbox-multiple' ], + title: __( 'Multiple Choice Option', 'jetpack' ), + icon: renderMaterialIcon( + <> + + + ), + }, + }, + { + name: 'field-option-radio', + settings: { + ...OptionFieldDefaults, + parent: [ 'jetpack/field-radio' ], + title: __( 'Single Choice Option', 'jetpack' ), + icon: renderMaterialIcon( + + ), + }, + }, { name: 'field-checkbox-multiple', settings: { @@ -525,6 +601,7 @@ export const childBlocks = [ /> ), edit: editMultiField( 'checkbox' ), + save: () => , attributes: { ...FieldDefaults.attributes, label: { @@ -532,6 +609,7 @@ export const childBlocks = [ default: 'Choose several options', }, }, + deprecated: [ multiFieldV1( 'checkbox' ) ], }, }, { @@ -553,6 +631,7 @@ export const childBlocks = [ ), edit: editMultiField( 'radio' ), + save: () => , attributes: { ...FieldDefaults.attributes, label: { @@ -560,6 +639,7 @@ export const childBlocks = [ default: 'Choose one option', }, }, + deprecated: [ multiFieldV1( 'radio' ) ], }, }, { @@ -589,6 +669,10 @@ export const childBlocks = [ type: 'string', default: null, }, + options: { + type: 'array', + default: [ '' ], + }, }, }, }, diff --git a/projects/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-multiple.js b/projects/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-multiple.js index b5123fd49ac18..7ee3150cfc151 100644 --- a/projects/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-multiple.js +++ b/projects/plugins/jetpack/extensions/blocks/contact-form/components/jetpack-field-multiple.js @@ -1,15 +1,15 @@ -import { Button } from '@wordpress/components'; +import { InnerBlocks } from '@wordpress/block-editor'; import { compose, withInstanceId } from '@wordpress/compose'; -import { useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; import classnames from 'classnames'; import { useFormStyle } from '../util/form'; import { withSharedFieldAttributes } from '../util/with-shared-field-attributes'; import JetpackFieldControls from './jetpack-field-controls'; import JetpackFieldLabel from './jetpack-field-label'; -import JetpackOption from './jetpack-option'; import { useJetpackFieldStyles } from './use-jetpack-field-styles'; +const ALLOWED_BLOCKS = [ 'jetpack/field-option' ]; + function JetpackFieldMultiple( props ) { const { clientId, @@ -27,52 +27,19 @@ function JetpackFieldMultiple( props ) { } = props; const formStyle = useFormStyle( clientId ); + const innerBlocks = useSelect( + select => { + return select( 'core/block-editor' ).getBlock( clientId ).innerBlocks; + }, + [ clientId ] + ); + const classes = classnames( 'jetpack-field jetpack-field-multiple', { 'is-selected': isSelected, - 'has-placeholder': options.length, + 'has-placeholder': ( options && options.length ) || innerBlocks.length, } ); - const [ inFocus, setInFocus ] = useState( null ); - - const onChangeOption = ( key = null, option = null ) => { - const newOptions = options.slice( 0 ); - - if ( null === option ) { - // Remove a key - newOptions.splice( key, 1 ); - if ( key > 0 ) { - setInFocus( key - 1 ); - } - } else { - // update a key - newOptions.splice( key, 1, option ); - setInFocus( key ); // set the focus. - } - setAttributes( { options: newOptions } ); - }; - - const addNewOption = ( key = null ) => { - const newOptions = options.slice( 0 ); - let newInFocus = 0; - - if ( 'object' === typeof key ) { - newOptions.push( '' ); - newInFocus = newOptions.length - 1; - } else { - newOptions.splice( key + 1, 0, '' ); - newInFocus = key + 1; - } - - setInFocus( newInFocus ); - setAttributes( { options: newOptions } ); - }; - - const { blockStyle, fieldStyle } = useJetpackFieldStyles( attributes ); - const optionStyle = { - color: fieldStyle.color, - fontSize: fieldStyle.fontSize, - lineHeight: fieldStyle.lineHeight, - }; + const { blockStyle } = useJetpackFieldStyles( attributes ); return ( <> @@ -87,40 +54,16 @@ function JetpackFieldMultiple( props ) { label={ label } setAttributes={ setAttributes } isSelected={ isSelected } - resetFocus={ () => setInFocus( null ) } attributes={ attributes } style={ formStyle } /> -
      - { options.map( ( option, index ) => ( - - ) ) } - { isSelected && ( -
    1. - -
    2. - ) } -
    +
    + +
    { + const { attributes, clientId, name, onReplace, setAttributes } = props; + const { removeBlock } = useDispatch( 'core/block-editor' ); + const parentAttributes = useParentAttributes( clientId ); + const { optionStyle } = useJetpackFieldStyles( parentAttributes ); + + const siblingsCount = useSelect( + select => { + const blockEditor = select( 'core/block-editor' ); + const parentBlockId = first( blockEditor.getBlockParents( clientId, true ) ); + return blockEditor.getBlock( parentBlockId ).innerBlocks.length; + }, + [ clientId ] + ); + + const type = name.replace( 'jetpack/field-option-', '' ); + + const handleSplit = label => + createBlock( name, { + ...attributes, + clientId: label && attributes.label.indexOf( label ) === 0 ? attributes.clientId : undefined, + label, + } ); + + const handleDelete = () => { + if ( siblingsCount <= 1 ) { + return; + } + + removeBlock( clientId ); + }; + + return ( +
    + + { + setAttributes( { label: value } ); + } } + onRemove={ handleDelete } + onSplit={ handleSplit } + onReplace={ onReplace } + placeholder={ __( 'Add option…', 'jetpack' ) } + preserveWhiteSpace={ false } + withoutInteractiveFormatting + value={ attributes.label } + style={ optionStyle } + /> +
    + ); +}; diff --git a/projects/plugins/jetpack/extensions/blocks/contact-form/components/use-jetpack-field-styles.js b/projects/plugins/jetpack/extensions/blocks/contact-form/components/use-jetpack-field-styles.js index 96409b578d96a..432614c49831d 100644 --- a/projects/plugins/jetpack/extensions/blocks/contact-form/components/use-jetpack-field-styles.js +++ b/projects/plugins/jetpack/extensions/blocks/contact-form/components/use-jetpack-field-styles.js @@ -31,9 +31,16 @@ export const useJetpackFieldStyles = attributes => { lineHeight: attributes.lineHeight, }; + const optionStyle = { + color: fieldStyle.color, + fontSize: fieldStyle.fontSize, + lineHeight: fieldStyle.lineHeight, + }; + return { blockStyle, fieldStyle, labelStyle, + optionStyle, }; }; diff --git a/projects/plugins/jetpack/extensions/blocks/contact-form/edit.js b/projects/plugins/jetpack/extensions/blocks/contact-form/edit.js index b7aaa6bd10896..91075ffe77c5b 100644 --- a/projects/plugins/jetpack/extensions/blocks/contact-form/edit.js +++ b/projects/plugins/jetpack/extensions/blocks/contact-form/edit.js @@ -21,7 +21,7 @@ import { withDispatch, withSelect } from '@wordpress/data'; import { Fragment, useEffect, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import classnames from 'classnames'; -import { filter, get, map } from 'lodash'; +import { filter, get, isArray, map } from 'lodash'; import InspectorHint from '../../shared/components/inspector-hint'; import { childBlocks } from './child-blocks'; import CRMIntegrationSettings from './components/jetpack-crm-integration/jetpack-crm-integration-settings'; @@ -34,8 +34,16 @@ import SalesforceLeadFormSettings, { import { withStyleVariables } from './util/with-style-variables'; import defaultVariations from './variations'; +const validFields = filter( childBlocks, ( { settings } ) => { + return ( + ! settings.parent || + settings.parent === 'jetpack/contact-form' || + ( isArray( settings.parent ) && settings.parent.includes( 'jetpack/contact-form' ) ) + ); +} ); + const ALLOWED_BLOCKS = [ - ...map( childBlocks, block => `jetpack/${ block.name }` ), + ...map( validFields, block => `jetpack/${ block.name }` ), 'core/audio', 'core/columns', 'core/group', diff --git a/projects/plugins/jetpack/extensions/blocks/contact-form/editor.scss b/projects/plugins/jetpack/extensions/blocks/contact-form/editor.scss index 345e68c70901b..c57745b251848 100644 --- a/projects/plugins/jetpack/extensions/blocks/contact-form/editor.scss +++ b/projects/plugins/jetpack/extensions/blocks/contact-form/editor.scss @@ -321,7 +321,8 @@ height: 1rem; border-color: currentColor; opacity: 1; - margin: 0 0.75rem 0 0 + margin: 0 0.75rem 0 0; + pointer-events: none; } &.jetpack-field-multiple { @@ -351,6 +352,20 @@ .jetpack-field-consent__checkbox + .jetpack-field-label { line-height: normal; } + + .jetpack-field-option { + display: flex; + align-items: center; + justify-content: flex-start; + + > input { + margin: 0 5px 0 0; + } + + > .rich-text { + line-height: 2; + } + } } :where(:not(.contact-form)) > .jetpack-field { @@ -733,6 +748,10 @@ } } + &.jetpack-field-checkbox { + margin-top: 0; + } + .jetpack-field-dropdown { &__popover .rich-text { padding-left: calc(var(--notch-width) + 4px); diff --git a/projects/plugins/jetpack/extensions/blocks/contact-form/util/use-parent-attributes.js b/projects/plugins/jetpack/extensions/blocks/contact-form/util/use-parent-attributes.js new file mode 100644 index 0000000000000..b841c37968aa8 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/contact-form/util/use-parent-attributes.js @@ -0,0 +1,9 @@ +import { useSelect } from '@wordpress/data'; +import { first } from 'lodash'; + +export const useParentAttributes = clientId => + useSelect( select => { + const blockEditor = select( 'core/block-editor' ); + + return blockEditor.getBlockAttributes( first( blockEditor.getBlockParents( clientId, true ) ) ); + } ); diff --git a/projects/plugins/jetpack/extensions/blocks/cookie-consent/cookie-consent.php b/projects/plugins/jetpack/extensions/blocks/cookie-consent/cookie-consent.php index c6670aa3b364e..ea4fcf7a02d97 100644 --- a/projects/plugins/jetpack/extensions/blocks/cookie-consent/cookie-consent.php +++ b/projects/plugins/jetpack/extensions/blocks/cookie-consent/cookie-consent.php @@ -2,7 +2,7 @@ /** * Cookie-consent Block. * - * @since $$next-version$$ + * @since 12.0 * * @package automattic/jetpack */ @@ -20,7 +20,7 @@ * Should the block be registered? * In wp-admin, we only want to show the block in the site editor. * - * @since $$next-version$$ + * @since 12.0 * * @return bool */ diff --git a/projects/plugins/jetpack/extensions/blocks/slideshow/style.scss b/projects/plugins/jetpack/extensions/blocks/slideshow/style.scss index b55bd4c14632f..7b073c298b457 100644 --- a/projects/plugins/jetpack/extensions/blocks/slideshow/style.scss +++ b/projects/plugins/jetpack/extensions/blocks/slideshow/style.scss @@ -160,7 +160,7 @@ .wp-block-jetpack-slideshow_button-play, .wp-block-jetpack-slideshow_button-pause { - background-image: url( "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M6%2019h4V5H6v14zm8-14v14h4V5h-4z'%20fill='white'/%3E%3Cpath%20d='M0%200h24v24H0z'%20fill='none'/%3E%3C/svg%3E" ); + background-image: url( "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M6%2019h4V5H6v14zm8-14v14h4V5h-4z'%20fill='black'/%3E%3Cpath%20d='M0%200h24v24H0z'%20fill='none'/%3E%3C/svg%3E" ); display: none; margin-top: 0; position: absolute; @@ -171,7 +171,7 @@ .wp-block-jetpack-slideshow_button-play, .wp-block-jetpack-slideshow_autoplay-paused .wp-block-jetpack-slideshow_button-pause { - background-image: url( "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M8%205v14l11-7z'%20fill='white'/%3E%3Cpath%20d='M0 0h24v24H0z'%20fill='none'/%3E%3C/svg%3E" ); + background-image: url( "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='24'%20height='24'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M8%205v14l11-7z'%20fill='black'/%3E%3Cpath%20d='M0 0h24v24H0z'%20fill='none'/%3E%3C/svg%3E" ); } &[data-autoplay='true'] .wp-block-jetpack-slideshow_button-pause { diff --git a/projects/plugins/jetpack/extensions/blocks/subscriptions/panel.js b/projects/plugins/jetpack/extensions/blocks/subscriptions/panel.js index 570c7ed3b2038..3843eb0defc3f 100644 --- a/projects/plugins/jetpack/extensions/blocks/subscriptions/panel.js +++ b/projects/plugins/jetpack/extensions/blocks/subscriptions/panel.js @@ -14,11 +14,25 @@ import InspectorNotice from '../../shared/components/inspector-notice'; import { getSubscriberCounts } from './api'; import './panel.scss'; import { META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS } from './constants'; -import { NewsletterAccess, accessOptions } from './settings'; +import { NewsletterAccess, accessOptions, MisconfigurationWarning } from './settings'; import { isNewsletterFeatureEnabled } from './utils'; + +function AccessLevelSelectorPanel( { setPostMeta, accessLevel } ) { + if ( ! isNewsletterFeatureEnabled() ) { + return null; + } + + return ( + + + + ); +} + export default function SubscribePanels() { const [ subscriberCount, setSubscriberCount ] = useState( null ); - const [ postMeta = [], setPostMeta ] = useEntityProp( 'postType', 'post', 'meta' ); + const postType = useSelect( select => select( editorStore ).getCurrentPostType(), [] ); + const [ postMeta = [], setPostMeta ] = useEntityProp( 'postType', postType, 'meta' ); const accessLevel = postMeta[ META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS ] ?? Object.keys( accessOptions )[ 0 ]; @@ -31,27 +45,34 @@ export default function SubscribePanels() { } ); }, [] ); + // Can be “private”, “password”, or “public”. + const postVisibility = useSelect( select => select( 'core/editor' ).getEditedPostVisibility() ); + const showMisconfigurationMessage = postVisibility !== 'public' && accessLevel !== 'everybody'; + // Only show this for posts for now (subscriptions are only available on posts). - const postType = useSelect( select => select( editorStore ).getCurrentPostType(), [] ); const postWasEverPublished = useSelect( select => select( editorStore ).getEditedPostAttribute( 'meta' )?.jetpack_post_was_ever_published, [] ); - if ( 'post' !== postType ) { + // Subscriptions will not be triggered on private sites (on WordPress.com simple and WoA), + // nor on sites that have not been launched yet. + if ( isPrivateSite() || isComingSoon() ) { return null; } - // Subscriptions will not be triggered for a post that was already published in the past - if ( postWasEverPublished ) { + // Subscriptions are only available for posts. Additionally, we will allow access level selector for pages. + // TODO: Make it available for pages later. + if ( postType !== 'post' ) { return null; } - // Subscriptions will not be triggered on private sites (on WordPress.com simple and WoA), - // nor on sites that have not been launched yet. - if ( isPrivateSite() || isComingSoon() ) { - return null; + // Subscriptions will not be triggered for a post that was already published in the past and the email was sent. + // We still need to render the access level selector, as historical posts need a way to edit their access level for people visiting them on the web. + // TODO: Additionally, pages also can be protected. They will not send an email, but can be a resource that needs the acces selector. + if ( postWasEverPublished ) { + return ; } // Do not show any panels when we have no info about the subscriber count, or it is too low. @@ -63,14 +84,10 @@ export default function SubscribePanels() { } const showNotices = Number.isFinite( subscriberCount ) && subscriberCount > 0; + return ( <> - { isNewsletterFeatureEnabled() && ( - - - - ) } - + } > - { showNotices && ( + { isNewsletterFeatureEnabled() && showMisconfigurationMessage && ( + <> + +
    + + ) } + { ! showMisconfigurationMessage && showNotices && ( { createInterpolateElement( followerCount !== 0 diff --git a/projects/plugins/jetpack/extensions/blocks/subscriptions/settings.js b/projects/plugins/jetpack/extensions/blocks/subscriptions/settings.js index df7f800616457..d489998a5e4c8 100644 --- a/projects/plugins/jetpack/extensions/blocks/subscriptions/settings.js +++ b/projects/plugins/jetpack/extensions/blocks/subscriptions/settings.js @@ -1,11 +1,17 @@ +import { getRedirectUrl } from '@automattic/jetpack-components'; // eslint-disable-next-line wpcalypso/no-unsafe-wp-apis import { __experimentalInspectorPopoverHeader as InspectorPopoverHeader } from '@wordpress/block-editor'; -import { Button, PanelRow, Dropdown, VisuallyHidden, Flex, FlexBlock } from '@wordpress/components'; +import { Flex, FlexBlock, Button, PanelRow, Dropdown, VisuallyHidden } from '@wordpress/components'; import { useInstanceId } from '@wordpress/compose'; +import { useSelect } from '@wordpress/data'; import { PostVisibilityCheck } from '@wordpress/editor'; +import { createInterpolateElement } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; +import InspectorNotice from '../../shared/components/inspector-notice'; import { META_NAME_FOR_POST_LEVEL_ACCESS_SETTINGS } from './constants'; +import './settings.scss'; + export const accessOptions = { everybody: { label: __( 'Everybody', 'jetpack' ), @@ -21,6 +27,22 @@ export const accessOptions = { }, }; +export function MisconfigurationWarning( { accessLevel } ) { + return ( +
    + { sprintf( + /* translators: %1$s: visibility label for the newsletter, %2$s: label for setting "everybody". this is a warning in the newsletter when posts have a private or password-protected visibility */ + __( + 'Private or password-protected posts cannot be assigned a newsletter setting of "%1$s". Please update the setting to "%2$s", or update the post visibility setting.', + 'jetpack' + ), + accessOptions[ accessLevel ].label, + accessOptions.everybody.label + ) } +
    + ); +} + function NewsletterAccessChoices( { accessLevel, onChange } ) { const instanceId = useInstanceId( NewsletterAccessChoices ); return ( @@ -66,54 +88,105 @@ export function NewsletterAccess( { accessLevel, setPostMeta, withModal = true } } const accessLabel = accessOptions[ accessLevel ]?.label; + // Can be “private”, “password”, or “public”. + const postVisibility = useSelect( select => select( 'core/editor' ).getEditedPostVisibility() ); + const postVisibilityIsPublic = postVisibility === 'public'; + + const showVisibilityRestrictedMessage = ! postVisibilityIsPublic && accessLevel === 'everybody'; + const showMisconfigurationMessage = ! postVisibilityIsPublic && accessLevel !== 'everybody'; + return ( ( - - { + + { canEdit && withModal && showVisibilityRestrictedMessage && ( - { __( 'Access', 'jetpack' ) } + + { + /* translators: this is a warning in the newsletter when posts have a private or password-protected visibility */ + __( + 'Private or password-protected posts cannot be assigned as Subscribers-only.', + 'jetpack' + ) + } + - } - { ! canEdit && { accessLabel } } - { withModal && canEdit && ( + ) } + + { canEdit && + withModal /* to prevent displaying in pre-publish panel */ && + showMisconfigurationMessage && ( + + + + ) } + + - ( - - ) } - renderContent={ ( { onClose } ) => ( -
    - - -
    - ) } - /> + { __( 'Access', 'jetpack' ) }
    - ) } + { ( ! canEdit || showVisibilityRestrictedMessage ) && { accessLabel } } + { ! showVisibilityRestrictedMessage && withModal && canEdit && ( + + ( + + ) } + renderContent={ ( { onClose } ) => ( +
    + + +
    + ) } + /> +
    + ) } - { ! withModal && canEdit && ( + { ! showVisibilityRestrictedMessage && ! withModal && canEdit && ( + + + + ) } +
    + { withModal && ( - + + { createInterpolateElement( + /* translators: basic information about the newsletter visibility */ + __( 'Restrict your post to subscribers. Learn more.', 'jetpack' ), + { + a: ( + + ), + } + ) } + ) }
    diff --git a/projects/plugins/jetpack/extensions/blocks/subscriptions/settings.scss b/projects/plugins/jetpack/extensions/blocks/subscriptions/settings.scss new file mode 100644 index 0000000000000..553484f83c027 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/subscriptions/settings.scss @@ -0,0 +1,16 @@ +@import '@automattic/jetpack-base-styles/gutenberg-base-styles'; + +.jetpack-inspector-notice { + span.jetpack-subscribe-notice-visibility{ + margin: 0; + padding: 0; + width: 100%; + } +} + +.jetpack-subscribe-notice-misconfiguration.warning { + color: $alert-red; + border: solid 1px $alert-red; + padding: 1em; + text-align: justify; +} diff --git a/projects/plugins/jetpack/extensions/blocks/videopress/v6-transform/components/transform-control/index.js b/projects/plugins/jetpack/extensions/blocks/videopress/v6-transform/components/transform-control/index.js index c95b62c35a488..9014c476a581f 100644 --- a/projects/plugins/jetpack/extensions/blocks/videopress/v6-transform/components/transform-control/index.js +++ b/projects/plugins/jetpack/extensions/blocks/videopress/v6-transform/components/transform-control/index.js @@ -18,12 +18,58 @@ import { ReactElement } from 'react'; import { isVideoPressBlockBasedOnAttributes } from '../../../utils'; import styles from './styles.module.scss'; +const videoPressVideoBlocks = { instances: [] }; + +// eslint-disable-next-line jsdoc/require-returns-check +/** + * Recursively get all core/video VideoPress (aka v5) block instances + * + * @param {Array} blocks - Array of blocks + * @param {boolean} root - Whether it is the root block + * @param {number} level - Nesting level in the block tree + * @returns {Array} Array of VideoPress blocks + */ +const getAllCoreVideoVideoPressVideoBlocks = ( blocks = [], root = false, level = 0 ) => { + if ( root ) { + // Clear the instances when it's called from the root block. + videoPressVideoBlocks.instances = []; + } + + blocks.forEach( block => { + if ( block.innerBlocks.length ) { + // Recursively call increasing the nesting level. + getAllCoreVideoVideoPressVideoBlocks( block.innerBlocks, false, level + 1 ); + return; + } + + const { clientId, name, attributes } = block; + if ( name === 'core/video' && isVideoPressBlockBasedOnAttributes( attributes ) ) { + videoPressVideoBlocks.instances.push( { clientId, name, attributes } ); + } + } ); + + /* + * Level zero is the first call, + * but also is the last of the recursive calls. + * Return the collected block instances when it's the last one. + */ + if ( level === 0 ) { + return videoPressVideoBlocks.instances; + } +}; + /** * React component that renders a block conversion control * - * @returns {ReactElement} - Transform panel component. + * @param {object} props - Component props + * @param {string} props.clientId - Block client ID + * @param {object} props.attributes - Block attributes + * @returns {ReactElement} Transform panel component. */ -export default function TransformControl() { +export default function TransformControl( { + clientId: currentBlockClientId, + attributes: currentBlockAttributes, +} ) { const postId = useSelect( select => select( editorStore ).getCurrentPostId() ); const { getBlocks } = useSelect( blockEditorStore ); @@ -31,9 +77,21 @@ export default function TransformControl() { const { tracks } = useAnalytics(); const handleTransformAll = () => { - const blocks = getBlocks(); + const allV5Instances = getAllCoreVideoVideoPressVideoBlocks( getBlocks(), true ); + if ( ! allV5Instances?.length ) { + return; + } + + // Ensure the current block is included in the list. + if ( ! allV5Instances.find( block => block.clientId === currentBlockClientId ) ) { + allV5Instances.push( { + clientId: currentBlockClientId, + name: 'core/video', + currentBlockAttributes, + } ); + } - blocks.forEach( block => { + allV5Instances.forEach( block => { const { clientId, name, attributes } = block; if ( name === 'core/video' && isVideoPressBlockBasedOnAttributes( attributes ) ) { replaceBlock( clientId, createBlock( 'videopress/video', attributes ) ); diff --git a/projects/plugins/jetpack/extensions/blocks/videopress/v6-transform/edit.js b/projects/plugins/jetpack/extensions/blocks/videopress/v6-transform/edit.js index ce45fb1128eaf..aedbe12818534 100644 --- a/projects/plugins/jetpack/extensions/blocks/videopress/v6-transform/edit.js +++ b/projects/plugins/jetpack/extensions/blocks/videopress/v6-transform/edit.js @@ -18,7 +18,7 @@ const withV6TransformEdit = createHigherOrderComponent( BlockEdit => props => { return ( <> - + diff --git a/projects/plugins/jetpack/extensions/shared/components/inspector-notice/index.jsx b/projects/plugins/jetpack/extensions/shared/components/inspector-notice/index.jsx index 7a4c3edbc237b..4a19b5cba20a8 100644 --- a/projects/plugins/jetpack/extensions/shared/components/inspector-notice/index.jsx +++ b/projects/plugins/jetpack/extensions/shared/components/inspector-notice/index.jsx @@ -1,9 +1,9 @@ import './style.scss'; -export default function InspectorNotice( { children } ) { +export default function InspectorNotice( { children, spanClass } ) { return (
    - { children } + { children }
    ); } diff --git a/projects/plugins/jetpack/jetpack.php b/projects/plugins/jetpack/jetpack.php index facd96e4aa230..d0577fbe2e191 100644 --- a/projects/plugins/jetpack/jetpack.php +++ b/projects/plugins/jetpack/jetpack.php @@ -4,7 +4,7 @@ * Plugin URI: https://jetpack.com * Description: Security, performance, and marketing tools made by WordPress experts. Jetpack keeps your site protected so you can focus on more important things. * Author: Automattic - * Version: 12.0-a.0 + * Version: 12.0-a.2 * Author URI: https://jetpack.com * License: GPL2+ * Text Domain: jetpack @@ -32,7 +32,7 @@ define( 'JETPACK__MINIMUM_WP_VERSION', '6.0' ); define( 'JETPACK__MINIMUM_PHP_VERSION', '5.6' ); -define( 'JETPACK__VERSION', '12.0-a.0' ); +define( 'JETPACK__VERSION', '12.0-a.2' ); /** * Constant used to fetch the connection owner token diff --git a/projects/plugins/jetpack/modules/contact-form/css/grunion.css b/projects/plugins/jetpack/modules/contact-form/css/grunion.css index 4d710748a4d6d..4c49ae71d8855 100644 --- a/projects/plugins/jetpack/modules/contact-form/css/grunion.css +++ b/projects/plugins/jetpack/modules/contact-form/css/grunion.css @@ -44,7 +44,7 @@ height: 200px; } -.contact-form .grunion-field { +.contact-form :where(.grunion-field[type="text"], .grunion-field.textarea) { padding-left: max(var(--jetpack--contact-form--input-padding-left, 16px), var(--jetpack--contact-form--border-radius)); padding-right: max(var(--jetpack--contact-form--input-padding-left, 16px), var(--jetpack--contact-form--border-radius)); } @@ -59,11 +59,14 @@ min-width: 150px; } -.contact-form input[type='radio'], -.contact-form input[type='checkbox'] { +.contact-form :where(input[type='radio'], input[type='checkbox']) { width: 1rem; height: 1rem; float: none; +} + +.contact-form input[type='radio'], +.contact-form input[type='checkbox'] { margin: 0 0.75rem 0 0; } @@ -99,14 +102,14 @@ font-weight: normal; display: inline-flex; align-items: center; - line-height: 1.5; + line-height: 1; } .contact-form .grunion-checkbox-multiple-options, .contact-form .grunion-radio-options { display: flex; flex-direction: column; - gap: 0.25em + gap: 12px; } .contact-form label span { @@ -340,6 +343,12 @@ padding: 0; line-height: normal; box-shadow: 0 2px 6px rgb(0 0 0 / 5%); + list-style: none; + margin: 0; +} + +.contact-form .contact-form-dropdown__menu .ui-menu-item { + margin: 0; } .contact-form .contact-form-dropdown__menu .ui-menu { @@ -377,7 +386,7 @@ .contact-form .is-style-animated .grunion-field-wrap .grunion-radio-options { padding: var(--jetpack--contact-form--input-padding, 16px); - padding-top: calc(var(--jetpack--contact-form--input-padding, 16px) + 4px); + padding-top: calc(var(--jetpack--contact-form--input-padding-top, 16px) + 4px); flex-grow: 1; } diff --git a/projects/plugins/jetpack/modules/contact-form/grunion-contact-form.php b/projects/plugins/jetpack/modules/contact-form/grunion-contact-form.php index 7e2c45e23a02c..a89a45e6288bd 100644 --- a/projects/plugins/jetpack/modules/contact-form/grunion-contact-form.php +++ b/projects/plugins/jetpack/modules/contact-form/grunion-contact-form.php @@ -480,12 +480,24 @@ private static function register_contact_form_blocks() { 'render_callback' => array( __CLASS__, 'gutenblock_render_field_checkbox_multiple' ), ) ); + Blocks::jetpack_register_block( + 'jetpack/field-option-checkbox', + array( + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_option' ), + ) + ); Blocks::jetpack_register_block( 'jetpack/field-radio', array( 'render_callback' => array( __CLASS__, 'gutenblock_render_field_radio' ), ) ); + Blocks::jetpack_register_block( + 'jetpack/field-option-radio', + array( + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_option' ), + ) + ); Blocks::jetpack_register_block( 'jetpack/field-select', array( @@ -662,6 +674,19 @@ public static function gutenblock_render_field_checkbox_multiple( $atts, $conten return Grunion_Contact_Form::parse_contact_field( $atts, $content ); } + /** + * Render the multiple choice field option. + * + * @param array $atts - the block attributes. + * @param string $content - html content. + * + * @return string HTML for the contact form field. + */ + public static function gutenblock_render_field_option( $atts, $content ) { + $atts = self::block_attributes_to_shortcode_attributes( $atts, 'field-option' ); + return Grunion_Contact_Form::parse_contact_field( $atts, $content ); + } + /** * Render the radio button field. * @@ -1003,6 +1028,10 @@ public function insert_feedback_filter( $data, $postarr ) { public function add_shortcode() { add_shortcode( 'contact-form', array( 'Grunion_Contact_Form', 'parse' ) ); add_shortcode( 'contact-field', array( 'Grunion_Contact_Form', 'parse_contact_field' ) ); + + // We need 'contact-field-option' to be registered, so it's included to the get_shortcode_regex() method + // But we don't need a callback because we're handling contact-field-option manually + add_shortcode( 'contact-field-option', '__return_null' ); } /** @@ -1974,9 +2003,58 @@ public function download_feedback_as_csv() { } fclose( $output ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose + + $this->record_tracks_event( 'forms_export_responses', array( 'format' => 'csv' ) ); exit(); } + /** + * Send an event to Tracks + * + * @param string $event_name - the name of the event. + * @param array $event_props - event properties to send. + * + * @return null|void + */ + private function record_tracks_event( $event_name, $event_props ) { + /* + * Event details. + */ + $event_user = wp_get_current_user(); + + /* + * Record event. + * We use different libs on wpcom and Jetpack. + */ + if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { + $event_name = 'wpcom_' . $event_name; + $event_props['blog_id'] = get_current_blog_id(); + // logged out visitor, record event with blog owner. + if ( empty( $event_user->ID ) ) { + $event_user_id = wpcom_get_blog_owner( $event_props['blog_id'] ); + $event_user = get_userdata( $event_user_id ); + } + + require_lib( 'tracks/client' ); + tracks_record_event( $event_user, $event_name, $event_props ); + } else { + $user_connected = ( new \Automattic\Jetpack\Connection\Manager( 'jetpack-forms' ) )->is_user_connected( get_current_user_id() ); + if ( ! $user_connected ) { + return; + } + // logged out visitor, record event with Jetpack master user. + if ( empty( $event_user->ID ) ) { + $master_user_id = Jetpack_Options::get_option( 'master_user' ); + if ( ! empty( $master_user_id ) ) { + $event_user = get_userdata( $master_user_id ); + } + } + + $tracking = new \Automattic\Jetpack\Tracking(); + $tracking->record_user_event( $event_name, $event_props, $event_user ); + } + } + /** * Escape a string to be used in a CSV context * @@ -3200,11 +3278,27 @@ private static function esc_shortcode_val( $val ) { public static function parse_contact_field( $attributes, $content ) { // Don't try to parse contact form fields if not inside a contact form if ( ! Grunion_Contact_Form_Plugin::$using_contact_form_field ) { - $att_strs = array(); + $type = isset( $attributes['type'] ) ? $attributes['type'] : null; + + if ( $type === 'checkbox-multiple' || $type === 'radio' ) { + preg_match_all( '/' . get_shortcode_regex() . '/s', $content, $matches ); + + if ( ! empty( $matches[0] ) ) { + $options = array(); + foreach ( $matches[0] as $shortcode ) { + $attr = shortcode_parse_atts( $shortcode ); + $options[] = $attr['label']; + } + + $attributes['options'] = $options; + } + } + if ( ! isset( $attributes['label'] ) ) { - $type = isset( $attributes['type'] ) ? $attributes['type'] : null; $attributes['label'] = self::get_default_label_from_type( $type ); } + + $att_strs = array(); foreach ( $attributes as $att => $val ) { if ( is_numeric( $att ) ) { // Is a valueless attribute $att_strs[] = self::esc_shortcode_val( $val ); @@ -3223,7 +3317,12 @@ public static function parse_contact_field( $attributes, $content ) { } } - $html = '[contact-field ' . implode( ' ', $att_strs ); + $shortcode_type = 'contact-field'; + if ( $type === 'field-option' ) { + $shortcode_type = 'contact-field-option'; + } + + $html = '[' . $shortcode_type . ' ' . implode( ' ', $att_strs ); if ( isset( $content ) && ! empty( $content ) ) { // If there is content, let's add a closing tag $html .= ']' . esc_html( $content ) . '[/contact-field]'; @@ -5268,7 +5367,7 @@ function jetpack_tracks_record_grunion_pre_message_sent( $post_id, $all_values, } } - $tracking = new Automattic\Jetpack\Tracking(); + $tracking = new \Automattic\Jetpack\Tracking(); $tracking->record_user_event( $event_name, $event_props, $event_user ); } } diff --git a/projects/plugins/jetpack/modules/scan/class-admin-sidebar-link.php b/projects/plugins/jetpack/modules/scan/class-admin-sidebar-link.php index 794a964aeea86..8f1b4f8cb8bdc 100644 --- a/projects/plugins/jetpack/modules/scan/class-admin-sidebar-link.php +++ b/projects/plugins/jetpack/modules/scan/class-admin-sidebar-link.php @@ -9,6 +9,7 @@ use Automattic\Jetpack\My_Jetpack\Products\Backup; use Automattic\Jetpack\Redirect; +use Automattic\Jetpack\Status\Host; use Jetpack_Core_Json_Api_Endpoints; /** @@ -135,12 +136,12 @@ private function should_show_link() { /** * Check if we should display the Scan menu item. * - * It will only be displayed if site has Scan enabled and the stand-alone Protect plugin is not active, because it will have a menu item of its own. + * It will only be displayed if site has Scan enabled, is not an Atomic site, and the stand-alone Protect plugin is not active, because it will have a menu item of its own. * * @return boolean */ private function should_show_scan() { - return $this->has_scan() && ! $this->has_protect_plugin(); + return $this->has_scan() && ! $this->has_protect_plugin() && ! ( new Host() )->is_woa_site(); } /** diff --git a/projects/plugins/jetpack/modules/shortcodes/crowdsignal.php b/projects/plugins/jetpack/modules/shortcodes/crowdsignal.php index 63f03d8f65e86..ea0b1539cba5b 100644 --- a/projects/plugins/jetpack/modules/shortcodes/crowdsignal.php +++ b/projects/plugins/jetpack/modules/shortcodes/crowdsignal.php @@ -57,7 +57,6 @@ public function __construct() { add_shortcode( 'polldaddy', array( $this, 'polldaddy_shortcode' ) ); add_filter( 'pre_kses', array( $this, 'crowdsignal_embed_to_shortcode' ) ); - add_action( 'wp_enqueue_scripts', array( $this, 'check_infinite' ) ); add_action( 'infinite_scroll_render', array( $this, 'crowdsignal_shortcode_infinite' ), 11 ); } @@ -68,7 +67,7 @@ public static function register_scripts() { wp_register_script( 'crowdsignal-shortcode', Assets::get_file_url_for_environment( '_inc/build/crowdsignal-shortcode.min.js', '_inc/crowdsignal-shortcode.js' ), - array( 'jquery' ), + array(), JETPACK__VERSION, true ); @@ -687,19 +686,6 @@ public function generate_scripts() { self::$scripts = false; } - /** - * If the theme uses infinite scroll, include jquery at the start - */ - public function check_infinite() { - if ( - current_theme_supports( 'infinite-scroll' ) - && class_exists( 'The_Neverending_Home_Page' ) - && The_Neverending_Home_Page::archive_supports_infinity() - ) { - wp_enqueue_script( 'jquery' ); - } - } - /** * Dynamically load the .js, if needed * diff --git a/projects/plugins/jetpack/package.json b/projects/plugins/jetpack/package.json index ab5d05c584a74..fb55073bc24bc 100644 --- a/projects/plugins/jetpack/package.json +++ b/projects/plugins/jetpack/package.json @@ -1,6 +1,6 @@ { "name": "Jetpack", - "version": "12.0.0-a.0", + "version": "12.0.0-a.2", "private": true, "description": "[Jetpack](https://jetpack.com/) is a WordPress plugin that supercharges your self-hosted WordPress site with the awesome cloud power of [WordPress.com](https://wordpress.com).", "homepage": "https://jetpack.com", diff --git a/projects/plugins/jetpack/readme.txt b/projects/plugins/jetpack/readme.txt index 38561e79509ce..415e8fb4ed083 100644 --- a/projects/plugins/jetpack/readme.txt +++ b/projects/plugins/jetpack/readme.txt @@ -1,10 +1,10 @@ === Jetpack - WP Security, Backup, Speed, & Growth === Contributors: automattic, adamkheckler, adrianmoldovanwp, aduth, akirk, allendav, alternatekev, andy, annamcphee, annezazu, apeatling, arcangelini, azaozz, batmoo, barry, beaulebens, bindlegirl, biskobe, blobaugh, bjorsch, brbrr, cainm, cena, cfinke, chaselivingston, chellycat, clickysteve, csonnek, danielbachhuber, davoraltman, daniloercoli, delawski, designsimply, dllh, drawmyface, dsmart, dzver, ebinnion, egregor, eliorivero, enej, eoigal, erania-pinnera, ethitter, fgiannar, gcorne, georgestephanis, gibrown, goldsounds, hew, hugobaeta, hypertextranch, iammattthomas, iandunn, jblz, jasmussen, jeffgolenski, jeherve, jenhooks, jenia, jessefriedman, jgs, jkudish, jmdodd, joanrho, johnjamesjacoby, jshreve, kbrownkd, keoshi, koke, kraftbj, lancewillett, leogermani, lschuyler, macmanx, martinremy, matt, matveb, mattwiebe, maverick3x6, mcsf, mdawaffe, mdbitz, MichaelArestad, migueluy, mikeyarce, mkaz, nancythanki, nickmomrik, obenland, oskosk, pento, professor44, rachelsquirrel, rdcoll, ryancowles, richardmuscat, richardmtl, robertbpugh, roccotripaldi, samhotchkiss, samiff, scarstocea, scottsweb, sdixon194, sdquirk, sermitr, simison, stephdau, tmoorewp, tyxla, Viper007Bond, westi, wpkaren, yoavf, zinigor Tags: Security, backup, Woo, malware, scan, spam, CDN, search, social -Stable tag: 11.8.4 +Stable tag: 11.9 Requires at least: 6.0 Requires PHP: 5.6 -Tested up to: 6.1 +Tested up to: 6.2 Improve your WP security with powerful one-click tools like backup and malware scan. Get essential free tools including stats, CDN and social sharing. @@ -197,7 +197,7 @@ Blocks are the individual sections that make up a page. There are many block typ * Related Posts Block - The Related Posts feature scans all of your posts' contents, analyzes it, and uses that to display contextual posts your visitors might be interested in reading after they're finished with the current post. * Repeat Visitor Block - The Repeat Visitor block enables the author to control the visibility of its nested block(s) depending on how many times a visitor has previously visited the page. * Revue Block - The Revue block creates a simple signup form for readers to opt-in to receive your newsletter. -* Slideshow Block - The Slideshow block lets you insert an image slideshow into a post or page. +* Slideshow Block - The Slideshow block lets you insert an image slideshow into a post or page. * Star Rating Block - The Ratings block allows any site author to add reviews to the site. * Subscription Form Block - The Subscription Form Block allows you to insert a subscription form within the content area of any post or page, enabling your readers to get notifications when you publish new posts. * Tiled Gallery Block - With Tiled Galleries you can display your image galleries in four styles: tiled mosaic, circular grid, square tiles, and tiled columns. @@ -244,25 +244,12 @@ Jetpack Backup can do a full website migration to a new host, migrate theme file 4. Promote your newest posts, pages, and products across your social media channels. == Changelog == -### 11.9-beta - 2023-02-28 +### 12.0-a.1 - 2023-03-08 #### Enhancements -- Assistant: add new card to highlight VaultPress Backup. -- Form block: add form field style synchronization for input fields. -- Related Posts: add support for font family in Related Posts block. -- Sharing: add Mastodon sharing button. - -#### Improved compatibility -- Stats: add upgrade notice for Odyssey Stats. -- VideoPress: add support for the `preload` or `preloadcontent` attribute to the VideoPress shortcode. - -#### Bug fixes -- Connection: revise Jetpack connection agreement text to comply with our User Agreement. -- Custom CSS: ensure the link to enable Custom CSS works in all languages. -- Form block: increase form fields padding based on user-defined border-radius. -- Form block: remove body font normalization in contact-form module and package. -- Presentation shortcode: always add presentation container. -- Recommendations: avoid applying coupon codes from the Assistant on products with trial prices. -- Sharing buttons: fix display issues when choosing the icon-only option. +- Admin: fix submenu positioning in admin menu. +- Blocks: add a new Cookie Consent block to display a GDPR-compliant cookie consent widget on your site for your visitors. +- SSO: add message to logout notice when SSO is enabled that gives a heads up to also log out of WordPress.com if they are on a shared computer. +- Stats: updates the layout of the loading and some sections on the Stats page. -------- diff --git a/projects/plugins/jetpack/tests/php/sync/test_class.jetpack-sync-posts.php b/projects/plugins/jetpack/tests/php/sync/test_class.jetpack-sync-posts.php index 3280f292811cf..50e2ccf7ab9f8 100644 --- a/projects/plugins/jetpack/tests/php/sync/test_class.jetpack-sync-posts.php +++ b/projects/plugins/jetpack/tests/php/sync/test_class.jetpack-sync-posts.php @@ -1303,7 +1303,7 @@ public function provider_jetpack_published_post_no_action() { */ public function test_sync_jetpack_published_post_no_action( $post_ID, $post ) { $this->server_event_storage->reset(); - do_action( 'wp_after_insert_post', $post_ID, $post, false ); + do_action( 'wp_after_insert_post', $post_ID, $post, false, null ); $this->sender->do_sync(); diff --git a/projects/plugins/search/changelog/add-new-wpcom-subscription-emails-use-excerpt-option b/projects/plugins/migration/changelog/add-zendesk-chat-module similarity index 100% rename from projects/plugins/search/changelog/add-new-wpcom-subscription-emails-use-excerpt-option rename to projects/plugins/migration/changelog/add-zendesk-chat-module diff --git a/projects/plugins/search/changelog/add-promoted-posts-post-publish-panel b/projects/plugins/migration/changelog/add-zendesk-chat-module#2 similarity index 100% rename from projects/plugins/search/changelog/add-promoted-posts-post-publish-panel rename to projects/plugins/migration/changelog/add-zendesk-chat-module#2 diff --git a/projects/plugins/migration/changelog/update-wp-tested-up-to b/projects/plugins/migration/changelog/update-wp-tested-up-to new file mode 100644 index 0000000000000..ad53b760f90b2 --- /dev/null +++ b/projects/plugins/migration/changelog/update-wp-tested-up-to @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +General: indicate full compatibility with the latest version of WordPress, 6.2. diff --git a/projects/plugins/migration/composer.lock b/projects/plugins/migration/composer.lock index e29ed584c59b8..c6bb0bc157b7f 100644 --- a/projects/plugins/migration/composer.lock +++ b/projects/plugins/migration/composer.lock @@ -711,7 +711,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "5c154d90af8faaf107ce35a8aedbe1025e2313d4" + "reference": "d8bfc30fbd87ba4b6ba30c604bc551ea88f9234f" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -738,7 +738,7 @@ "link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}" }, "branch-alias": { - "dev-trunk": "2.7.x-dev" + "dev-trunk": "2.8.x-dev" }, "version-constants": { "::PACKAGE_VERSION": "src/class-initializer.php" diff --git a/projects/plugins/migration/readme.txt b/projects/plugins/migration/readme.txt index 4e2d3f2ae9847..7f6970bc40566 100644 --- a/projects/plugins/migration/readme.txt +++ b/projects/plugins/migration/readme.txt @@ -3,7 +3,7 @@ Contributors: automattic Tags: migrate, migration, backup, restore, transfer, move, copy, wordpress.com, automattic, import, importer, hosting Requires at least: 6.0 Requires PHP: 5.6 -Tested up to: 6.1 +Tested up to: 6.2 Stable tag: 0.1.0-alpha License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html diff --git a/projects/plugins/mu-wpcom-plugin/CHANGELOG.md b/projects/plugins/mu-wpcom-plugin/CHANGELOG.md index bf9e9d4b15b15..c1a6e2bd4ae05 100644 --- a/projects/plugins/mu-wpcom-plugin/CHANGELOG.md +++ b/projects/plugins/mu-wpcom-plugin/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 1.0.7 - 2023-03-08 +### Changed +- Minor internal updates. + ## 1.0.6 - 2023-02-28 ### Changed - Minor internal updates. diff --git a/projects/plugins/mu-wpcom-plugin/changelog/init-release-cycle b/projects/plugins/mu-wpcom-plugin/changelog/init-release-cycle index a9493d8b91def..57daddf2f6f3c 100644 --- a/projects/plugins/mu-wpcom-plugin/changelog/init-release-cycle +++ b/projects/plugins/mu-wpcom-plugin/changelog/init-release-cycle @@ -1,5 +1,5 @@ Significance: patch Type: changed -Comment: Init 1.0.7-alpha +Comment: Init 1.0.8-alpha diff --git a/projects/plugins/mu-wpcom-plugin/composer.json b/projects/plugins/mu-wpcom-plugin/composer.json index d6f647ad9543a..ca4bce33b4914 100644 --- a/projects/plugins/mu-wpcom-plugin/composer.json +++ b/projects/plugins/mu-wpcom-plugin/composer.json @@ -42,6 +42,6 @@ "release-branch-prefix": "jetpack" }, "config": { - "autoloader-suffix": "d9d132a783958a00a2c7cccff60ca42d_jetpack_mu_wpcom_pluginⓥ1_0_7_alpha" + "autoloader-suffix": "d9d132a783958a00a2c7cccff60ca42d_jetpack_mu_wpcom_pluginⓥ1_0_8_alpha" } } diff --git a/projects/plugins/mu-wpcom-plugin/mu-wpcom-plugin.php b/projects/plugins/mu-wpcom-plugin/mu-wpcom-plugin.php index b4800f541bee0..f3aa58abb8066 100644 --- a/projects/plugins/mu-wpcom-plugin/mu-wpcom-plugin.php +++ b/projects/plugins/mu-wpcom-plugin/mu-wpcom-plugin.php @@ -3,7 +3,7 @@ * * Plugin Name: WordPress.com Features * Description: Test plugin for the jetpack-mu-wpcom package - * Version: 1.0.7-alpha + * Version: 1.0.8-alpha * Author: Automattic * License: GPLv2 or later * Text Domain: jetpack-mu-wpcom-plugin diff --git a/projects/plugins/mu-wpcom-plugin/package.json b/projects/plugins/mu-wpcom-plugin/package.json index a6940419711dc..e08ce12afb12e 100644 --- a/projects/plugins/mu-wpcom-plugin/package.json +++ b/projects/plugins/mu-wpcom-plugin/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-mu-wpcom-plugin", - "version": "1.0.7-alpha", + "version": "1.0.8-alpha", "description": "Test plugin for the jetpack-mu-wpcom package", "homepage": "https://jetpack.com", "bugs": { diff --git a/projects/plugins/search/changelog/add-shared-get-blog-id b/projects/plugins/protect/changelog/add-zendesk-chat-module similarity index 100% rename from projects/plugins/search/changelog/add-shared-get-blog-id rename to projects/plugins/protect/changelog/add-zendesk-chat-module diff --git a/projects/plugins/search/changelog/add-status-private-coming-soon-site b/projects/plugins/protect/changelog/add-zendesk-chat-module#2 similarity index 100% rename from projects/plugins/search/changelog/add-status-private-coming-soon-site rename to projects/plugins/protect/changelog/add-zendesk-chat-module#2 diff --git a/projects/plugins/protect/changelog/fix-all-1-threats-title b/projects/plugins/protect/changelog/fix-all-1-threats-title new file mode 100644 index 0000000000000..2c67d2e2794c1 --- /dev/null +++ b/projects/plugins/protect/changelog/fix-all-1-threats-title @@ -0,0 +1,5 @@ +Significance: patch +Type: fixed +Comment: Fixed awkward phrasing in a title, no significant changes. + + diff --git a/projects/plugins/protect/changelog/more-descriptive-error-messages b/projects/plugins/protect/changelog/more-descriptive-error-messages new file mode 100644 index 0000000000000..9610037d6750d --- /dev/null +++ b/projects/plugins/protect/changelog/more-descriptive-error-messages @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Minor improvement to error messages on the firewall settings screen. + + diff --git a/projects/plugins/protect/changelog/update-wp-tested-up-to b/projects/plugins/protect/changelog/update-wp-tested-up-to new file mode 100644 index 0000000000000..ad53b760f90b2 --- /dev/null +++ b/projects/plugins/protect/changelog/update-wp-tested-up-to @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +General: indicate full compatibility with the latest version of WordPress, 6.2. diff --git a/projects/plugins/protect/composer.lock b/projects/plugins/protect/composer.lock index a6031b2399937..64ab168a7960a 100644 --- a/projects/plugins/protect/composer.lock +++ b/projects/plugins/protect/composer.lock @@ -760,7 +760,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "5c154d90af8faaf107ce35a8aedbe1025e2313d4" + "reference": "d8bfc30fbd87ba4b6ba30c604bc551ea88f9234f" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -787,7 +787,7 @@ "link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}" }, "branch-alias": { - "dev-trunk": "2.7.x-dev" + "dev-trunk": "2.8.x-dev" }, "version-constants": { "::PACKAGE_VERSION": "src/class-initializer.php" diff --git a/projects/plugins/protect/readme.txt b/projects/plugins/protect/readme.txt index 968ba9c4ea97e..6350720f45827 100644 --- a/projects/plugins/protect/readme.txt +++ b/projects/plugins/protect/readme.txt @@ -3,7 +3,7 @@ Contributors: automattic, retrofox, leogermani, renatoagds, bjorsch, ebinnion, f Tags: jetpack, protect, security, malware, scan Requires at least: 6.0 Requires PHP: 5.6 -Tested up to: 6.1 +Tested up to: 6.2 Stable tag: 1.2.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -14,7 +14,7 @@ Free daily malware scanning and WordPress site security. Jetpack Protect leverag == TOTAL SITE SECURITY FROM WORDPRESS EXPERTS == -Jetpack Protect is a free and essential WordPress security plugin that scans your site and warns you about vulnerabilities, keeping your site one step ahead of security threats. It’s easy to use; setup requires just a few clicks! +Jetpack Protect is a free and essential WordPress security plugin that scans your site and warns you about vulnerabilities, keeping your site one step ahead of security threats. It’s easy to use; setup requires just a few clicks! By upgrading Protect, you also unlock malware scanning with one-click fixes for most issues and instant notifications when threats are detected. Our automated Web Application Firewall (WAF) also protects your site from bad actors around the clock. @@ -28,7 +28,7 @@ Jetpack Protect scans your site on a daily basis and warns you about: - What themes are installed, and any associated vulnerabilities = What are vulnerabilities? Why do I need to scan my site regularly? = -Site vulnerabilities are flaws in a website's code that weaken the site's overall security. These can be introduced to a site in various ways, in most cases unintentionally. +Site vulnerabilities are flaws in a website's code that weaken the site's overall security. These can be introduced to a site in various ways, in most cases unintentionally. Some of the ways vulnerabilities can be introduced to a site are: - Poorly written site code @@ -36,7 +36,7 @@ Some of the ways vulnerabilities can be introduced to a site are: - WordPress version bugs - System misconfigurations -If a bad actor detects a vulnerability on your site, they can exploit it to access sensitive information, update your site, and more to damage your business or brand. +If a bad actor detects a vulnerability on your site, they can exploit it to access sensitive information, update your site, and more to damage your business or brand. That’s why it’s essential to use a reputable and reliable vulnerability & malware site scanner like Jetpack Protect to safeguard your site. @@ -56,9 +56,9 @@ Similar to the vulnerabilities listed above, bad actors can use malware to captu Jetpack Protect instantly notifies you of any threats detected, with one-click fixes for most issues. = What is a Web Application Firewall (WAF)? = -A web application firewall blocks traffic and malicious requests to your site from known bad actors. +A web application firewall blocks traffic and malicious requests to your site from known bad actors. -As threats are detected, new rules are added to Jetpack Protect’s firewall, which provides around-the-clock protection for your WordPress site. +As threats are detected, new rules are added to Jetpack Protect’s firewall, which provides around-the-clock protection for your WordPress site. == OVER 38,393 REGISTERED VULNERABILITIES IN OUR DATABASE == @@ -94,7 +94,7 @@ Protect is a free WordPress security and malware scanner plugin that scans your The free plan scans your site for WordPress version, plugin, and theme vulnerabilities from our extensive vulnerability database (38,393) that is powered by WPScan. -By upgrading Protect, you gain access to WordPress malware scanning with one-click fixes, instant threat notifications, and our Web application Firewall (WAF) that protects your site around the clock. +By upgrading Protect, you gain access to WordPress malware scanning with one-click fixes, instant threat notifications, and our Web application Firewall (WAF) that protects your site around the clock. = Does this plugin require the Jetpack plugin to work? = @@ -110,7 +110,7 @@ If you are already a Jetpack Scan, Jetpack Security, or Jetpack Complete custome WPScan is an enterprise vulnerability scanning solution. It is not recommended for small to medium-sized businesses. If you are an enterprise company looking for custom WordPress site protection solutions, please visit: https://wpscan.com/ -For small to medium-sized businesses, you can access our vulnerability scanning solution in the Jetpack Protect plugin. +For small to medium-sized businesses, you can access our vulnerability scanning solution in the Jetpack Protect plugin. = Will Jetpack Protect work on my local site? @@ -122,7 +122,7 @@ You can visit Jetpack Protect dashboard in your WordPress admin panel to see the = What do I do if Jetpack Protect finds a security threat? = -When the vulnerability scanner finds a security threat, you can view the recommended actions on the Jetpack Protect dashboard to secure your sites. +When the vulnerability scanner finds a security threat, you can view the recommended actions on the Jetpack Protect dashboard to secure your sites. If you have upgraded Protect, your site will also be automatically scanned for malware each day, and you will be notified instantly via email if any threats are detected. You will be able to fix most issues in one click. diff --git a/projects/plugins/protect/src/class-rest-controller.php b/projects/plugins/protect/src/class-rest-controller.php index 04b8598ef54d9..465f679d8a651 100644 --- a/projects/plugins/protect/src/class-rest-controller.php +++ b/projects/plugins/protect/src/class-rest-controller.php @@ -254,7 +254,7 @@ public static function api_clear_scan_cache() { */ public static function api_ignore_threat( $request ) { if ( ! $request['threat_id'] ) { - return new WP_REST_RESPONSE( 'Missing threat ID.', 400 ); + return new WP_REST_Response( 'Missing threat ID.', 400 ); } $threat_ignored = Threats::ignore_threat( $request['threat_id'] ); @@ -275,7 +275,7 @@ public static function api_ignore_threat( $request ) { */ public static function api_fix_threats( $request ) { if ( empty( $request['threat_ids'] ) ) { - return new WP_REST_RESPONSE( 'Missing threat IDs.', 400 ); + return new WP_REST_Response( 'Missing threat IDs.', 400 ); } $threats_fixed = Threats::fix_threats( $request['threat_ids'] ); @@ -296,7 +296,7 @@ public static function api_fix_threats( $request ) { */ public static function api_fix_threats_status( $request ) { if ( empty( $request['threat_ids'] ) ) { - return new WP_REST_RESPONSE( 'Missing threat IDs.', 400 ); + return new WP_REST_Response( 'Missing threat IDs.', 400 ); } $threats_fixed = Threats::fix_threats_status( $request['threat_ids'] ); @@ -341,16 +341,32 @@ public static function api_scan() { /** * Toggles the WAF module on or off for the API endpoint * - * @return WP_REST_Response + * @return WP_REST_Response|WP_Error */ public static function api_toggle_waf() { if ( Waf_Runner::is_enabled() ) { - Waf_Runner::disable(); - return rest_ensure_response( true, 200 ); + $disabled = Waf_Runner::disable(); + if ( ! $disabled ) { + return new WP_Error( + 'waf_disable_failed', + __( 'An error occured disabling the firewall.', 'jetpack-protect' ), + array( 'status' => 500 ) + ); + } + + return rest_ensure_response( true ); + } + + $enabled = Waf_Runner::enable(); + if ( ! $enabled ) { + return new WP_Error( + 'waf_enable_failed', + __( 'An error occured enabling the firewall.', 'jetpack-protect' ), + array( 'status' => 500 ) + ); } - Waf_Runner::enable(); - return rest_ensure_response( true, 200 ); + return rest_ensure_response( true ); } /** diff --git a/projects/plugins/protect/src/js/components/firewall-page/index.jsx b/projects/plugins/protect/src/js/components/firewall-page/index.jsx index 117ca967c7b47..629d86834afde 100644 --- a/projects/plugins/protect/src/js/components/firewall-page/index.jsx +++ b/projects/plugins/protect/src/js/components/firewall-page/index.jsx @@ -33,16 +33,6 @@ import styles from './styles.module.scss'; const ADMIN_URL = window?.jetpackProtectInitialState?.adminUrl; const SUCCESS_NOTICE_DURATION = 5000; -const errorMessage = createInterpolateElement( - __( - 'An error ocurred. Please try again or contact support.', - 'jetpack-protect' - ), - { - supportLink: , - } -); - const FirewallPage = () => { const [ isSmall ] = useBreakpointMatch( [ 'sm', 'lg' ], [ null, '<' ] ); const notice = useSelect( select => select( STORE_ID ).getNotice() ); @@ -110,6 +100,52 @@ const FirewallPage = () => { */ const [ showManualRules, setShowManualRules ] = useState( false ); + /** + * Get a custom error message based on the error code. + * + * @param {object} error - Error object. + * @returns string|bool Custom error message or false if no custom message exists. + */ + const getCustomErrorMessage = useCallback( error => { + switch ( error.code ) { + case 'file_system_error': + return __( 'A filesystem error occurred.', 'jetpack-protect' ); + case 'rules_api_error': + return __( + 'An error occurred retrieving the latest firewall rules from Jetpack.', + 'jetpack-protect' + ); + default: + return false; + } + }, [] ); + + /** + * Handle errors returned by the API. + */ + const handleApiError = useCallback( + error => { + const errorMessage = + getCustomErrorMessage( error ) || __( 'An error occurred.', 'jetpack-protect' ); + const supportMessage = createInterpolateElement( + __( 'Please try again or contact support.', 'jetpack-protect' ), + { + supportLink: , + } + ); + + setNotice( { + type: 'error', + message: ( + <> + { errorMessage } { supportMessage } + + ), + } ); + }, + [ getCustomErrorMessage, setNotice ] + ); + /** * Get Scan * @@ -137,14 +173,9 @@ const FirewallPage = () => { message: __( 'Changes saved.', 'jetpack-protect' ), } ) ) - .catch( () => { - setNotice( { - type: 'error', - message: errorMessage, - } ); - } ) + .catch( handleApiError ) .finally( () => setFormIsSubmitting( false ) ); - }, [ formState, updateConfig, setNotice ] ); + }, [ updateConfig, formState, handleApiError, setNotice ] ); /** * Handle Change @@ -197,15 +228,19 @@ const FirewallPage = () => { API.wafUpgradeSeen(); } } ) - .catch( () => { + .catch( error => { setAutomaticRulesInstallationError( true ); - setNotice( { - type: 'error', - message: errorMessage, - } ); + handleApiError( error ); } ) .finally( () => setFormIsSubmitting( false ) ); - }, [ formState, toggleAutomaticRules, setNotice, upgradeIsSeen, setWafUpgradeIsSeen ] ); + }, [ + formState, + toggleAutomaticRules, + setNotice, + upgradeIsSeen, + setWafUpgradeIsSeen, + handleApiError, + ] ); /** * Handle Manual Rules Change @@ -232,14 +267,9 @@ const FirewallPage = () => { ), } ) ) - .catch( () => { - setNotice( { - type: 'error', - message: errorMessage, - } ); - } ) + .catch( handleApiError ) .finally( () => setFormIsSubmitting( false ) ); - }, [ formState, toggleManualRules, setNotice ] ); + }, [ formState, toggleManualRules, handleApiError, setNotice ] ); /** * Handle Show Manual Rules Click @@ -432,7 +462,8 @@ const FirewallPage = () => { variant={ 'body-small' } mt={ 2 } > - { __( 'Failed to install automatic rules.', 'jetpack-protect' ) } + { __( 'Failed to update automatic rules.', 'jetpack-protect' ) }{ ' ' } + { getCustomErrorMessage( automaticRulesInstallationError ) }