diff --git a/.github/versions.sh b/.github/versions.sh index 439fc660f8607..a96fbbba552b9 100644 --- a/.github/versions.sh +++ b/.github/versions.sh @@ -2,7 +2,7 @@ PHP_VERSION=8.2 COMPOSER_VERSION=2.8.3 NODE_VERSION=22.9.0 -PNPM_VERSION=9.3.0 +PNPM_VERSION=9.15.0 # Other useful version numbers. MIN_PHP_VERSION=7.2 diff --git a/.github/workflows/gardening.yml b/.github/workflows/gardening.yml index d87dd2fdb9e71..a32fee68ea632 100644 --- a/.github/workflows/gardening.yml +++ b/.github/workflows/gardening.yml @@ -37,7 +37,7 @@ jobs: composer build-development working-directory: ./projects/github-actions/repo-gardening - - name: Checkout the PR + - name: Check out the PR if: github.event_name == 'pull_request_target' && github.event.pull_request.state != 'closed' uses: actions/checkout@v4 with: diff --git a/.husky/post-checkout b/.husky/post-checkout index 9446120543228..2c34b3c5d7416 100755 --- a/.husky/post-checkout +++ b/.husky/post-checkout @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - ./tools/js-tools/git-hooks/post-merge-checkout-hook.sh "$1" diff --git a/.husky/post-merge b/.husky/post-merge index 7399d9143f1bb..1ab789ff25e76 100755 --- a/.husky/post-merge +++ b/.husky/post-merge @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - ./tools/js-tools/git-hooks/post-merge-checkout-hook.sh ORIG_HEAD diff --git a/.husky/pre-commit b/.husky/pre-commit index 3f41ae0243919..7530bd93a2b36 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - node tools/js-tools/git-hooks/pre-commit-hook.js diff --git a/.husky/pre-push b/.husky/pre-push index 300e69f5b8f78..8b85d51a7d8ae 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,6 +1,3 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - if test -c /dev/tty && sh -c ': < /dev/tty' >/dev/null 2>/dev/null; then exec < /dev/tty fi diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs index 424998f096eea..8483ae624e8fd 100644 --- a/.pnpmfile.cjs +++ b/.pnpmfile.cjs @@ -223,6 +223,16 @@ function afterAllResolved( lockfile ) { return lockfile; } + for ( const [ k, v ] of Object.entries( lockfile.packages ) ) { + // Forbid installing webpack without webpack-cli. It results in lots of spurious lockfile changes. + // https://github.com/pnpm/pnpm/issues/3935 + if ( k.startsWith( 'webpack@' ) && ! v.optionalDependencies?.[ 'webpack-cli' ] ) { + throw new Error( + "Something you've done is trying to add a dependency on webpack without webpack-cli.\nThis is not allowed, as it tends to result in pnpm lockfile flip-flopping.\nSee https://github.com/pnpm/pnpm/issues/3935 for the upstream bug report.\n" + ); + } + } + return lockfile; } diff --git a/docs/git-workflow.md b/docs/git-workflow.md index c2ec0b5c6aa1c..ccb35850f08b8 100644 --- a/docs/git-workflow.md +++ b/docs/git-workflow.md @@ -80,7 +80,7 @@ If you're working directly with Jetpack trunk and need to update an external con This method assumes you are using the `gh` shorthand from the [Github CLI](https://cli.github.com/): ```sh -# Use the Github CLI to checkout the PR with the PR number - for example gh pr checkout 12345. +# Use the Github CLI to check out the PR with the PR number - for example gh pr checkout 12345. gh pr checkout xxxxx # Run merge-base to check where that branch differed from trunk - example git merge-base update/broken-jetpack-feature trunk. diff --git a/package.json b/package.json index c48245d935c8a..954da13145901 100644 --- a/package.json +++ b/package.json @@ -23,21 +23,21 @@ "php:autofix": "composer phpcs:fix", "php:compatibility": "composer phpcs:compatibility", "php:lint": "composer phpcs:lint", - "prepare": "husky install", + "prepare": "husky", "reformat-files": "prettier --ignore-path .eslintignore --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,svelte,json,json5}\"", "version-packages": "bash ./tools/version-packages.sh" }, "devDependencies": { "eslint": "9.16.0", - "husky": "8.0.3", + "husky": "9.1.7", "jetpack-cli": "workspace:*", "jetpack-js-tools": "workspace:*" }, "engines": { "node": "^22.9.0", - "pnpm": "^9.3.0 <9.12.0" + "pnpm": "^9.15.0" }, - "packageManager": "pnpm@9.3.0", + "packageManager": "pnpm@9.15.0", "pnpm": { "patchedDependencies": { "@wordpress/dataviews@4.10.0": ".pnpm-patches/@wordpress__dataviews@4.10.0.patch" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eba1d19846d96..2c0398584de7a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,7 +4,7 @@ settings: autoInstallPeers: false excludeLinksFromLockfile: false -pnpmfileChecksum: kmseazxfbymwdp5ie53bnph5mq +pnpmfileChecksum: elvoqfz3hkxf3joxybwjpabdjm patchedDependencies: '@wordpress/dataviews@4.10.0': @@ -19,8 +19,8 @@ importers: specifier: 9.16.0 version: 9.16.0 husky: - specifier: 8.0.3 - version: 8.0.3 + specifier: 9.1.7 + version: 9.1.7 jetpack-cli: specifier: workspace:* version: link:tools/cli @@ -138,6 +138,9 @@ importers: '@wordpress/api-fetch': specifier: 7.14.0 version: 7.14.0 + '@wordpress/base-styles': + specifier: 5.14.0 + version: 5.14.0 '@wordpress/blob': specifier: 4.14.0 version: 4.14.0 @@ -645,11 +648,11 @@ importers: specifier: 1.0.1 version: 1.0.1 playwright: - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 playwright-core: specifier: ^1.45.1 - version: 1.49.0 + version: 1.49.1 process: specifier: 0.11.10 version: 0.11.10 @@ -673,7 +676,7 @@ importers: version: 4.9.1(webpack@5.94.0) webpack-dev-middleware: specifier: 5.3.4 - version: 5.3.4(webpack@5.94.0(webpack-cli@4.9.1)) + version: 5.3.4(webpack@5.94.0) projects/js-packages/eslint-changed: dependencies: @@ -752,7 +755,7 @@ importers: devDependencies: '@wordpress/dependency-extraction-webpack-plugin': specifier: 6.14.0 - version: 6.14.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.14.0(webpack@5.94.0) '@wordpress/i18n': specifier: 5.14.0 version: 5.14.0 @@ -1440,8 +1443,8 @@ importers: specifier: 7.26.0 version: 7.26.0 '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 '@storybook/addon-a11y': specifier: 8.3.5 version: 8.3.5(storybook@8.3.5) @@ -1456,7 +1459,7 @@ importers: version: 8.3.5(storybook@8.3.5) '@storybook/addon-webpack5-compiler-babel': specifier: ^3.0.3 - version: 3.0.3(webpack@5.94.0(webpack-cli@4.9.1)) + version: 3.0.3(webpack@5.94.0) '@storybook/blocks': specifier: 8.3.5 version: 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5) @@ -1471,7 +1474,7 @@ importers: version: 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4) '@storybook/react-webpack5': specifier: 8.3.5 - version: 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1(webpack@5.94.0)) + version: 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1) '@storybook/source-loader': specifier: 8.3.5 version: 8.3.5(storybook@8.3.5) @@ -1507,16 +1510,16 @@ importers: version: 2.9.2 babel-loader: specifier: 9.1.2 - version: 9.1.2(@babel/core@7.26.0)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 9.1.2(@babel/core@7.26.0)(webpack@5.94.0) babel-plugin-inline-json-import: specifier: 0.3.2 version: 0.3.2 css-loader: specifier: 6.5.1 - version: 6.5.1(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.5.1(webpack@5.94.0) esbuild-loader: specifier: 3.0.1 - version: 3.0.1(webpack@5.94.0(webpack-cli@4.9.1)) + version: 3.0.1(webpack@5.94.0) jest: specifier: 29.7.0 version: 29.7.0 @@ -1528,7 +1531,7 @@ importers: version: 8.4.47 postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) react: specifier: 18.3.1 version: 18.3.1 @@ -1546,7 +1549,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) storybook: specifier: 8.3.5 version: 8.3.5 @@ -1555,7 +1558,7 @@ importers: version: 5.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) style-loader: specifier: 2.0.0 - version: 2.0.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 2.0.0(webpack@5.94.0) ts-dedent: specifier: 2.2.0 version: 2.2.0 @@ -1642,7 +1645,7 @@ importers: version: link:../i18n-loader-webpack-plugin '@automattic/webpack-rtl-plugin': specifier: 6.0.0 - version: 6.0.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.0.0(webpack@5.94.0) '@babel/compat-data': specifier: 7.26.2 version: 7.26.2 @@ -1663,16 +1666,16 @@ importers: version: 7.26.0(@babel/core@7.26.0) '@cerner/duplicate-package-checker-webpack-plugin': specifier: 2.3.0 - version: 2.3.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 2.3.0(webpack@5.94.0) '@wordpress/browserslist-config': specifier: 6.14.0 version: 6.14.0 '@wordpress/dependency-extraction-webpack-plugin': specifier: 6.14.0 - version: 6.14.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.14.0(webpack@5.94.0) babel-loader: specifier: 9.1.2 - version: 9.1.2(@babel/core@7.26.0)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 9.1.2(@babel/core@7.26.0)(webpack@5.94.0) babel-plugin-polyfill-corejs3: specifier: 0.10.6 version: 0.10.6(@babel/core@7.26.0) @@ -1684,22 +1687,22 @@ importers: version: 3.38.1 css-loader: specifier: 6.5.1 - version: 6.5.1(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.5.1(webpack@5.94.0) css-minimizer-webpack-plugin: specifier: 5.0.1 - version: 5.0.1(webpack@5.94.0(webpack-cli@4.9.1)) + version: 5.0.1(webpack@5.94.0) fork-ts-checker-webpack-plugin: specifier: 9.0.2 - version: 9.0.2(typescript@5.0.4)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 9.0.2(typescript@5.0.4)(webpack@5.94.0) mini-css-extract-plugin: specifier: 2.9.1 - version: 2.9.1(webpack@5.94.0(webpack-cli@4.9.1)) + version: 2.9.1(webpack@5.94.0) terser-webpack-plugin: specifier: 5.3.3 - version: 5.3.3(webpack@5.94.0(webpack-cli@4.9.1)) + version: 5.3.3(webpack@5.94.0) thread-loader: specifier: 3.0.4 - version: 3.0.4(webpack@5.94.0(webpack-cli@4.9.1)) + version: 3.0.4(webpack@5.94.0) devDependencies: '@babel/core': specifier: 7.26.0 @@ -1845,7 +1848,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) typescript: specifier: 5.0.4 version: 5.0.4 @@ -1918,7 +1921,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -1945,7 +1948,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -1983,13 +1986,13 @@ importers: version: 8.4.47 postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) sass: specifier: 1.64.1 version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -2041,7 +2044,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -2141,7 +2144,7 @@ importers: version: 2.1.1 copy-webpack-plugin: specifier: 11.0.0 - version: 11.0.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 11.0.0(webpack@5.94.0) email-validator: specifier: 2.0.4 version: 2.0.4 @@ -2245,10 +2248,10 @@ importers: version: 8.4.47 postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) typescript: specifier: 5.0.4 version: 5.0.4 @@ -2379,8 +2382,8 @@ importers: specifier: 7.25.9 version: 7.25.9(@babel/core@7.26.0) '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 '@types/node': specifier: ^20.4.2 version: 20.17.9 @@ -2401,7 +2404,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) typescript: specifier: ^5.0.4 version: 5.7.2 @@ -2425,7 +2428,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -2471,13 +2474,13 @@ importers: version: 8.4.47 postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) sass: specifier: 1.64.1 version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -2616,7 +2619,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) storybook: specifier: 8.3.5 version: 8.3.5 @@ -2642,7 +2645,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) tslib: specifier: 2.5.0 version: 2.5.0 @@ -2819,7 +2822,7 @@ importers: version: 6.14.0 '@wordpress/dependency-extraction-webpack-plugin': specifier: 6.14.0 - version: 6.14.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.14.0(webpack@5.94.0) autoprefixer: specifier: 10.4.14 version: 10.4.14(postcss@8.4.47) @@ -2846,13 +2849,13 @@ importers: version: 12.1.7(postcss@8.4.47) postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) sass: specifier: 1.64.1 version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) size-limit: specifier: 11.1.6 version: 11.1.6(@size-limit/preset-app@11.1.6) @@ -3013,7 +3016,7 @@ importers: version: 7.6.0 copy-webpack-plugin: specifier: 11.0.0 - version: 11.0.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 11.0.0(webpack@5.94.0) jest: specifier: 29.7.0 version: 29.7.0 @@ -3028,7 +3031,7 @@ importers: version: 12.1.7(postcss@8.4.47) postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) require-from-string: specifier: 2.0.2 version: 2.0.2 @@ -3037,7 +3040,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) storybook: specifier: 8.3.5 version: 8.3.5 @@ -3191,7 +3194,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -3257,7 +3260,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -3345,7 +3348,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -3356,8 +3359,8 @@ importers: projects/plugins/automattic-for-agencies-client/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -3402,7 +3405,7 @@ importers: version: 2.1.1 copy-webpack-plugin: specifier: 11.0.0 - version: 11.0.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 11.0.0(webpack@5.94.0) history: specifier: 5.3.0 version: 5.3.0 @@ -3493,7 +3496,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) storybook: specifier: 8.3.5 version: 8.3.5 @@ -3513,8 +3516,8 @@ importers: projects/plugins/boost/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -3593,7 +3596,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -3604,8 +3607,8 @@ importers: projects/plugins/classic-theme-helper-plugin/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -3702,7 +3705,7 @@ importers: version: 29.3.1(@babel/core@7.26.0) css-loader: specifier: 6.5.1 - version: 6.5.1(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.5.1(webpack@5.94.0) glob: specifier: 10.4.1 version: 10.4.1 @@ -3717,7 +3720,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) typescript: specifier: 5.0.4 version: 5.0.4 @@ -3919,7 +3922,7 @@ importers: version: 1.0.1 copy-webpack-plugin: specifier: 11.0.0 - version: 11.0.0(webpack@5.94.0(webpack-cli@4.9.1)) + version: 11.0.0(webpack@5.94.0) crypto-js: specifier: 4.2.0 version: 4.2.0 @@ -4134,13 +4137,13 @@ importers: version: 8.4.47 postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) regenerator-runtime: specifier: 0.13.9 version: 0.13.9 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) typescript: specifier: 5.0.4 version: 5.0.4 @@ -4148,8 +4151,8 @@ importers: projects/plugins/jetpack/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -4257,7 +4260,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) typescript: specifier: 5.0.4 version: 5.0.4 @@ -4273,8 +4276,8 @@ importers: projects/plugins/search/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -4395,13 +4398,13 @@ importers: version: 12.1.7(postcss@8.4.47) postcss-loader: specifier: 6.2.0 - version: 6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 6.2.0(postcss@8.4.47)(webpack@5.94.0) sass: specifier: 1.64.1 version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -4412,8 +4415,8 @@ importers: projects/plugins/social/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -4492,7 +4495,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -4503,8 +4506,8 @@ importers: projects/plugins/starter-plugin/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -4597,7 +4600,7 @@ importers: version: 1.64.1 sass-loader: specifier: 12.4.0 - version: 12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)) + version: 12.4.0(sass@1.64.1)(webpack@5.94.0) webpack: specifier: 5.94.0 version: 5.94.0(webpack-cli@4.9.1) @@ -4608,8 +4611,8 @@ importers: projects/plugins/videopress/tests/e2e: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 allure-playwright: specifier: 2.9.2 version: 2.9.2 @@ -4726,8 +4729,8 @@ importers: tools/e2e-commons: devDependencies: '@playwright/test': - specifier: 1.45.1 - version: 1.45.1 + specifier: 1.48.2 + version: 1.48.2 '@slack/web-api': specifier: 7.3.2 version: 7.3.2 @@ -4805,7 +4808,7 @@ importers: version: 6.5.0 '@wordpress/eslint-plugin': specifier: 22.0.0 - version: 22.0.0(@babel/core@7.26.0)(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint-plugin-import@2.31.0(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0))(eslint-plugin-jest@28.9.0(eslint@9.16.0)(jest@29.7.0)(typescript@5.0.4))(eslint-plugin-jsdoc@50.6.0(eslint@9.16.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@9.16.0))(eslint-plugin-playwright@2.1.0(eslint@9.16.0))(eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint@9.16.0)(wp-prettier@3.0.3))(eslint-plugin-react-hooks@5.1.0(eslint@9.16.0))(eslint-plugin-react@7.37.2(eslint@9.16.0))(eslint@9.16.0)(typescript@5.0.4)(wp-prettier@3.0.3) + version: 22.0.0(@babel/core@7.26.0)(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint-plugin-import@2.31.0)(eslint-plugin-jest@28.9.0(eslint@9.16.0)(jest@29.7.0)(typescript@5.0.4))(eslint-plugin-jsdoc@50.6.0(eslint@9.16.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@9.16.0))(eslint-plugin-playwright@2.1.0(eslint@9.16.0))(eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint@9.16.0)(wp-prettier@3.0.3))(eslint-plugin-react-hooks@5.1.0(eslint@9.16.0))(eslint-plugin-react@7.37.2(eslint@9.16.0))(eslint@9.16.0)(typescript@5.0.4)(wp-prettier@3.0.3) '@wordpress/jest-console': specifier: 8.14.0 version: 8.14.0(jest@29.7.0) @@ -6509,8 +6512,8 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@playwright/test@1.45.1': - resolution: {integrity: sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==} + '@playwright/test@1.48.2': + resolution: {integrity: sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==} engines: {node: '>=18'} hasBin: true @@ -10768,9 +10771,9 @@ packages: humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} - husky@8.0.3: - resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} - engines: {node: '>=14'} + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} hasBin: true iconv-lite@0.4.24: @@ -12372,18 +12375,18 @@ packages: resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} engines: {node: '>=14.16'} - playwright-core@1.45.1: - resolution: {integrity: sha512-LF4CUUtrUu2TCpDw4mcrAIuYrEjVDfT1cHbJMfwnE2+1b8PZcFzPNgvZCvq2JfQ4aTjRCCHw5EJ2tmr2NSzdPg==} + playwright-core@1.48.2: + resolution: {integrity: sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==} engines: {node: '>=18'} hasBin: true - playwright-core@1.49.0: - resolution: {integrity: sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==} + playwright-core@1.49.1: + resolution: {integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==} engines: {node: '>=18'} hasBin: true - playwright@1.45.1: - resolution: {integrity: sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==} + playwright@1.48.2: + resolution: {integrity: sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==} engines: {node: '>=18'} hasBin: true @@ -14840,7 +14843,7 @@ snapshots: '@automattic/viewport@1.0.0': {} - '@automattic/webpack-rtl-plugin@6.0.0(webpack@5.94.0(webpack-cli@4.9.1))': + '@automattic/webpack-rtl-plugin@6.0.0(webpack@5.94.0)': dependencies: rtlcss: 3.5.0 webpack: 5.94.0(webpack-cli@4.9.1) @@ -15660,7 +15663,7 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@cerner/duplicate-package-checker-webpack-plugin@2.3.0(webpack@5.94.0(webpack-cli@4.9.1))': + '@cerner/duplicate-package-checker-webpack-plugin@2.3.0(webpack@5.94.0)': dependencies: chalk: 4.1.2 find-root: 1.1.0 @@ -16487,9 +16490,9 @@ snapshots: '@pkgr/core@0.1.1': {} - '@playwright/test@1.45.1': + '@playwright/test@1.48.2': dependencies: - playwright: 1.45.1 + playwright: 1.48.2 '@popperjs/core@2.11.8': {} @@ -17185,21 +17188,21 @@ snapshots: transitivePeerDependencies: - supports-color - '@size-limit/file@11.1.6(size-limit@11.1.6(@size-limit/preset-app@11.1.6))': + '@size-limit/file@11.1.6(size-limit@11.1.6)': dependencies: size-limit: 11.1.6(@size-limit/preset-app@11.1.6) '@size-limit/preset-app@11.1.6(size-limit@11.1.6)': dependencies: - '@size-limit/file': 11.1.6(size-limit@11.1.6(@size-limit/preset-app@11.1.6)) - '@size-limit/time': 11.1.6(size-limit@11.1.6(@size-limit/preset-app@11.1.6)) + '@size-limit/file': 11.1.6(size-limit@11.1.6) + '@size-limit/time': 11.1.6(size-limit@11.1.6) size-limit: 11.1.6(@size-limit/preset-app@11.1.6) transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@size-limit/time@11.1.6(size-limit@11.1.6(@size-limit/preset-app@11.1.6))': + '@size-limit/time@11.1.6(size-limit@11.1.6)': dependencies: estimo: 3.0.3 size-limit: 11.1.6(@size-limit/preset-app@11.1.6) @@ -17341,10 +17344,10 @@ snapshots: memoizerific: 1.11.3 storybook: 8.3.5 - '@storybook/addon-webpack5-compiler-babel@3.0.3(webpack@5.94.0(webpack-cli@4.9.1))': + '@storybook/addon-webpack5-compiler-babel@3.0.3(webpack@5.94.0)': dependencies: '@babel/core': 7.26.0 - babel-loader: 9.2.1(@babel/core@7.26.0)(webpack@5.94.0(webpack-cli@4.9.1)) + babel-loader: 9.2.1(@babel/core@7.26.0)(webpack@5.94.0) transitivePeerDependencies: - supports-color - webpack @@ -17380,7 +17383,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/builder-webpack5@8.3.5(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1(webpack@5.94.0))': + '@storybook/builder-webpack5@8.3.5(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1)': dependencies: '@storybook/core-webpack': 8.3.5(storybook@8.3.5) '@types/node': 22.10.1 @@ -17389,25 +17392,25 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.1 constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.94.0(webpack-cli@4.9.1)) + css-loader: 6.11.0(webpack@5.94.0) es-module-lexer: 1.5.4 express: 4.21.2 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.0.4)(webpack@5.94.0(webpack-cli@4.9.1)) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.0.4)(webpack@5.94.0) fs-extra: 11.2.0 - html-webpack-plugin: 5.6.3(webpack@5.94.0(webpack-cli@4.9.1)) + html-webpack-plugin: 5.6.3(webpack@5.94.0) magic-string: 0.30.14 path-browserify: 1.0.1 process: 0.11.10 semver: 7.5.2 storybook: 8.3.5 - style-loader: 3.3.4(webpack@5.94.0(webpack-cli@4.9.1)) - terser-webpack-plugin: 5.3.3(webpack@5.94.0(webpack-cli@4.9.1)) + style-loader: 3.3.4(webpack@5.94.0) + terser-webpack-plugin: 5.3.3(webpack@5.94.0) ts-dedent: 2.2.0 url: 0.11.4 util: 0.12.5 util-deprecate: 1.0.2 webpack: 5.94.0(webpack-cli@4.9.1) - webpack-dev-middleware: 6.1.3(webpack@5.94.0(webpack-cli@4.9.1)) + webpack-dev-middleware: 6.1.3(webpack@5.94.0) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -17520,11 +17523,11 @@ snapshots: dependencies: storybook: 8.4.6 - '@storybook/preset-react-webpack@8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1(webpack@5.94.0))': + '@storybook/preset-react-webpack@8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1)': dependencies: '@storybook/core-webpack': 8.3.5(storybook@8.3.5) '@storybook/react': 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.0.4)(webpack@5.94.0(webpack-cli@4.9.1)) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.0.4)(webpack@5.94.0) '@types/node': 22.10.1 '@types/semver': 7.5.8 find-up: 5.0.0 @@ -17560,7 +17563,7 @@ snapshots: dependencies: storybook: 8.4.6 - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.0.4)(webpack@5.94.0(webpack-cli@4.9.1))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.0.4)(webpack@5.94.0)': dependencies: debug: 4.3.4 endent: 2.1.0 @@ -17586,10 +17589,10 @@ snapshots: react-dom: 18.3.1(react@18.3.1) storybook: 8.4.6 - '@storybook/react-webpack5@8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1(webpack@5.94.0))': + '@storybook/react-webpack5@8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1)': dependencies: - '@storybook/builder-webpack5': 8.3.5(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1(webpack@5.94.0)) - '@storybook/preset-react-webpack': 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1(webpack@5.94.0)) + '@storybook/builder-webpack5': 8.3.5(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1) + '@storybook/preset-react-webpack': 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4)(webpack-cli@4.9.1) '@storybook/react': 8.3.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.0.4) '@types/node': 22.10.1 react: 18.3.1 @@ -17679,7 +17682,7 @@ snapshots: jest-serializer-html: 7.1.0 jest-watch-typeahead: 2.2.2(jest@29.7.0) nyc: 15.1.0 - playwright: 1.45.1 + playwright: 1.48.2 transitivePeerDependencies: - '@swc/helpers' - '@types/node' @@ -18724,17 +18727,17 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@1.2.0(webpack-cli@4.9.1(webpack@5.94.0))(webpack@5.94.0(webpack-cli@4.9.1))': + '@webpack-cli/configtest@1.2.0(webpack-cli@4.9.1)(webpack@5.94.0)': dependencies: webpack: 5.94.0(webpack-cli@4.9.1) webpack-cli: 4.9.1(webpack@5.94.0) - '@webpack-cli/info@1.5.0(webpack-cli@4.9.1(webpack@5.94.0))': + '@webpack-cli/info@1.5.0(webpack-cli@4.9.1)': dependencies: envinfo: 7.14.0 webpack-cli: 4.9.1(webpack@5.94.0) - '@webpack-cli/serve@1.7.0(webpack-cli@4.9.1(webpack@5.94.0))': + '@webpack-cli/serve@1.7.0(webpack-cli@4.9.1)': dependencies: webpack-cli: 4.9.1(webpack@5.94.0) @@ -19694,7 +19697,7 @@ snapshots: moment: 2.29.4 moment-timezone: 0.5.46 - '@wordpress/dependency-extraction-webpack-plugin@6.14.0(webpack@5.94.0(webpack-cli@4.9.1))': + '@wordpress/dependency-extraction-webpack-plugin@6.14.0(webpack@5.94.0)': dependencies: json2php: 0.0.7 webpack: 5.94.0(webpack-cli@4.9.1) @@ -19993,7 +19996,7 @@ snapshots: dependencies: '@babel/runtime': 7.25.7 - '@wordpress/eslint-plugin@22.0.0(@babel/core@7.26.0)(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint-plugin-import@2.31.0(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0))(eslint-plugin-jest@28.9.0(eslint@9.16.0)(jest@29.7.0)(typescript@5.0.4))(eslint-plugin-jsdoc@50.6.0(eslint@9.16.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@9.16.0))(eslint-plugin-playwright@2.1.0(eslint@9.16.0))(eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint@9.16.0)(wp-prettier@3.0.3))(eslint-plugin-react-hooks@5.1.0(eslint@9.16.0))(eslint-plugin-react@7.37.2(eslint@9.16.0))(eslint@9.16.0)(typescript@5.0.4)(wp-prettier@3.0.3)': + '@wordpress/eslint-plugin@22.0.0(@babel/core@7.26.0)(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint-plugin-import@2.31.0)(eslint-plugin-jest@28.9.0(eslint@9.16.0)(jest@29.7.0)(typescript@5.0.4))(eslint-plugin-jsdoc@50.6.0(eslint@9.16.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@9.16.0))(eslint-plugin-playwright@2.1.0(eslint@9.16.0))(eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint@9.16.0)(wp-prettier@3.0.3))(eslint-plugin-react-hooks@5.1.0(eslint@9.16.0))(eslint-plugin-react@7.37.2(eslint@9.16.0))(eslint@9.16.0)(typescript@5.0.4)(wp-prettier@3.0.3)': dependencies: '@babel/core': 7.26.0 '@babel/eslint-parser': 7.25.9(@babel/core@7.26.0)(eslint@9.16.0) @@ -21075,14 +21078,14 @@ snapshots: transitivePeerDependencies: - supports-color - babel-loader@9.1.2(@babel/core@7.26.0)(webpack@5.94.0(webpack-cli@4.9.1)): + babel-loader@9.1.2(@babel/core@7.26.0)(webpack@5.94.0): dependencies: '@babel/core': 7.26.0 find-cache-dir: 3.3.2 schema-utils: 4.2.0 webpack: 5.94.0(webpack-cli@4.9.1) - babel-loader@9.2.1(@babel/core@7.26.0)(webpack@5.94.0(webpack-cli@4.9.1)): + babel-loader@9.2.1(@babel/core@7.26.0)(webpack@5.94.0): dependencies: '@babel/core': 7.26.0 find-cache-dir: 4.0.0 @@ -21717,7 +21720,7 @@ snapshots: cookie@1.0.1: {} - copy-webpack-plugin@11.0.0(webpack@5.94.0(webpack-cli@4.9.1)): + copy-webpack-plugin@11.0.0(webpack@5.94.0): dependencies: fast-glob: 3.3.2 glob-parent: 6.0.2 @@ -21796,7 +21799,7 @@ snapshots: dependencies: postcss: 8.4.47 - css-loader@6.11.0(webpack@5.94.0(webpack-cli@4.9.1)): + css-loader@6.11.0(webpack@5.94.0): dependencies: icss-utils: 5.1.0(postcss@8.4.47) postcss: 8.4.47 @@ -21809,7 +21812,7 @@ snapshots: optionalDependencies: webpack: 5.94.0(webpack-cli@4.9.1) - css-loader@6.5.1(webpack@5.94.0(webpack-cli@4.9.1)): + css-loader@6.5.1(webpack@5.94.0): dependencies: icss-utils: 5.1.0(postcss@8.4.47) postcss: 8.4.47 @@ -21821,7 +21824,7 @@ snapshots: semver: 7.5.2 webpack: 5.94.0(webpack-cli@4.9.1) - css-minimizer-webpack-plugin@5.0.1(webpack@5.94.0(webpack-cli@4.9.1)): + css-minimizer-webpack-plugin@5.0.1(webpack@5.94.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 cssnano: 6.1.2(postcss@8.4.47) @@ -22428,7 +22431,7 @@ snapshots: es6-error@4.1.1: {} - esbuild-loader@3.0.1(webpack@5.94.0(webpack-cli@4.9.1)): + esbuild-loader@3.0.1(webpack@5.94.0): dependencies: esbuild: 0.17.19 get-tsconfig: 4.8.1 @@ -22582,7 +22585,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@9.16.0))(eslint@9.16.0): + eslint-module-utils@2.12.0(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -22610,7 +22613,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.16.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0(eslint-plugin-import@2.31.0)(eslint@9.16.0))(eslint@9.16.0) + eslint-module-utils: 2.12.0(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.7.0)(eslint@9.16.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -23149,7 +23152,7 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.0.4)(webpack@5.94.0(webpack-cli@4.9.1)): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.0.4)(webpack@5.94.0): dependencies: '@babel/code-frame': 7.26.2 chalk: 4.1.2 @@ -23166,7 +23169,7 @@ snapshots: typescript: 5.0.4 webpack: 5.94.0(webpack-cli@4.9.1) - fork-ts-checker-webpack-plugin@9.0.2(typescript@5.0.4)(webpack@5.94.0(webpack-cli@4.9.1)): + fork-ts-checker-webpack-plugin@9.0.2(typescript@5.0.4)(webpack@5.94.0): dependencies: '@babel/code-frame': 7.26.2 chalk: 4.1.2 @@ -23510,7 +23513,7 @@ snapshots: html-tags@3.3.1: {} - html-webpack-plugin@5.6.3(webpack@5.94.0(webpack-cli@4.9.1)): + html-webpack-plugin@5.6.3(webpack@5.94.0): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -23588,7 +23591,7 @@ snapshots: dependencies: ms: 2.1.3 - husky@8.0.3: {} + husky@9.1.7: {} iconv-lite@0.4.24: dependencies: @@ -24196,7 +24199,7 @@ snapshots: jest-process-manager: 0.4.0 jest-runner: 29.7.0 nyc: 15.1.0 - playwright-core: 1.49.0 + playwright-core: 1.49.1 rimraf: 3.0.2 uuid: 8.3.2 transitivePeerDependencies: @@ -25154,7 +25157,7 @@ snapshots: min-indent@1.0.1: {} - mini-css-extract-plugin@2.9.1(webpack@5.94.0(webpack-cli@4.9.1)): + mini-css-extract-plugin@2.9.1(webpack@5.94.0): dependencies: schema-utils: 4.2.0 tapable: 2.2.1 @@ -25657,13 +25660,13 @@ snapshots: dependencies: find-up: 6.3.0 - playwright-core@1.45.1: {} + playwright-core@1.48.2: {} - playwright-core@1.49.0: {} + playwright-core@1.49.1: {} - playwright@1.45.1: + playwright@1.48.2: dependencies: - playwright-core: 1.45.1 + playwright-core: 1.48.2 optionalDependencies: fsevents: 2.3.2 @@ -25723,7 +25726,7 @@ snapshots: optionalDependencies: postcss: 8.4.47 - postcss-loader@6.2.0(postcss@8.4.47)(webpack@5.94.0(webpack-cli@4.9.1)): + postcss-loader@6.2.0(postcss@8.4.47)(webpack@5.94.0): dependencies: cosmiconfig: 7.1.0 klona: 2.0.6 @@ -26707,7 +26710,7 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@12.4.0(sass@1.64.1)(webpack@5.94.0(webpack-cli@4.9.1)): + sass-loader@12.4.0(sass@1.64.1)(webpack@5.94.0): dependencies: klona: 2.0.6 neo-async: 2.6.2 @@ -27174,13 +27177,13 @@ snapshots: style-inject@0.3.0: {} - style-loader@2.0.0(webpack@5.94.0(webpack-cli@4.9.1)): + style-loader@2.0.0(webpack@5.94.0): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 webpack: 5.94.0(webpack-cli@4.9.1) - style-loader@3.3.4(webpack@5.94.0(webpack-cli@4.9.1)): + style-loader@3.3.4(webpack@5.94.0): dependencies: webpack: 5.94.0(webpack-cli@4.9.1) @@ -27348,7 +27351,7 @@ snapshots: dependencies: memoizerific: 1.11.3 - terser-webpack-plugin@5.3.10(webpack@5.94.0(webpack-cli@4.9.1)): + terser-webpack-plugin@5.3.10(webpack@5.94.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 @@ -27357,7 +27360,7 @@ snapshots: terser: 5.37.0 webpack: 5.94.0(webpack-cli@4.9.1) - terser-webpack-plugin@5.3.3(webpack@5.94.0(webpack-cli@4.9.1)): + terser-webpack-plugin@5.3.3(webpack@5.94.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 @@ -27385,7 +27388,7 @@ snapshots: text-hex@1.0.0: {} - thread-loader@3.0.4(webpack@5.94.0(webpack-cli@4.9.1)): + thread-loader@3.0.4(webpack@5.94.0): dependencies: json-parse-better-errors: 1.0.2 loader-runner: 4.3.0 @@ -27871,9 +27874,9 @@ snapshots: webpack-cli@4.9.1(webpack@5.94.0): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 1.2.0(webpack-cli@4.9.1(webpack@5.94.0))(webpack@5.94.0(webpack-cli@4.9.1)) - '@webpack-cli/info': 1.5.0(webpack-cli@4.9.1(webpack@5.94.0)) - '@webpack-cli/serve': 1.7.0(webpack-cli@4.9.1(webpack@5.94.0)) + '@webpack-cli/configtest': 1.2.0(webpack-cli@4.9.1)(webpack@5.94.0) + '@webpack-cli/info': 1.5.0(webpack-cli@4.9.1) + '@webpack-cli/serve': 1.7.0(webpack-cli@4.9.1) colorette: 2.0.20 commander: 7.2.0 execa: 5.1.1 @@ -27884,7 +27887,7 @@ snapshots: webpack: 5.94.0(webpack-cli@4.9.1) webpack-merge: 5.10.0 - webpack-dev-middleware@5.3.4(webpack@5.94.0(webpack-cli@4.9.1)): + webpack-dev-middleware@5.3.4(webpack@5.94.0): dependencies: colorette: 2.0.20 memfs: 3.5.3 @@ -27893,7 +27896,7 @@ snapshots: schema-utils: 4.2.0 webpack: 5.94.0(webpack-cli@4.9.1) - webpack-dev-middleware@6.1.3(webpack@5.94.0(webpack-cli@4.9.1)): + webpack-dev-middleware@6.1.3(webpack@5.94.0): dependencies: colorette: 2.0.20 memfs: 3.5.3 @@ -27946,7 +27949,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(webpack@5.94.0(webpack-cli@4.9.1)) + terser-webpack-plugin: 5.3.10(webpack@5.94.0) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: diff --git a/projects/github-actions/repo-gardening/README.md b/projects/github-actions/repo-gardening/README.md index 174af439a2056..df9209c409cd8 100644 --- a/projects/github-actions/repo-gardening/README.md +++ b/projects/github-actions/repo-gardening/README.md @@ -104,7 +104,7 @@ To get the channel ID of the channel where you'd like to post, copy one of the m Certain tasks require filesystem access to the PR, which `pull_request_target` does not provide. To accommodate this, you'll need to include a step to check the PR out in a subdirectory, like ```yaml - - name: Checkout the PR + - name: Check out the PR if: github.event_name == 'pull_request_target' uses: actions/checkout@v4 with: diff --git a/projects/github-actions/repo-gardening/changelog/fix-gardening-catch_max_labels b/projects/github-actions/repo-gardening/changelog/fix-gardening-catch_max_labels new file mode 100644 index 0000000000000..1723c0443417c --- /dev/null +++ b/projects/github-actions/repo-gardening/changelog/fix-gardening-catch_max_labels @@ -0,0 +1,5 @@ +Significance: patch +Type: fixed +Comment: Gardening: Prevent error if more than 100 labels are added to a PR. + + diff --git a/projects/github-actions/repo-gardening/changelog/fix-gardening-catch_max_labels2 b/projects/github-actions/repo-gardening/changelog/fix-gardening-catch_max_labels2 new file mode 100644 index 0000000000000..c0090e71358eb --- /dev/null +++ b/projects/github-actions/repo-gardening/changelog/fix-gardening-catch_max_labels2 @@ -0,0 +1,5 @@ +Significance: patch +Type: fixed +Comment: Gardening: Add an "all the things" label if more than 90 labels are on a PR. + + diff --git a/projects/github-actions/repo-gardening/src/tasks/add-labels/index.js b/projects/github-actions/repo-gardening/src/tasks/add-labels/index.js index 30a95cbbb4357..ea6164ab6b999 100644 --- a/projects/github-actions/repo-gardening/src/tasks/add-labels/index.js +++ b/projects/github-actions/repo-gardening/src/tasks/add-labels/index.js @@ -1,6 +1,7 @@ const { getInput } = require( '@actions/core' ); const debug = require( '../../utils/debug' ); const getFiles = require( '../../utils/get-files' ); +const getLabels = require( '../../utils/labels/get-labels' ); /* global GitHub, WebhookPayloadPullRequest */ @@ -49,7 +50,7 @@ function cleanName( name ) { } /** - * Build a list of labels to add to the issue, based off our file list. + * Build a list of labels to add to the pull request, based off our file list. * * @param {GitHub} octokit - Initialized Octokit REST client. * @param {string} owner - Repository owner. @@ -59,7 +60,7 @@ function cleanName( name ) { * @param {boolean} isRevert - Whether the pull request is a revert. * @return {Promise} Promise resolving to an array of keywords we'll search for. */ -async function getLabelsToAdd( octokit, owner, repo, number, isDraft, isRevert ) { +async function getFileDerivedLabels( octokit, owner, repo, number, isDraft, isRevert ) { const keywords = new Set(); // Get next valid milestone. @@ -320,7 +321,7 @@ async function getLabelsToAdd( octokit, owner, repo, number, isDraft, isRevert ) } /** - * Assigns any issues that are being worked to the author of the matching PR. + * Adds appropriate labels to the specified PR. * * @param {WebhookPayloadPullRequest} payload - Pull request event payload. * @param {GitHub} octokit - Initialized Octokit REST client. @@ -330,26 +331,83 @@ async function addLabels( payload, octokit ) { const { owner, name } = repository; const { draft, title } = pull_request; + // GitHub allows 100 labels on a PR. + // Limit to less than that to allow a buffer for future manual labels. + const maxLabels = 90; + const bigProjectLabel = '[Project] All the things!'; + // Get labels to add to the PR. const isDraft = !! ( pull_request && draft ); // If the PR title includes the word "revert", mark it as such. const isRevert = title.toLowerCase().includes( 'revert' ); - const labels = await getLabelsToAdd( octokit, owner.login, name, number, isDraft, isRevert ); + const fileDerivedLabels = await getFileDerivedLabels( + octokit, + owner.login, + name, + number, + isDraft, + isRevert + ); + + // Grab current labels on the PR. + // We can't rely on payload, as it could be outdated by the time this runs. + const currentLabels = await getLabels( octokit, owner.login, name, number ); + + // This is an array of labels that GitHub doesn't already have. + let labelsToAdd = fileDerivedLabels.filter( label => ! currentLabels.includes( label ) ); - if ( ! labels.length ) { - debug( 'add-labels: Could not find labels to add to that PR. Aborting' ); + // Nothing new was added, so abort. + if ( labelsToAdd.length === 0 ) { + debug( 'add-labels: No new labels to add to that PR. Aborting.' ); return; } - debug( `add-labels: Adding labels ${ labels } to PR #${ number }` ); + // Determine how many labels can safely be added. + let maxLabelsToAdd = Math.max( 0, maxLabels - currentLabels.length ); + + // Overkill, but let's prevent this label from counting toward the max. + const hasBigProjectLabel = currentLabels.includes( bigProjectLabel ); + if ( hasBigProjectLabel ) { + maxLabelsToAdd++; + } + + // If there are too many labels, we need to reduce the label count to keep GitHub happy. + if ( labelsToAdd.length > maxLabelsToAdd ) { + debug( `add-labels: Too many labels! Grouping project labels into '${ bigProjectLabel }'.` ); + + // Filter out project-type labels in deference to bigProjectLabel. + // In theory we could also remove any existing project-type labels here, but for now + // let's not as that would prevent manually adding specific project labels. + const projectLabelRegex = /^(\[Action\]|\[Package\]|\[Plugin\]|\[JS Package\])/; + labelsToAdd = labelsToAdd.filter( label => ! projectLabelRegex.test( label ) ); + + if ( ! hasBigProjectLabel ) { + // Add to the beginning of the labels array in case the array gets truncated later on. + labelsToAdd.unshift( bigProjectLabel ); + } + } else if ( hasBigProjectLabel ) { + await octokit.rest.issues.removeLabel( { + owner: owner.login, + repo: name, + issue_number: number, + name: bigProjectLabel, + } ); + } + // In the rare chance there would still be too many labels... + if ( labelsToAdd.length > maxLabelsToAdd ) { + debug( `add-labels: Limiting to the first ${ maxLabels }.` ); + labelsToAdd.splice( maxLabelsToAdd ); + } + + debug( `add-labels: Adding labels ${ labelsToAdd } to PR #${ number }` ); await octokit.rest.issues.addLabels( { owner: owner.login, repo: name, issue_number: number, - labels, + labels: labelsToAdd, } ); } diff --git a/projects/js-packages/ai-client/CHANGELOG.md b/projects/js-packages/ai-client/CHANGELOG.md index 7fb63198f9391..4b2d8f705f7c8 100644 --- a/projects/js-packages/ai-client/CHANGELOG.md +++ b/projects/js-packages/ai-client/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.25.3] - 2024-12-23 +### Added +- Jetpack AI: Add thumbs up/down component to AI logo generator [#40610] + ## [0.25.2] - 2024-12-16 ### Changed - Updated package dependencies. [#40564] @@ -483,6 +487,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - AI Client: stop using smart document visibility handling on the fetchEventSource library, so it does not restart the completion when changing tabs. [#32004] - Updated package dependencies. [#31468] [#31659] [#31785] +[0.25.3]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.2...v0.25.3 [0.25.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.1...v0.25.2 [0.25.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.0...v0.25.1 [0.25.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.24.3...v0.25.0 diff --git a/projects/js-packages/ai-client/package.json b/projects/js-packages/ai-client/package.json index f101983f9841d..ac14f9cd15232 100644 --- a/projects/js-packages/ai-client/package.json +++ b/projects/js-packages/ai-client/package.json @@ -1,7 +1,7 @@ { "private": false, "name": "@automattic/jetpack-ai-client", - "version": "0.25.2", + "version": "0.25.3", "description": "A JS client for consuming Jetpack AI services", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/ai-client/#readme", "bugs": { @@ -52,6 +52,7 @@ "@types/react": "18.3.12", "@types/wordpress__block-editor": "11.5.15", "@wordpress/api-fetch": "7.14.0", + "@wordpress/base-styles": "5.14.0", "@wordpress/blob": "4.14.0", "@wordpress/block-editor": "14.9.0", "@wordpress/components": "29.0.0", diff --git a/projects/js-packages/ai-client/src/components/ai-feedback/index.tsx b/projects/js-packages/ai-client/src/components/ai-feedback/index.tsx new file mode 100644 index 0000000000000..a6f6ed67ebbf1 --- /dev/null +++ b/projects/js-packages/ai-client/src/components/ai-feedback/index.tsx @@ -0,0 +1,133 @@ +/** + * External dependencies + */ +import { + useAnalytics, + getJetpackExtensionAvailability, +} from '@automattic/jetpack-shared-extension-utils'; +import { Button, Tooltip } from '@wordpress/components'; +import { useEffect, useState } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { thumbsUp, thumbsDown } from '@wordpress/icons'; +import clsx from 'clsx'; +/* + * Internal dependencies + */ +import './style.scss'; +/** + * Types + */ +import type React from 'react'; + +type AiFeedbackThumbsProps = { + disabled?: boolean; + iconSize?: number; + ratedItem?: string; + feature?: string; + savedRatings?: Record< string, string >; + options?: { + mediaLibraryId?: number; + prompt?: string; + revisedPrompt?: string; + }; + onRate?: ( rating: string ) => void; +}; + +/** + * Get the availability of a feature. + * + * @param {string} feature - The feature to check availability for. + * @return {boolean} - Whether the feature is available. + */ +function getFeatureAvailability( feature: string ): boolean { + return getJetpackExtensionAvailability( feature ).available === true; +} + +/** + * AiFeedbackThumbs component. + * + * @param {AiFeedbackThumbsProps} props - component props. + * @return {React.ReactElement} - rendered component. + */ +export default function AiFeedbackThumbs( { + disabled = false, + iconSize = 24, + ratedItem = '', + feature = '', + savedRatings = {}, + options = {}, + onRate, +}: AiFeedbackThumbsProps ): React.ReactElement { + if ( ! getFeatureAvailability( 'ai-response-feedback' ) ) { + return null; + } + + const [ itemsRated, setItemsRated ] = useState( {} ); + const { tracks } = useAnalytics(); + + useEffect( () => { + const newItemsRated = { ...savedRatings, ...itemsRated }; + + if ( JSON.stringify( newItemsRated ) !== JSON.stringify( itemsRated ) ) { + setItemsRated( newItemsRated ); + } + }, [ savedRatings ] ); + + const checkThumb = ( thumbValue: string ) => { + if ( ! itemsRated[ ratedItem ] ) { + return false; + } + + return itemsRated[ ratedItem ] === thumbValue; + }; + + const rateAI = ( isThumbsUp: boolean ) => { + const aiRating = isThumbsUp ? 'thumbs-up' : 'thumbs-down'; + + if ( ! checkThumb( aiRating ) ) { + setItemsRated( { + ...itemsRated, + [ ratedItem ]: aiRating, + } ); + + onRate?.( aiRating ); + + tracks.recordEvent( 'jetpack_ai_feedback', { + type: feature, + rating: aiRating, + mediaLibraryId: options.mediaLibraryId || null, + prompt: options.prompt || null, + revisedPrompt: options.revisedPrompt || null, + } ); + } + }; + + return ( +
+ +
+ ); +} diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-feedback/style.scss b/projects/js-packages/ai-client/src/components/ai-feedback/style.scss similarity index 70% rename from projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-feedback/style.scss rename to projects/js-packages/ai-client/src/components/ai-feedback/style.scss index 9697d17aff1ad..89b8f9f2053bc 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-feedback/style.scss +++ b/projects/js-packages/ai-client/src/components/ai-feedback/style.scss @@ -11,6 +11,6 @@ } &__thumb-selected { - color: var(--wp-components-color-accent,var(--wp-admin-theme-color,#3858e9)); + color: var( --wp-components-color-accent, var( --wp-admin-theme-color, #3858e9 ) ); } } diff --git a/projects/js-packages/ai-client/src/components/index.ts b/projects/js-packages/ai-client/src/components/index.ts index 20d5714db6baf..1ef8b40b80426 100644 --- a/projects/js-packages/ai-client/src/components/index.ts +++ b/projects/js-packages/ai-client/src/components/index.ts @@ -1,4 +1,5 @@ export { AIControl, BlockAIControl, ExtensionAIControl } from './ai-control/index.js'; +export { default as AiFeedbackThumbs } from './ai-feedback/index.js'; export { default as AiStatusIndicator } from './ai-status-indicator/index.js'; export { default as AudioDurationDisplay } from './audio-duration-display/index.js'; export { default as AiModalFooter } from './ai-modal-footer/index.js'; diff --git a/projects/js-packages/ai-client/src/logo-generator/components/logo-presenter.tsx b/projects/js-packages/ai-client/src/logo-generator/components/logo-presenter.tsx index e539e1d751b6a..2b7e63ff9c9e6 100644 --- a/projects/js-packages/ai-client/src/logo-generator/components/logo-presenter.tsx +++ b/projects/js-packages/ai-client/src/logo-generator/components/logo-presenter.tsx @@ -9,6 +9,7 @@ import debugFactory from 'debug'; /** * Internal dependencies */ +import AiFeedbackThumbs from '../../components/ai-feedback/index.js'; import CheckIcon from '../assets/icons/check.js'; import LogoIcon from '../assets/icons/logo.js'; import MediaIcon from '../assets/icons/media.js'; @@ -152,11 +153,50 @@ const LogoEmpty: React.FC = () => { ); }; +const RateLogo: React.FC< { + disabled: boolean; + ratedItem: string; + onRate: ( rating: string ) => void; +} > = ( { disabled, ratedItem, onRate } ) => { + const { logos, selectedLogo } = useLogoGenerator(); + const savedRatings = logos + .filter( logo => logo.rating ) + .reduce( ( acc, logo ) => { + acc[ logo.url ] = logo.rating; + return acc; + }, {} ); + + return ( + + ); +}; + const LogoReady: React.FC< { siteId: string; logo: Logo; onApplyLogo: ( mediaId: number ) => void; } > = ( { siteId, logo, onApplyLogo } ) => { + const handleRateLogo = ( rating: string ) => { + // Update localStorage + updateLogo( { + siteId, + url: logo.url, + newUrl: logo.url, + mediaId: logo.mediaId, + rating, + } ); + }; + return ( <> + diff --git a/projects/js-packages/ai-client/src/logo-generator/hooks/use-logo-generator.ts b/projects/js-packages/ai-client/src/logo-generator/hooks/use-logo-generator.ts index 70acbf3fe98e0..d2d232da900d0 100644 --- a/projects/js-packages/ai-client/src/logo-generator/hooks/use-logo-generator.ts +++ b/projects/js-packages/ai-client/src/logo-generator/hooks/use-logo-generator.ts @@ -412,10 +412,13 @@ User request:${ prompt }`; throw error; } + const revisedPrompt = image.data[ 0 ].revised_prompt || null; + // response_format=url returns object with url, otherwise b64_json const logo: Logo = { url: 'data:image/png;base64,' + image.data[ 0 ].b64_json, description: prompt, + revisedPrompt, }; try { @@ -424,6 +427,7 @@ User request:${ prompt }`; url: savedLogo.mediaURL, description: prompt, mediaId: savedLogo.mediaId, + revisedPrompt, } ); } catch ( error ) { storeLogo( logo ); diff --git a/projects/js-packages/ai-client/src/logo-generator/lib/logo-storage.ts b/projects/js-packages/ai-client/src/logo-generator/lib/logo-storage.ts index 81e9b08319575..e6c6bb1176e83 100644 --- a/projects/js-packages/ai-client/src/logo-generator/lib/logo-storage.ts +++ b/projects/js-packages/ai-client/src/logo-generator/lib/logo-storage.ts @@ -10,21 +10,28 @@ const MAX_LOGOS = 10; /** * Add an entry to the site's logo history. * - * @param {SaveToStorageProps} saveToStorageProps - The properties to save to storage - * @param {SaveToStorageProps.siteId} saveToStorageProps.siteId - The site ID - * @param {SaveToStorageProps.url} saveToStorageProps.url - The URL of the logo - * @param {SaveToStorageProps.description} saveToStorageProps.description - The description of the logo, based on the prompt used to generate it - * @param {SaveToStorageProps.mediaId} saveToStorageProps.mediaId - The media ID of the logo on the backend - * + * @param {SaveToStorageProps} saveToStorageProps - The properties to save to storage + * @param {SaveToStorageProps.siteId} saveToStorageProps.siteId - The site ID + * @param {SaveToStorageProps.url} saveToStorageProps.url - The URL of the logo + * @param {SaveToStorageProps.description} saveToStorageProps.description - The description of the logo, based on the prompt used to generate it + * @param {SaveToStorageProps.mediaId} saveToStorageProps.mediaId - The media ID of the logo on the backend + * @param {SaveToStorageProps.revisedPrompt} saveToStorageProps.revisedPrompt - The revised prompt of the logo * @return {Logo} The logo that was saved */ -export function stashLogo( { siteId, url, description, mediaId }: SaveToStorageProps ) { +export function stashLogo( { + siteId, + url, + description, + mediaId, + revisedPrompt, +}: SaveToStorageProps ) { const storedContent = getSiteLogoHistory( siteId ); const logo: Logo = { url, description, mediaId, + revisedPrompt, }; storedContent.push( logo ); @@ -45,9 +52,10 @@ export function stashLogo( { siteId, url, description, mediaId }: SaveToStorageP * @param {UpdateInStorageProps.url} updateInStorageProps.url - The URL of the logo to update * @param {UpdateInStorageProps.newUrl} updateInStorageProps.newUrl - The new URL of the logo * @param {UpdateInStorageProps.mediaId} updateInStorageProps.mediaId - The new media ID of the logo + * @param {UpdateInStorageProps.rating} updateInStorageProps.rating - The new rating of the logo * @return {Logo} The logo that was updated */ -export function updateLogo( { siteId, url, newUrl, mediaId }: UpdateInStorageProps ) { +export function updateLogo( { siteId, url, newUrl, mediaId, rating }: UpdateInStorageProps ) { const storedContent = getSiteLogoHistory( siteId ); const index = storedContent.findIndex( logo => logo.url === url ); @@ -55,6 +63,7 @@ export function updateLogo( { siteId, url, newUrl, mediaId }: UpdateInStoragePro if ( index > -1 ) { storedContent[ index ].url = newUrl; storedContent[ index ].mediaId = mediaId; + storedContent[ index ].rating = rating; } localStorage.setItem( @@ -96,6 +105,7 @@ export function getSiteLogoHistory( siteId: string ) { url: logo.url, description: logo.description, mediaId: logo.mediaId, + rating: logo.rating, } ) ); return storedContent; diff --git a/projects/js-packages/ai-client/src/logo-generator/store/types.ts b/projects/js-packages/ai-client/src/logo-generator/store/types.ts index b34f3b9f8a22e..d8c0f0fbcb1bf 100644 --- a/projects/js-packages/ai-client/src/logo-generator/store/types.ts +++ b/projects/js-packages/ai-client/src/logo-generator/store/types.ts @@ -140,6 +140,8 @@ export type Logo = { url: string; description: string; mediaId?: number; + rating?: string; + revisedPrompt?: string; }; export type RequestError = string | Error | null; diff --git a/projects/js-packages/ai-client/src/logo-generator/types.ts b/projects/js-packages/ai-client/src/logo-generator/types.ts index e54cf774ac98e..2a617a2b97d58 100644 --- a/projects/js-packages/ai-client/src/logo-generator/types.ts +++ b/projects/js-packages/ai-client/src/logo-generator/types.ts @@ -92,6 +92,7 @@ export type UpdateInStorageProps = { url: Logo[ 'url' ]; newUrl: Logo[ 'url' ]; mediaId: Logo[ 'mediaId' ]; + rating?: Logo[ 'rating' ]; }; export type RemoveFromStorageProps = { diff --git a/projects/js-packages/charts/CHANGELOG.md b/projects/js-packages/charts/CHANGELOG.md index 721294abd00ad..5bc5149001dbd 100644 --- a/projects/js-packages/charts/CHANGELOG.md +++ b/projects/js-packages/charts/CHANGELOG.md @@ -5,3 +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.1.0 - 2024-12-20 +### Added +- Adding a theme provider to Automattic Charts [#40558] +- Adding build option for Charts. [#40676] +- Adding new chart type - pie chart. [#40581] +- Adding new chart type. [#40466] +- Adding support for multiple data series to the Bar chart component. [#40641] +- Adding support for mutliple data series for the line charts. [#40605] +- Chart library: add legend component [#40594] +- Charts: adds Barchart functionality and storybook item [#40353] +- Charts: adds tooltip component [#40495] +- Initial version. [#40250] + +### Changed +- Chart Library: Update tooltip component [#40582] +- Update PieSemiCircleChart component [#40625] + +### Fixed +- Fixed lints following ESLint rule changes for TS [#40584] +- Fixing a bug in Chart storybook data. [#40640] diff --git a/projects/js-packages/charts/changelog/add-chart-library-legend-component b/projects/js-packages/charts/changelog/add-chart-library-legend-component deleted file mode 100644 index 47958063ac1fa..0000000000000 --- a/projects/js-packages/charts/changelog/add-chart-library-legend-component +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Chart library: add legend component diff --git a/projects/js-packages/charts/changelog/add-charts-bar-multi b/projects/js-packages/charts/changelog/add-charts-bar-multi deleted file mode 100644 index cc4a689ae6231..0000000000000 --- a/projects/js-packages/charts/changelog/add-charts-bar-multi +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -Adding support for multiple data series to the Bar chart component. diff --git a/projects/js-packages/charts/changelog/add-charts-basic-line b/projects/js-packages/charts/changelog/add-charts-basic-line deleted file mode 100644 index d9ac6c96a0e66..0000000000000 --- a/projects/js-packages/charts/changelog/add-charts-basic-line +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -Adding new chart type. diff --git a/projects/js-packages/charts/changelog/add-charts-multi-series b/projects/js-packages/charts/changelog/add-charts-multi-series deleted file mode 100644 index c5707223a9596..0000000000000 --- a/projects/js-packages/charts/changelog/add-charts-multi-series +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -Adding support for mutliple data series for the line charts. diff --git a/projects/js-packages/charts/changelog/add-charts-pie-chart b/projects/js-packages/charts/changelog/add-charts-pie-chart deleted file mode 100644 index 81e8c5063e8ae..0000000000000 --- a/projects/js-packages/charts/changelog/add-charts-pie-chart +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -Adding new chart type - pie chart. diff --git a/projects/js-packages/charts/changelog/add-charts-themes b/projects/js-packages/charts/changelog/add-charts-themes deleted file mode 100644 index bed6b167cc232..0000000000000 --- a/projects/js-packages/charts/changelog/add-charts-themes +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -Adding a theme provider to Automattic Charts diff --git a/projects/js-packages/charts/changelog/add-js-package-charts-tooltip b/projects/js-packages/charts/changelog/add-js-package-charts-tooltip deleted file mode 100644 index f4735a85f793c..0000000000000 --- a/projects/js-packages/charts/changelog/add-js-package-charts-tooltip +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Charts: adds tooltip component diff --git a/projects/js-packages/charts/changelog/add-try-new-chart-library b/projects/js-packages/charts/changelog/add-try-new-chart-library deleted file mode 100644 index 3f2e0a62d3396..0000000000000 --- a/projects/js-packages/charts/changelog/add-try-new-chart-library +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: added - -Charts: adds Barchart functionality and storybook item diff --git a/projects/js-packages/charts/changelog/fix-charts-line-chart-themes b/projects/js-packages/charts/changelog/fix-charts-line-chart-themes deleted file mode 100644 index 5dc5e61fc4c0a..0000000000000 --- a/projects/js-packages/charts/changelog/fix-charts-line-chart-themes +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Fixing a bug in Chart storybook data. diff --git a/projects/js-packages/charts/changelog/initial-version b/projects/js-packages/charts/changelog/initial-version deleted file mode 100644 index fb1837c901e51..0000000000000 --- a/projects/js-packages/charts/changelog/initial-version +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: added - -Initial version. diff --git a/projects/js-packages/charts/changelog/update-charts-library-tooltip-iteration b/projects/js-packages/charts/changelog/update-charts-library-tooltip-iteration deleted file mode 100644 index b95e70a4acf87..0000000000000 --- a/projects/js-packages/charts/changelog/update-charts-library-tooltip-iteration +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: changed - -Chart Library: Update tooltip component diff --git a/projects/js-packages/charts/changelog/update-charts-pie-iteration b/projects/js-packages/charts/changelog/update-charts-pie-iteration deleted file mode 100644 index 04e1f08148d44..0000000000000 --- a/projects/js-packages/charts/changelog/update-charts-pie-iteration +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: changed - -Update PieSemiCircleChart component diff --git a/projects/js-packages/charts/changelog/update-eslint-9 b/projects/js-packages/charts/changelog/update-eslint-9 deleted file mode 100644 index 1cb10572ab69e..0000000000000 --- a/projects/js-packages/charts/changelog/update-eslint-9 +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Update eslint config for eslint 9. - - diff --git a/projects/js-packages/charts/changelog/update-eslint-config-for-ts-files b/projects/js-packages/charts/changelog/update-eslint-config-for-ts-files deleted file mode 100644 index fefec667583fd..0000000000000 --- a/projects/js-packages/charts/changelog/update-eslint-config-for-ts-files +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fixed - -Fixed lints following ESLint rule changes for TS diff --git a/projects/js-packages/charts/composer.json b/projects/js-packages/charts/composer.json index f2d38414dc78c..41bb0f0dadc60 100644 --- a/projects/js-packages/charts/composer.json +++ b/projects/js-packages/charts/composer.json @@ -13,6 +13,12 @@ ] }, "scripts": { + "build-development": [ + "pnpm run build" + ], + "build-production": [ + "NODE_ENV=production pnpm run build" + ], "test-coverage": [ "pnpm run test-coverage" ], diff --git a/projects/js-packages/charts/index.ts b/projects/js-packages/charts/index.ts new file mode 100644 index 0000000000000..c5666e2773e38 --- /dev/null +++ b/projects/js-packages/charts/index.ts @@ -0,0 +1,19 @@ +// Charts +export { BarChart } from './src/components/bar-chart'; +export { LineChart } from './src/components/line-chart'; +export { PieChart } from './src/components/pie-chart'; +export { PieSemiCircleChart } from './src/components/pie-semi-circle-chart'; + +// Chart components +export { BaseTooltip } from './src/components/tooltip'; +export { Legend } from './src/components/legend'; + +// Providers +export { ThemeProvider } from './src/providers/theme'; + +// Hooks +export { default as useChartMouseHandler } from './src/hooks/use-chart-mouse-handler'; + +// Types +export type * from './src/components/shared/types'; +export type { BaseTooltipProps } from './src/components/tooltip'; diff --git a/projects/js-packages/charts/package.json b/projects/js-packages/charts/package.json index c9acca5d411b5..310db3e1f9cf0 100644 --- a/projects/js-packages/charts/package.json +++ b/projects/js-packages/charts/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/charts", - "version": "0.1.0-alpha", + "version": "0.1.0", "description": "Display charts within Automattic products.", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/charts/#readme", "bugs": { @@ -17,7 +17,10 @@ "clean": "rm -rf node_modules", "test": "jest --config=tests/jest.config.cjs", "test-coverage": "pnpm run test --coverage", - "storybook": "cd ../storybook && pnpm run storybook:dev" + "storybook": "cd ../storybook && pnpm run storybook:dev", + "compile-ts": "tsc --pretty", + "build": "pnpm run clean-build && pnpm run compile-ts", + "clean-build": "rm -rf build/" }, "dependencies": { "@react-spring/web": "9.7.3", diff --git a/projects/js-packages/charts/src/components/bar-chart/index.tsx b/projects/js-packages/charts/src/components/bar-chart/index.tsx index df4a1f97ba3ad..35afda744bf9e 100644 --- a/projects/js-packages/charts/src/components/bar-chart/index.tsx +++ b/projects/js-packages/charts/src/components/bar-chart/index.tsx @@ -1 +1 @@ -export { default } from './bar-chart'; +export { default as BarChart } from './bar-chart'; diff --git a/projects/js-packages/charts/src/components/bar-chart/stories/index.stories.tsx b/projects/js-packages/charts/src/components/bar-chart/stories/index.stories.tsx index 0ca8d40d60462..33dd147e604b4 100644 --- a/projects/js-packages/charts/src/components/bar-chart/stories/index.stories.tsx +++ b/projects/js-packages/charts/src/components/bar-chart/stories/index.stories.tsx @@ -1,4 +1,4 @@ -import BarChart from '../index'; +import { BarChart } from '../index'; import data from './sample-data'; import type { Meta, StoryObj } from '@storybook/react'; diff --git a/projects/js-packages/charts/src/index.ts b/projects/js-packages/charts/src/index.ts index b52a51461252c..cd8a712946e4b 100644 --- a/projects/js-packages/charts/src/index.ts +++ b/projects/js-packages/charts/src/index.ts @@ -1,16 +1,18 @@ // Charts -export { default as BarChart } from './components/bar-chart'; +export { BarChart } from './components/bar-chart'; export { LineChart } from './components/line-chart'; export { PieChart } from './components/pie-chart'; export { PieSemiCircleChart } from './components/pie-semi-circle-chart'; // Chart components export { BaseTooltip } from './components/tooltip'; +export { Legend } from './components/legend'; // Providers export { ThemeProvider } from './providers/theme'; // Hooks +export { default as useChartMouseHandler } from './hooks/use-chart-mouse-handler'; // Types export type * from './components/shared/types'; diff --git a/projects/js-packages/charts/src/providers/theme/stories/index.stories.tsx b/projects/js-packages/charts/src/providers/theme/stories/index.stories.tsx index 31a1aa1c320b1..cd14af571d52f 100644 --- a/projects/js-packages/charts/src/providers/theme/stories/index.stories.tsx +++ b/projects/js-packages/charts/src/providers/theme/stories/index.stories.tsx @@ -1,6 +1,6 @@ import { Meta, StoryObj } from '@storybook/react'; import { ThemeProvider, jetpackTheme, wooTheme } from '../.'; -import { LineChart, BarChart, PieSemiCircleChart } from '../../..'; +import { LineChart, BarChart, PieSemiCircleChart } from '../../../.'; import barSampleData from '../../../components/bar-chart/stories/sample-data'; const meta: Meta< typeof LineChart > = { diff --git a/projects/js-packages/charts/tsconfig.json b/projects/js-packages/charts/tsconfig.json index 0e1116eec9836..6fa28c02613bd 100644 --- a/projects/js-packages/charts/tsconfig.json +++ b/projects/js-packages/charts/tsconfig.json @@ -5,5 +5,5 @@ "outDir": "./build/" }, // List all sources and source-containing subdirs. - "include": [ "./src" ] + "include": [ "./index.ts", "./src" ] } diff --git a/projects/js-packages/components/CHANGELOG.md b/projects/js-packages/components/CHANGELOG.md index 7f48662114eff..664badf23dfb4 100644 --- a/projects/js-packages/components/CHANGELOG.md +++ b/projects/js-packages/components/CHANGELOG.md @@ -2,6 +2,10 @@ ### This is a list detailing changes for the Jetpack RNA Components package releases. +## [0.65.1] - 2024-12-23 +### Changed +- Internal updates. + ## [0.65.0] - 2024-12-16 ### Changed - Fixes ThreatsDataViews defaultLayouts. [#40598] @@ -1251,6 +1255,7 @@ ### Changed - Update node version requirement to 14.16.1 +[0.65.1]: https://github.com/Automattic/jetpack-components/compare/0.65.0...0.65.1 [0.65.0]: https://github.com/Automattic/jetpack-components/compare/0.64.1...0.65.0 [0.64.1]: https://github.com/Automattic/jetpack-components/compare/0.64.0...0.64.1 [0.64.0]: https://github.com/Automattic/jetpack-components/compare/0.63.0...0.64.0 diff --git a/projects/js-packages/components/changelog/notice-component-minor-css-adjustment b/projects/js-packages/components/changelog/notice-component-minor-css-adjustment deleted file mode 100644 index a005ea7cb6a5b..0000000000000 --- a/projects/js-packages/components/changelog/notice-component-minor-css-adjustment +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: changed -Comment: Notice component: Minor css adjustment. - - diff --git a/projects/js-packages/components/package.json b/projects/js-packages/components/package.json index b877c9943635f..9b2982eb5079d 100644 --- a/projects/js-packages/components/package.json +++ b/projects/js-packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@automattic/jetpack-components", - "version": "0.65.0", + "version": "0.65.1", "description": "Jetpack Components Package", "author": "Automattic", "license": "GPL-2.0-or-later", diff --git a/projects/js-packages/critical-css-gen/changelog/renovate-playwright-monorepo b/projects/js-packages/critical-css-gen/changelog/renovate-playwright-monorepo new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/js-packages/critical-css-gen/changelog/renovate-playwright-monorepo @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/js-packages/critical-css-gen/package.json b/projects/js-packages/critical-css-gen/package.json index dfe7ccb4218b0..861323500e52f 100644 --- a/projects/js-packages/critical-css-gen/package.json +++ b/projects/js-packages/critical-css-gen/package.json @@ -34,7 +34,7 @@ "express": "4.21.2", "jest": "29.7.0", "path-browserify": "1.0.1", - "playwright": "1.45.1", + "playwright": "1.48.2", "playwright-core": "^1.45.1", "process": "0.11.10", "source-map": "0.7.4", diff --git a/projects/js-packages/storybook/changelog/renovate-playwright-monorepo b/projects/js-packages/storybook/changelog/renovate-playwright-monorepo new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/js-packages/storybook/changelog/renovate-playwright-monorepo @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/js-packages/storybook/package.json b/projects/js-packages/storybook/package.json index 7984577ae5ffd..82541f0584358 100644 --- a/projects/js-packages/storybook/package.json +++ b/projects/js-packages/storybook/package.json @@ -25,7 +25,7 @@ "@babel/plugin-syntax-jsx": "7.25.9", "@babel/preset-react": "7.25.9", "@babel/runtime": "7.26.0", - "@playwright/test": "1.45.1", + "@playwright/test": "1.48.2", "@storybook/addon-a11y": "8.3.5", "@storybook/addon-docs": "8.3.5", "@storybook/addon-essentials": "8.3.5", diff --git a/projects/packages/boost-core/CHANGELOG.md b/projects/packages/boost-core/CHANGELOG.md index 9bc1eb396b5c9..886821835592d 100644 --- a/projects/packages/boost-core/CHANGELOG.md +++ b/projects/packages/boost-core/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.3] - 2024-12-23 +### Fixed +- General: Fixed not parsing error responses from WordPress.com properly. [#40660] + ## [0.3.2] - 2024-11-28 ### Fixed - Cachable: Make the expiry overridable by child classes. [#40339] @@ -97,6 +101,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Introduce new package. [#31163] +[0.3.3]: https://github.com/Automattic/jetpack-boost-core/compare/v0.3.2...v0.3.3 [0.3.2]: https://github.com/Automattic/jetpack-boost-core/compare/v0.3.1...v0.3.2 [0.3.1]: https://github.com/Automattic/jetpack-boost-core/compare/v0.3.0...v0.3.1 [0.3.0]: https://github.com/Automattic/jetpack-boost-core/compare/v0.2.14...v0.3.0 diff --git a/projects/packages/boost-core/package.json b/projects/packages/boost-core/package.json index dd47e02e58e1e..3ea2772d01755 100644 --- a/projects/packages/boost-core/package.json +++ b/projects/packages/boost-core/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-boost-core", - "version": "0.3.2", + "version": "0.3.3", "description": "Core functionality for boost and relevant packages to depend on", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/boost-core/#readme", "bugs": { diff --git a/projects/packages/boost-core/src/lib/class-utils.php b/projects/packages/boost-core/src/lib/class-utils.php index 848cf3009c0b3..52207e115f075 100644 --- a/projects/packages/boost-core/src/lib/class-utils.php +++ b/projects/packages/boost-core/src/lib/class-utils.php @@ -133,11 +133,26 @@ public static function send_wpcom_request( $method, $endpoint, $args = null, $bo $code ); + /* + * Normalize error responses from WordPress.com. + * + * When WordPress.com returns an error from Boost Cloud, the body contains + * statusCode and error. When it returns a WP_Error, it contains code and message. + */ // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase - $err_code = empty( $data['statusCode'] ) ? 'http_error' : $data['statusCode']; - $message = empty( $data['error'] ) ? $default_message : $data['error']; - - return new \WP_Error( $err_code, $message ); + if ( isset( $data['statusCode'] ) && isset( $data['error'] ) ) { + // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + $data_code = $data['statusCode']; + $data_message = $data['error']; + } elseif ( isset( $data['code'] ) && isset( $data['message'] ) ) { + $data_code = $data['code']; + $data_message = $data['message']; + } + + $error_code = empty( $data_code ) ? 'http_error' : $data_code; + $message = empty( $data_message ) ? $default_message : $data_message; + + return new \WP_Error( $error_code, $message ); } return $data; diff --git a/projects/packages/classic-theme-helper/.phan/baseline.php b/projects/packages/classic-theme-helper/.phan/baseline.php index d1f7843b03b03..92a89530ede7f 100644 --- a/projects/packages/classic-theme-helper/.phan/baseline.php +++ b/projects/packages/classic-theme-helper/.phan/baseline.php @@ -12,9 +12,9 @@ // PhanUndeclaredClassMethod : 20+ occurrences // PhanTypeMismatchArgumentInternal : 10+ occurrences // PhanTypePossiblyInvalidDimOffset : 8 occurrences + // PhanTypeSuspiciousNonTraversableForeach : 6 occurrences // PhanTypeMismatchArgumentProbablyReal : 5 occurrences // PhanUndeclaredClassReference : 4 occurrences - // PhanTypeSuspiciousNonTraversableForeach : 3 occurrences // PhanTypeInvalidDimOffset : 2 occurrences // PhanTypeMismatchArgument : 2 occurrences // PhanTypeComparisonToArray : 1 occurrence @@ -30,6 +30,7 @@ 'src/custom-content-types.php' => ['PhanUndeclaredClassMethod'], 'src/custom-post-types/class-jetpack-portfolio.php' => ['PhanTypeMismatchArgumentProbablyReal', 'PhanTypeSuspiciousNonTraversableForeach', 'PhanUndeclaredClassMethod'], 'src/custom-post-types/class-jetpack-testimonial.php' => ['PhanTypeMismatchArgumentProbablyReal', 'PhanUndeclaredClassMethod'], + 'src/custom-post-types/class-nova-restaurant.php' => ['PhanTypeSuspiciousNonTraversableForeach'], 'src/site-breadcrumbs.php' => ['PhanUndeclaredClassMethod'], ], // 'directory_suppressions' => ['src/directory_name' => ['PhanIssueName1', 'PhanIssueName2']] can be manually added if needed. diff --git a/projects/packages/classic-theme-helper/CHANGELOG.md b/projects/packages/classic-theme-helper/CHANGELOG.md index 702edf2a17030..2dd55e8a04324 100644 --- a/projects/packages/classic-theme-helper/CHANGELOG.md +++ b/projects/packages/classic-theme-helper/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.1] - 2024-12-23 +### Added +- Custom Post Types: Added Restaurant Menu CPT files. [#40668] + ## [0.8.0] - 2024-12-16 ### Added - Jetpack Testimonials: Ensuring functionality runs via the Classic Theme Helper package. [#40388] @@ -177,6 +181,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Add wordpress folder on gitignore. [#37177] +[0.8.1]: https://github.com/Automattic/jetpack-classic-theme-helper/compare/v0.8.0...v0.8.1 [0.8.0]: https://github.com/Automattic/jetpack-classic-theme-helper/compare/v0.7.4...v0.8.0 [0.7.4]: https://github.com/Automattic/jetpack-classic-theme-helper/compare/v0.7.3...v0.7.4 [0.7.3]: https://github.com/Automattic/jetpack-classic-theme-helper/compare/v0.7.2...v0.7.3 diff --git a/projects/packages/classic-theme-helper/package.json b/projects/packages/classic-theme-helper/package.json index 504deb824d9f2..1c0091b2b1543 100644 --- a/projects/packages/classic-theme-helper/package.json +++ b/projects/packages/classic-theme-helper/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@automattic/jetpack-classic-theme-helper", - "version": "0.8.0", + "version": "0.8.1", "description": "Features used with classic themes", "homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/classic-theme-helper/#readme", "bugs": { diff --git a/projects/packages/classic-theme-helper/src/class-main.php b/projects/packages/classic-theme-helper/src/class-main.php index 3c934378d21f7..6fea8cb57aba1 100644 --- a/projects/packages/classic-theme-helper/src/class-main.php +++ b/projects/packages/classic-theme-helper/src/class-main.php @@ -14,7 +14,7 @@ */ class Main { - const PACKAGE_VERSION = '0.8.0'; + const PACKAGE_VERSION = '0.8.1'; /** * Modules to include. diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/class-nova-restaurant.php b/projects/packages/classic-theme-helper/src/custom-post-types/class-nova-restaurant.php new file mode 100644 index 0000000000000..924209b5503dc --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/class-nova-restaurant.php @@ -0,0 +1,1760 @@ + 'section', + * 'menu_class' => 'menu-items', + * 'menu_header_tag' => 'header', + * 'menu_header_class' => 'menu-group-header', + * 'menu_title_tag' => 'h1', + * 'menu_title_class' => 'menu-group-title', + * 'menu_description_tag' => 'div', + * 'menu_description_class' => 'menu-group-description', + * ) ); + * } + * + * @todo + * - Bulk/Quick edit response of Menu Item rows is broken. + * - Drag and Drop reordering. + * + * @package automattic/jetpack-classic-theme-helper + */ + +namespace Automattic\Jetpack\Classic_Theme_Helper; + +use Automattic\Jetpack\Assets; +use Automattic\Jetpack\Roles; +use WP_Post; +use WP_Query; + +if ( ! class_exists( __NAMESPACE__ . '\Nova_Restaurant' ) ) { + + /** + * Create the new Nova CPT. + */ + class Nova_Restaurant { + const MENU_ITEM_POST_TYPE = 'nova_menu_item'; + const MENU_ITEM_LABEL_TAX = 'nova_menu_item_label'; + const MENU_TAX = 'nova_menu'; + + /** + * Version number used when enqueuing all resources (css and js). + * + * @var string + */ + public $version = '20210303'; + + /** + * Default markup for the menu items. + * + * @var array + */ + protected $default_menu_item_loop_markup = array( + 'menu_tag' => 'section', + 'menu_class' => 'menu-items', + 'menu_header_tag' => 'header', + 'menu_header_class' => 'menu-group-header', + 'menu_title_tag' => 'h1', + 'menu_title_class' => 'menu-group-title', + 'menu_description_tag' => 'div', + 'menu_description_class' => 'menu-group-description', + ); + + /** + * Array of markup for the menu items. + * + * @var array + */ + protected $menu_item_loop_markup = array(); + + /** + * Last term ID of a loop of menu items. + * + * @var bool|int + */ + protected $menu_item_loop_last_term_id = false; + + /** + * Current term ID of a loop of menu items. + * + * @var bool|int|\WP_Term + */ + protected $menu_item_loop_current_term = false; + + /** + * Initialize class. + * + * @param array $menu_item_loop_markup Array of markup for the menu items. + * + * @return self + */ + public static function init( $menu_item_loop_markup = array() ) { + static $instance = false; + + if ( ! $instance ) { + $instance = new Nova_Restaurant(); + } + + if ( $menu_item_loop_markup ) { + $instance->menu_item_loop_markup = wp_parse_args( $menu_item_loop_markup, $instance->default_menu_item_loop_markup ); + } + + return $instance; + } + + /** + * Constructor. + * Hook into WordPress to create CPT and utilities if needed. + */ + public function __construct() { + if ( ! $this->site_supports_nova() ) { + return; + } + + $this->register_taxonomies(); + $this->register_post_types(); + add_action( 'admin_menu', array( $this, 'add_admin_menus' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_nova_styles' ) ); + add_action( 'admin_head', array( $this, 'set_custom_font_icon' ) ); + + // Always sort menu items correctly + add_action( 'parse_query', array( $this, 'sort_menu_item_queries_by_menu_order' ) ); + add_filter( 'posts_results', array( $this, 'sort_menu_item_queries_by_menu_taxonomy' ), 10, 2 ); + + add_action( 'wp_insert_post', array( $this, 'add_post_meta' ) ); + + $this->menu_item_loop_markup = $this->default_menu_item_loop_markup; + + // Only output our Menu Item Loop Markup on a real blog view. Not feeds, XML-RPC, admin, etc. + add_filter( 'template_include', array( $this, 'setup_menu_item_loop_markup__in_filter' ) ); + + add_filter( 'enter_title_here', array( $this, 'change_default_title' ) ); + add_filter( 'post_updated_messages', array( $this, 'updated_messages' ) ); + add_filter( 'dashboard_glance_items', array( $this, 'add_to_dashboard' ) ); + } + + /** + * Should this Custom Post Type be made available? + * + * @return bool + */ + public function site_supports_nova() { + // If we're on WordPress.com, and it has the menu site vertical. + if ( function_exists( 'site_vertical' ) && 'nova_menu' === site_vertical() ) { // @phan-suppress-current-line PhanUndeclaredFunction -- only calling if it exists. + return true; + } + + // Else, if the current theme requests it. + if ( current_theme_supports( self::MENU_ITEM_POST_TYPE ) ) { + return true; + } + + // Otherwise, say no unless something wants to filter us to say yes. + /** + * Allow something else to hook in and enable this CPT. + * + * @module custom-content-types + * + * @since 2.6.0 + * + * @param bool false Whether or not to enable this CPT. + * @param string $var The slug for this CPT. + */ + return (bool) apply_filters( 'jetpack_enable_cpt', false, self::MENU_ITEM_POST_TYPE ); + } + + /* Setup */ + + /** + * Register Taxonomies and Post Type + */ + public function register_taxonomies() { + if ( ! taxonomy_exists( self::MENU_ITEM_LABEL_TAX ) ) { + register_taxonomy( + self::MENU_ITEM_LABEL_TAX, + self::MENU_ITEM_POST_TYPE, + array( + 'labels' => array( + /* translators: this is about a food menu */ + 'name' => __( 'Menu Item Labels', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'singular_name' => __( 'Menu Item Label', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'search_items' => __( 'Search Menu Item Labels', 'jetpack-classic-theme-helper' ), + 'popular_items' => __( 'Popular Labels', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'all_items' => __( 'All Menu Item Labels', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'edit_item' => __( 'Edit Menu Item Label', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'view_item' => __( 'View Menu Item Label', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'update_item' => __( 'Update Menu Item Label', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'add_new_item' => __( 'Add New Menu Item Label', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'new_item_name' => __( 'New Menu Item Label Name', 'jetpack-classic-theme-helper' ), + 'separate_items_with_commas' => __( 'For example, spicy, favorite, etc.
Separate Labels with commas', 'jetpack-classic-theme-helper' ), + 'add_or_remove_items' => __( 'Add or remove Labels', 'jetpack-classic-theme-helper' ), + 'choose_from_most_used' => __( 'Choose from the most used Labels', 'jetpack-classic-theme-helper' ), + 'items_list_navigation' => __( 'Menu item label list navigation', 'jetpack-classic-theme-helper' ), + 'items_list' => __( 'Menu item labels list', 'jetpack-classic-theme-helper' ), + ), + 'no_tagcloud' => __( 'No Labels found', 'jetpack-classic-theme-helper' ), + 'hierarchical' => false, + ) + ); + } + + if ( ! taxonomy_exists( self::MENU_TAX ) ) { + register_taxonomy( + self::MENU_TAX, + self::MENU_ITEM_POST_TYPE, + array( + 'labels' => array( + /* translators: this is about a food menu */ + 'name' => __( 'Menu Sections', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'singular_name' => __( 'Menu Section', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'search_items' => __( 'Search Menu Sections', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'all_items' => __( 'All Menu Sections', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'parent_item' => __( 'Parent Menu Section', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'parent_item_colon' => __( 'Parent Menu Section:', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'edit_item' => __( 'Edit Menu Section', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'view_item' => __( 'View Menu Section', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'update_item' => __( 'Update Menu Section', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'add_new_item' => __( 'Add New Menu Section', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'new_item_name' => __( 'New Menu Sections Name', 'jetpack-classic-theme-helper' ), + 'items_list_navigation' => __( 'Menu section list navigation', 'jetpack-classic-theme-helper' ), + 'items_list' => __( 'Menu section list', 'jetpack-classic-theme-helper' ), + ), + 'rewrite' => array( + 'slug' => 'menu', + 'with_front' => false, + 'hierarchical' => true, + ), + 'hierarchical' => true, + 'show_tagcloud' => false, + 'query_var' => 'menu', + ) + ); + } + } + + /** + * Register our Post Type. + */ + public function register_post_types() { + if ( post_type_exists( self::MENU_ITEM_POST_TYPE ) ) { + return; + } + + register_post_type( + self::MENU_ITEM_POST_TYPE, + array( + 'description' => __( "Items on your restaurant's menu", 'jetpack-classic-theme-helper' ), + + 'labels' => array( + /* translators: this is about a food menu */ + 'name' => __( 'Menu Items', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'singular_name' => __( 'Menu Item', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'menu_name' => __( 'Food Menus', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'all_items' => __( 'Menu Items', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'add_new' => __( 'Add One Item', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'add_new_item' => __( 'Add Menu Item', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'edit_item' => __( 'Edit Menu Item', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'new_item' => __( 'New Menu Item', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'view_item' => __( 'View Menu Item', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'search_items' => __( 'Search Menu Items', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'not_found' => __( 'No Menu Items found', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 'not_found_in_trash' => __( 'No Menu Items found in Trash', 'jetpack-classic-theme-helper' ), + 'filter_items_list' => __( 'Filter menu items list', 'jetpack-classic-theme-helper' ), + 'items_list_navigation' => __( 'Menu item list navigation', 'jetpack-classic-theme-helper' ), + 'items_list' => __( 'Menu items list', 'jetpack-classic-theme-helper' ), + ), + 'supports' => array( + 'title', + 'editor', + 'thumbnail', + 'excerpt', + ), + 'rewrite' => array( + 'slug' => 'item', + 'with_front' => false, + 'feeds' => false, + 'pages' => false, + ), + 'register_meta_box_cb' => array( $this, 'register_menu_item_meta_boxes' ), + + 'public' => true, + 'show_ui' => true, // set to false to replace with custom UI + 'menu_position' => 20, // below Pages + 'capability_type' => 'page', + 'map_meta_cap' => true, + 'has_archive' => false, + 'query_var' => 'item', + ) + ); + } + + /** + * Update messages for the Menu Item admin. + * + * @param array $messages Existing post update messages. + * + * @return array $messages Updated post update messages. + */ + public function updated_messages( $messages ) { + global $post; + + $messages[ self::MENU_ITEM_POST_TYPE ] = array( + 0 => '', // Unused. Messages start at index 1. + 1 => sprintf( + /* translators: this is about a food menu. Placeholder is a link to the food menu. */ + __( 'Menu item updated. View item', 'jetpack-classic-theme-helper' ), + esc_url( get_permalink( $post->ID ) ) + ), + 2 => esc_html__( 'Custom field updated.', 'jetpack-classic-theme-helper' ), + 3 => esc_html__( 'Custom field deleted.', 'jetpack-classic-theme-helper' ), + /* translators: this is about a food menu */ + 4 => esc_html__( 'Menu item updated.', 'jetpack-classic-theme-helper' ), + 5 => isset( $_GET['revision'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Copying core message handling. + ? sprintf( + /* translators: %s: date and time of the revision */ + esc_html__( 'Menu item restored to revision from %s', 'jetpack-classic-theme-helper' ), + wp_post_revision_title( (int) $_GET['revision'], false ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Copying core message handling. + ) + : false, + 6 => sprintf( + /* translators: this is about a food menu. Placeholder is a link to the food menu. */ + __( 'Menu item published. View item', 'jetpack-classic-theme-helper' ), + esc_url( get_permalink( $post->ID ) ) + ), + /* translators: this is about a food menu */ + 7 => esc_html__( 'Menu item saved.', 'jetpack-classic-theme-helper' ), + 8 => sprintf( + /* translators: this is about a food menu */ + __( 'Menu item submitted. Preview item', 'jetpack-classic-theme-helper' ), + esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) + ), + 9 => sprintf( + /* translators: this is about a food menu. 1. Publish box date format, see https://php.net/date 2. link to the food menu. */ + __( 'Menu item scheduled for: %1$s. Preview item', 'jetpack-classic-theme-helper' ), + /* translators: Publish box date format, see https://php.net/date */ + date_i18n( __( 'M j, Y @ G:i', 'jetpack-classic-theme-helper' ), strtotime( $post->post_date ) ), + esc_url( get_permalink( $post->ID ) ) + ), + 10 => sprintf( + /* translators: this is about a food menu. Placeholder is a link to the food menu. */ + __( 'Menu item draft updated. Preview item', 'jetpack-classic-theme-helper' ), + esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) + ), + ); + + return $messages; + } + + /** + * Nova styles and scripts. + * + * @param string $hook Page hook. + * + * @return void + */ + public function enqueue_nova_styles( $hook ) { + global $post_type; + $pages = array( 'edit.php', 'post.php', 'post-new.php' ); + + if ( in_array( $hook, $pages, true ) && $post_type === self::MENU_ITEM_POST_TYPE ) { + wp_enqueue_style( 'nova-style', plugins_url( 'css/nova.css', __FILE__ ), array(), $this->version ); + } + + wp_enqueue_style( 'nova-font', plugins_url( 'css/nova-font.css', __FILE__ ), array(), $this->version ); + } + + /** + * Change ‘Enter Title Here’ text for the Menu Item. + * + * @param string $title Default title placeholder text. + * + * @return string + */ + public function change_default_title( $title ) { + if ( self::MENU_ITEM_POST_TYPE === get_post_type() ) { + /* translators: this is about a food menu */ + $title = esc_html__( "Enter the menu item's name here", 'jetpack-classic-theme-helper' ); + } + + return $title; + } + + /** + * Add to Dashboard At A Glance + * + * @return void + */ + public function add_to_dashboard() { + $number_menu_items = wp_count_posts( self::MENU_ITEM_POST_TYPE ); + + $roles = new Roles(); // @phan-suppress-current-line PhanUndeclaredClassMethod -- declared at top of file. + if ( current_user_can( $roles->translate_role_to_cap( 'administrator' ) ) ) { // @phan-suppress-current-line PhanUndeclaredClassMethod + $number_menu_items_published = sprintf( + '%2$s', + esc_url( + get_admin_url( + get_current_blog_id(), + 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE + ) + ), + sprintf( + /* translators: Placehoder is a number of items. */ + _n( + '%1$d Food Menu Item', + '%1$d Food Menu Items', + (int) $number_menu_items->publish, + 'jetpack-classic-theme-helper' + ), + number_format_i18n( $number_menu_items->publish ) + ) + ); + } else { + $number_menu_items_published = sprintf( + '%1$s', + sprintf( + /* translators: Placehoder is a number of items. */ + _n( + '%1$d Food Menu Item', + '%1$d Food Menu Items', + (int) $number_menu_items->publish, + 'jetpack-classic-theme-helper' + ), + number_format_i18n( $number_menu_items->publish ) + ) + ); + } + + echo '
  • ' . $number_menu_items_published . '
  • '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- we escape things above. + } + + /** + * If the WP query for our menu items. + * + * @param WP_Query $query WP Query. + * + * @return bool + */ + public function is_menu_item_query( $query ) { + if ( + ( isset( $query->query_vars['taxonomy'] ) && self::MENU_TAX === $query->query_vars['taxonomy'] ) + || + ( isset( $query->query_vars['post_type'] ) && self::MENU_ITEM_POST_TYPE === $query->query_vars['post_type'] ) + ) { + return true; + } + + return false; + } + + /** + * Custom sort the menu item queries by menu order. + * + * @param WP_Query $query WP Query. + * + * @return void + */ + public function sort_menu_item_queries_by_menu_order( $query ) { + if ( ! $this->is_menu_item_query( $query ) ) { + return; + } + + $query->query_vars['orderby'] = 'menu_order'; + $query->query_vars['order'] = 'ASC'; + + // For now, just turn off paging so we can sort by taxonmy later + // If we want paging in the future, we'll need to add the taxonomy sort here (or at least before the DB query is made) + $query->query_vars['posts_per_page'] = -1; + } + + /** + * Custom sort the menu item queries by menu taxonomies. + * + * @param WP_Post[] $posts Array of post objects. + * @param WP_Query $query The WP_Query instance. + * + * @return WP_Post[] + */ + public function sort_menu_item_queries_by_menu_taxonomy( $posts, $query ) { + if ( ! $posts ) { + return $posts; + } + + if ( ! $this->is_menu_item_query( $query ) ) { + return $posts; + } + + $grouped_by_term = array(); + + foreach ( $posts as $post ) { + $term = $this->get_menu_item_menu_leaf( $post->ID ); + if ( ! $term || is_wp_error( $term ) ) { + $term_id = 0; + } else { + $term_id = $term->term_id; + } + + if ( ! isset( $grouped_by_term[ "$term_id" ] ) ) { + $grouped_by_term[ "$term_id" ] = array(); + } + + $grouped_by_term[ "$term_id" ][] = $post; + } + + $term_order = get_option( 'nova_menu_order', array() ); + + $return = array(); + foreach ( $term_order as $term_id ) { + if ( isset( $grouped_by_term[ "$term_id" ] ) ) { + $return = array_merge( $return, $grouped_by_term[ "$term_id" ] ); + unset( $grouped_by_term[ "$term_id" ] ); + } + } + + foreach ( $grouped_by_term as $term_id => $posts ) { + $return = array_merge( $return, $posts ); + } + + return $return; + } + + /** + * Add new "Add many items" submenu, custom colunmns, and custom bulk actions. + * + * @return void + */ + public function add_admin_menus() { + $hook = add_submenu_page( + 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE, + __( 'Add Many Items', 'jetpack-classic-theme-helper' ), + __( 'Add Many Items', 'jetpack-classic-theme-helper' ), + 'edit_pages', + 'add_many_nova_items', + array( $this, 'add_many_new_items_page' ) + ); + + add_action( "load-$hook", array( $this, 'add_many_new_items_page_load' ) ); + + add_action( 'current_screen', array( $this, 'current_screen_load' ) ); + + /* + * Adjust 'Add Many Items' submenu position + * We're making changes to the menu global, but no other choice unfortunately. + * phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited + */ + if ( isset( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] ) ) { + $submenu_item = array_pop( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] ); + $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ][11] = $submenu_item; + ksort( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] ); + } + // phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited + + $this->setup_menu_item_columns(); + + Assets::register_script( + 'nova-menu-checkboxes', + '../../dist/custom-post-types/js/menu-checkboxes.js', + __FILE__, + array( + 'in_footer' => true, + 'enqueue' => false, + 'textdomain' => 'jetpack-classic-theme-helper', + ) + ); + } + + /** + * Custom Nova Icon CSS + * + * @return void + */ + public function set_custom_font_icon() { + ?> + + id ) { + return; + } + + $this->edit_menu_items_page_load(); + add_filter( 'admin_notices', array( $this, 'admin_notices' ) ); + } + + /* Edit Items List */ + + /** + * Display a notice in wp-admin after items have been changed. + * + * @return void + */ + public function admin_notices() { + if ( isset( $_GET['nova_reordered'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is only displaying a message with no dynamic values. + printf( + '

    %s

    ', + /* translators: this is about a food menu */ + esc_html__( 'Menu Items re-ordered.', 'jetpack-classic-theme-helper' ) + ); + } + } + + /** + * Do not allow sorting by title. + * + * @param array $columns An array of sortable columns. + * + * @return array $columns. + */ + public function no_title_sorting( $columns ) { + if ( isset( $columns['title'] ) ) { + unset( $columns['title'] ); + } + return $columns; + } + + /** + * Set up custom columns for our Nova menu. + * + * @return void + */ + public function setup_menu_item_columns() { + add_filter( sprintf( 'manage_edit-%s_sortable_columns', self::MENU_ITEM_POST_TYPE ), array( $this, 'no_title_sorting' ) ); + add_filter( sprintf( 'manage_%s_posts_columns', self::MENU_ITEM_POST_TYPE ), array( $this, 'menu_item_columns' ) ); + + add_action( sprintf( 'manage_%s_posts_custom_column', self::MENU_ITEM_POST_TYPE ), array( $this, 'menu_item_column_callback' ), 10, 2 ); + } + + /** + * Add custom columns to the Nova menu item list. + * + * @param array $columns An array of columns. + * + * @return array $columns. + */ + public function menu_item_columns( $columns ) { + unset( $columns['date'], $columns['likes'] ); + + $columns['thumbnail'] = __( 'Thumbnail', 'jetpack-classic-theme-helper' ); + $columns['labels'] = __( 'Labels', 'jetpack-classic-theme-helper' ); + $columns['price'] = __( 'Price', 'jetpack-classic-theme-helper' ); + $columns['order'] = __( 'Order', 'jetpack-classic-theme-helper' ); + + return $columns; + } + + /** + * Display custom data in each new custom column we created. + * + * @param string $column The name of the column to display. + * @param int $post_id The current post ID. + * + * @return void + */ + public function menu_item_column_callback( $column, $post_id ) { + $screen = get_current_screen(); + + switch ( $column ) { + case 'thumbnail': + echo get_the_post_thumbnail( $post_id, array( 50, 50 ) ); + break; + case 'labels': + $this->list_admin_labels( $post_id ); + break; + case 'price': + $this->display_price( $post_id ); + break; + case 'order': + $url = admin_url( $screen->parent_file ); + + $up_url = add_query_arg( + array( + 'action' => 'move-item-up', + 'post_id' => (int) $post_id, + ), + wp_nonce_url( $url, 'nova_move_item_up_' . $post_id ) + ); + + $down_url = add_query_arg( + array( + 'action' => 'move-item-down', + 'post_id' => (int) $post_id, + ), + wp_nonce_url( $url, 'nova_move_item_down_' . $post_id ) + ); + $menu_item = get_post( $post_id ); + $menu = $this->get_menu_by_post_id( $post_id ); + $term_id = is_object( $menu ) ? $menu->term_id : ''; + ?> + + + + +     — up +
    +     — down +
    + $menu_order ) { + $id = absint( $id ); + unset( $order_pairs[ $id ] ); + if ( $id < 0 ) { + continue; + } + + $post = get_post( $id ); + if ( ! $post ) { + continue; + } + + // save a write if the order hasn't changed + if ( (int) $menu_order !== $post->menu_order ) { + $args = array( + 'ID' => $id, + 'menu_order' => $menu_order, + ); + wp_update_post( $args ); + } + + // save a write if the term hasn't changed + if ( + is_object( $this->get_menu_by_post_id( $id ) ) && + (int) $term_pairs[ $id ] !== $this->get_menu_by_post_id( $id )->term_id + ) { + wp_set_object_terms( $id, $term_pairs[ $id ], self::MENU_TAX ); + } + } + + $redirect = add_query_arg( + array( + 'post_type' => self::MENU_ITEM_POST_TYPE, + 'nova_reordered' => '1', + ), + admin_url( 'edit.php' ) + ); + wp_safe_redirect( $redirect ); + exit; + } + + /** + * Handle changes to menu items. + * (process actions, update data, enqueue necessary scripts). + * + * @return void + */ + public function edit_menu_items_page_load() { + if ( isset( $_GET['action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we process the form and check nonces in handle_menu_item_actions. + $this->handle_menu_item_actions(); + } + + $this->maybe_reorder_menu_items(); + + Assets::register_script( + 'nova-drag-drop', + '../../dist/custom-post-types/js/nova-drag-drop.js', + __FILE__, + array( + 'dependencies' => array( + 'jquery', + 'jquery-ui-sortable', + ), + 'in_footer' => true, + 'enqueue' => true, + 'textdomain' => 'jetpack-classic-theme-helper', + ) + ); + + wp_localize_script( + 'nova-drag-drop', + '_novaDragDrop', + array( + 'nonce' => wp_create_nonce( 'drag-drop-reorder' ), + 'nonceName' => 'drag-drop-reorder', + 'reorder' => __( 'Save New Order', 'jetpack-classic-theme-helper' ), + 'reorderName' => 'menu_reorder_submit', + ) + ); + add_action( 'the_post', array( $this, 'show_menu_titles_in_menu_item_list' ) ); + } + + /** + * Process actions to move menu items around. + * + * @return void + */ + public function handle_menu_item_actions() { + if ( isset( $_GET['action'] ) ) { + $action = (string) wp_unslash( $_GET['action'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- we check for nonces below, and check against specific strings in switch statement. + } else { + return; + } + + switch ( $action ) { + case 'move-item-up': + case 'move-item-down': + $reorder = false; + + if ( empty( $_GET['post_id'] ) ) { + break; + } + + $post_id = (int) $_GET['post_id']; + + $term = $this->get_menu_item_menu_leaf( $post_id ); + + // Get all posts in that term. + $query = new WP_Query( + array( + 'taxonomy' => self::MENU_TAX, + 'term' => $term->slug, + ) + ); + + $order = array(); + foreach ( $query->posts as $post ) { + $order[] = $post->ID; + } + + if ( 'move-item-up' === $action ) { + check_admin_referer( 'nova_move_item_up_' . $post_id ); + + $first_post_id = $order[0]; + if ( $post_id === $first_post_id ) { + break; + } + + foreach ( $order as $menu_order => $order_post_id ) { + if ( $post_id !== $order_post_id ) { + continue; + } + + $swap_post_id = $order[ $menu_order - 1 ]; + $order[ $menu_order - 1 ] = $post_id; + $order[ $menu_order ] = $swap_post_id; + + $reorder = true; + break; + } + } else { + check_admin_referer( 'nova_move_item_down_' . $post_id ); + + $last_post_id = end( $order ); + if ( $post_id === $last_post_id ) { + break; + } + + foreach ( $order as $menu_order => $order_post_id ) { + if ( $post_id !== $order_post_id ) { + continue; + } + + $swap_post_id = $order[ $menu_order + 1 ]; + $order[ $menu_order + 1 ] = $post_id; + $order[ $menu_order ] = $swap_post_id; + + $reorder = true; + } + } + + if ( $reorder ) { + foreach ( $order as $menu_order => $id ) { + wp_update_post( compact( 'id', 'menu_order' ) ); + } + } + + break; + case 'move-menu-up': + case 'move-menu-down': + $reorder = false; + + if ( empty( $_GET['term_id'] ) ) { + break; + } + + $term_id = (int) $_GET['term_id']; + + $terms = $this->get_menus(); + + $order = array(); + foreach ( $terms as $term ) { + $order[] = $term->term_id; + } + + if ( 'move-menu-up' === $action ) { + check_admin_referer( 'nova_move_menu_up_' . $term_id ); + + $first_term_id = $order[0]; + if ( $term_id === $first_term_id ) { + break; + } + + foreach ( $order as $menu_order => $order_term_id ) { + if ( $term_id !== $order_term_id ) { + continue; + } + + $swap_term_id = $order[ $menu_order - 1 ]; + $order[ $menu_order - 1 ] = $term_id; + $order[ $menu_order ] = $swap_term_id; + + $reorder = true; + break; + } + } else { + check_admin_referer( 'nova_move_menu_down_' . $term_id ); + + $last_term_id = end( $order ); + if ( $term_id === $last_term_id ) { + break; + } + + foreach ( $order as $menu_order => $order_term_id ) { + if ( $term_id !== $order_term_id ) { + continue; + } + + $swap_term_id = $order[ $menu_order + 1 ]; + $order[ $menu_order + 1 ] = $term_id; + $order[ $menu_order ] = $swap_term_id; + + $reorder = true; + } + } + + if ( $reorder ) { + update_option( 'nova_menu_order', $order ); + } + + break; + default: + return; + } + + $redirect = add_query_arg( + array( + 'post_type' => self::MENU_ITEM_POST_TYPE, + 'nova_reordered' => '1', + ), + admin_url( 'edit.php' ) + ); + wp_safe_redirect( $redirect ); + exit; + } + + /** + * Add menu title rows to the list table + * + * @param \WP_Post $post The Post object. + * + * @return void + */ + public function show_menu_titles_in_menu_item_list( $post ) { + global $wp_list_table; + + static $last_term_id = false; + + $term = $this->get_menu_item_menu_leaf( $post->ID ); + + $term_id = $term instanceof \WP_Term ? $term->term_id : null; + + if ( false !== $last_term_id && $last_term_id === $term_id ) { + return; + } + + if ( $term_id === null ) { + $last_term_id = null; + $term_name = ''; + $parent_count = 0; + } else { + $last_term_id = $term->term_id; + $term_name = $term->name; + $parent_count = 0; + $current_term = $term; + while ( $current_term->parent ) { + ++$parent_count; + $current_term = get_term( $current_term->parent, self::MENU_TAX ); + } + } + + $non_order_column_count = $wp_list_table->get_column_count() - 1; + + $screen = get_current_screen(); + + $url = admin_url( $screen->parent_file ); + + $up_url = add_query_arg( + array( + 'action' => 'move-menu-up', + 'term_id' => (int) $term_id, + ), + wp_nonce_url( $url, 'nova_move_menu_up_' . $term_id ) + ); + + $down_url = add_query_arg( + array( + 'action' => 'move-menu-down', + 'term_id' => (int) $term_id, + ), + wp_nonce_url( $url, 'nova_move_menu_down_' . $term_id ) + ); + + ?> + + +

    + ', '', $term ); + + } else { + esc_html_e( 'Uncategorized', 'jetpack-classic-theme-helper' ); + } + ?> +

    + + + + +
    + + + + + process_form_request(); + exit; + } + + $this->enqueue_many_items_scripts(); + } + + /** + * Enqueue script to create many items at once. + * + * @return void + */ + public function enqueue_many_items_scripts() { + + Assets::register_script( + 'nova-many-items', + '../../dist/custom-post-types/js/many-items.js', + __FILE__, + array( + 'in_footer' => true, + 'enqueue' => true, + 'textdomain' => 'jetpack-classic-theme-helper', + ) + ); + } + + /** + * Process form request to create many items at once. + * + * @return void + */ + public function process_form_request() { + if ( ! isset( $_POST['nova_title'] ) || ! is_array( $_POST['nova_title'] ) ) { + return; + } + + $is_ajax = ! empty( $_POST['ajax'] ); + + if ( $is_ajax ) { + check_ajax_referer( 'nova_many_items' ); + } else { + check_admin_referer( 'nova_many_items' ); + } + + /* + * $_POST is already slashed + * phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash + */ + foreach ( array_keys( $_POST['nova_title'] ) as $key ) : // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- we sanitize below. + $post_details = array( + 'post_status' => 'publish', + 'post_type' => self::MENU_ITEM_POST_TYPE, + 'post_content' => ! empty( $_POST['nova_content'] ) && ! empty( $_POST['nova_content'][ $key ] ) + ? sanitize_text_field( $_POST['nova_content'][ $key ] ) + : '', + 'post_title' => isset( $_POST['nova_title'][ $key ] ) + ? sanitize_title( $_POST['nova_title'][ $key ] ) + : '', + 'tax_input' => array( + self::MENU_ITEM_LABEL_TAX => isset( $_POST['nova_labels'][ $key ] ) + ? sanitize_meta( self::MENU_ITEM_LABEL_TAX, $_POST['nova_labels'][ $key ], 'term' ) + : null, + self::MENU_TAX => isset( $_POST['nova_menu_tax'] ) + ? sanitize_meta( self::MENU_TAX, $_POST['nova_menu_tax'], 'term' ) + : null, + ), + ); + + $post_id = wp_insert_post( $post_details ); + if ( ! $post_id || is_wp_error( $post_id ) ) { + continue; + } + + $this->set_price( + $post_id, + isset( $_POST['nova_price'][ $key ] ) + ? sanitize_meta( 'nova_price', $_POST['nova_price'][ $key ], 'post' ) + : '' + ); + // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash + + if ( $is_ajax ) : + $post = get_post( $post_id ); + $GLOBALS['post'] = $post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + setup_postdata( $post ); + + ?> + + display_price(); ?> + list_labels( $post_id ); ?> + + +
    +

    + +

    + TAB key on your keyboard to move between colums and the ENTER or RETURN key to save each row and move on to the next.', 'jetpack-classic-theme-helper' ), + array( + 'kbd' => array(), + ) + ); + ?> +

    + +
    +

    +

    + 'nova-menu-tax', + 'name' => 'nova_menu_tax', + 'taxonomy' => self::MENU_TAX, + 'hide_empty' => false, + 'hierarchical' => true, + ) + ); + ?> +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + spicy, favorite, etc. Separate Labels with commas', 'jetpack-classic-theme-helper' ), + array( + 'small' => array(), + 'em' => array(), + ) + ); + ?> +
    +
    +
    + +

    + + +

    +
    +
    + %2$s', + (int) $post->ID, + esc_html__( 'Price', 'jetpack-classic-theme-helper' ), + esc_attr( $this->get_price( (int) $post->ID ) ) + ); + } + + /** + * Save the price of a menu item. + * + * @param int $post_id Post ID. + */ + public function add_post_meta( $post_id ) { + if ( ! isset( $_POST['nova_price'][ $post_id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce handling happens via core, since we hook into wp_insert_post. + return; + } + + $this->set_price( + $post_id, + sanitize_meta( 'nova_price', wp_unslash( $_POST['nova_price'][ $post_id ] ), 'post' ) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce handling happens via core, since we hook into wp_insert_post. + ); + } + + /* Data */ + + /** + * Get ordered array of menu items. + * + * @param array $args Optional argumments. + * + * @return array + */ + public function get_menus( $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'hide_empty' => false, + ) + ); + $args['taxonomy'] = self::MENU_TAX; + + $terms = get_terms( $args ); // @phan-suppress-current-line PhanAccessMethodInternal + if ( ! $terms || is_wp_error( $terms ) ) { + return array(); + } + + $terms_by_id = array(); + foreach ( $terms as $term ) { + $terms_by_id[ "{$term->term_id}" ] = $term; + } + + $term_order = get_option( 'nova_menu_order', array() ); + + $return = array(); + foreach ( $term_order as $term_id ) { + if ( isset( $terms_by_id[ "$term_id" ] ) ) { + $return[] = $terms_by_id[ "$term_id" ]; + unset( $terms_by_id[ "$term_id" ] ); + } + } + + foreach ( $terms_by_id as $term_id => $term ) { + $return[] = $term; + } + + return $return; + } + + /** + * Get first menu taxonomy "leaf". + * + * @param int $post_id Post ID. + * + * @return bool|\WP_Term|\WP_Error|null + */ + public function get_menu_item_menu_leaf( $post_id ) { + // Get first menu taxonomy "leaf". + $term_ids = wp_get_object_terms( $post_id, self::MENU_TAX, array( 'fields' => 'ids' ) ); + + foreach ( $term_ids as $term_id ) { // possibly ignore PhanTypeSuspiciousNonTraversableForeach + $children = get_term_children( $term_id, self::MENU_TAX ); + if ( ! $children ) { + break; + } + } + + if ( ! isset( $term_id ) ) { + return false; + } + + return get_term( $term_id, self::MENU_TAX ); + } + + /** + * Get a list of the labels linked to a menu item. + * + * @param int $post_id Post ID. + * + * @return void + */ + public function list_labels( $post_id = 0 ) { + $post = get_post( $post_id ); + echo get_the_term_list( $post->ID, self::MENU_ITEM_LABEL_TAX, '', _x( ', ', 'Nova label separator', 'jetpack-classic-theme-helper' ), '' ); + } + + /** + * Get a list of the labels linked to a menu item, with links to manage them. + * + * @param int $post_id Post ID. + * + * @return void + */ + public function list_admin_labels( $post_id = 0 ) { + $post = get_post( $post_id ); + $labels = get_the_terms( $post->ID, self::MENU_ITEM_LABEL_TAX ); + if ( ! empty( $labels ) ) { + $out = array(); + foreach ( $labels as $label ) { // possibly ignore PhanTypeSuspiciousNonTraversableForeach + $out[] = sprintf( + '%s', + esc_url( + add_query_arg( + array( + 'post_type' => self::MENU_ITEM_POST_TYPE, + 'taxonomy' => self::MENU_ITEM_LABEL_TAX, + 'term' => $label->slug, + ), + 'edit.php' + ) + ), + esc_html( + sanitize_term_field( 'name', $label->name, $label->term_id, self::MENU_ITEM_LABEL_TAX, 'display' ) + ) + ); + } + + echo implode( _x( ', ', 'Nova label separator', 'jetpack-classic-theme-helper' ), $out ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- we build $out ourselves and escape things there. + } else { + esc_html_e( 'No Labels', 'jetpack-classic-theme-helper' ); + } + } + + /** + * Update post meta with the price defined in meta box. + * + * @param int $post_id Post ID. + * @param string $price Price. + * + * @return int|bool + */ + public function set_price( $post_id = 0, $price = '' ) { + return update_post_meta( $post_id, 'nova_price', $price ); + } + + /** + * Get the price of a menu item. + * + * @param int $post_id Post ID. + * + * @return bool|string + */ + public function get_price( $post_id = 0 ) { + return get_post_meta( $post_id, 'nova_price', true ); + } + + /** + * Echo the price of a menu item. + * + * @param int $post_id Post ID. + * + * @return void + */ + public function display_price( $post_id = 0 ) { + echo esc_html( $this->get_price( $post_id ) ); + } + + /* Menu Item Loop Markup */ + + /** + * Get markup for a menu item. + * Note: Does not support nested loops. + * + * @param null|string $field The field to get the value for. + * + * @return array + */ + public function get_menu_item_loop_markup( $field = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + return $this->menu_item_loop_markup; + } + + /** + * Sets up the loop markup. + * Attached to the 'template_include' *filter*, + * which fires only during a real blog view (not in admin, feeds, etc.) + * + * @param string $template Template File. + * + * @return string Template File. VERY Important. + */ + public function setup_menu_item_loop_markup__in_filter( $template ) { + add_action( 'loop_start', array( $this, 'start_menu_item_loop' ) ); + + return $template; + } + + /** + * If the Query is a Menu Item Query, start outputing the Menu Item Loop Marku + * Attached to the 'loop_start' action. + * + * @param WP_Query $query Post query. + * + * @return void + */ + public function start_menu_item_loop( $query ) { + if ( ! $this->is_menu_item_query( $query ) ) { + return; + } + + $this->menu_item_loop_last_term_id = false; + $this->menu_item_loop_current_term = false; + + add_action( 'the_post', array( $this, 'menu_item_loop_each_post' ) ); + add_action( 'loop_end', array( $this, 'stop_menu_item_loop' ) ); + } + + /** + * Outputs the Menu Item Loop Markup + * Attached to the 'the_post' action. + * + * @param WP_Post $post Post object. + * + * @return void + */ + public function menu_item_loop_each_post( $post ) { + $this->menu_item_loop_current_term = $this->get_menu_item_menu_leaf( $post->ID ); + + if ( + false === $this->menu_item_loop_current_term + || null === $this->menu_item_loop_current_term + || is_wp_error( $this->menu_item_loop_current_term ) + ) { + return; + } + + if ( false === $this->menu_item_loop_last_term_id ) { + // We're at the very beginning of the loop + + $this->menu_item_loop_open_element( 'menu' ); // Start a new menu section + $this->menu_item_loop_header(); // Output the menu's header + } elseif ( + is_object( $this->menu_item_loop_current_term ) && + $this->menu_item_loop_last_term_id !== $this->menu_item_loop_current_term->term_id + ) { + // We're not at the very beginning but still need to start a new menu section. End the previous menu section first. + + $this->menu_item_loop_close_element( 'menu' ); // End the previous menu section + $this->menu_item_loop_open_element( 'menu' ); // Start a new menu section + $this->menu_item_loop_header(); // Output the menu's header + } + if ( is_object( $this->menu_item_loop_current_term ) ) { + + $this->menu_item_loop_last_term_id = $this->menu_item_loop_current_term->term_id; + } + } + + /** + * If the Query is a Menu Item Query, stop outputing the Menu Item Loop Marku + * Attached to the 'loop_end' action. + * + * @param WP_Query $query Post query. + * + * @return void + */ + public function stop_menu_item_loop( $query ) { + if ( ! $this->is_menu_item_query( $query ) ) { + return; + } + + remove_action( 'the_post', array( $this, 'menu_item_loop_each_post' ) ); + remove_action( 'loop_start', array( $this, 'start_menu_item_loop' ) ); + remove_action( 'loop_end', array( $this, 'stop_menu_item_loop' ) ); + + $this->menu_item_loop_close_element( 'menu' ); // End the last menu section + } + + /** + * Outputs the Menu Group Header + * + * @return void + */ + public function menu_item_loop_header() { + $this->menu_item_loop_open_element( 'menu_header' ); + $this->menu_item_loop_open_element( 'menu_title' ); + echo esc_html( $this->menu_item_loop_current_term->name ); // @todo tax filter + $this->menu_item_loop_close_element( 'menu_title' ); + if ( $this->menu_item_loop_current_term->description ) : + $this->menu_item_loop_open_element( 'menu_description' ); + echo esc_html( $this->menu_item_loop_current_term->description ); // @todo kses, tax filter + $this->menu_item_loop_close_element( 'menu_description' ); + endif; + $this->menu_item_loop_close_element( 'menu_header' ); + } + + /** + * Outputs a Menu Item Markup element opening tag + * + * @param string $field - Menu Item Markup settings field. + * + * @return void + */ + public function menu_item_loop_open_element( $field ) { + $markup = $this->get_menu_item_loop_markup(); + /** + * Filter a menu item's element opening tag. + * + * @module custom-content-types + * + * @since 4.4.0 + * + * @param string $tag Menu item's element opening tag. + * @param string $field Menu Item Markup settings field. + * @param array $markup Array of markup elements for the menu item. + * @param false|object $term Taxonomy term for current menu item. + */ + echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- it's escaped in menu_item_loop_class. + 'jetpack_nova_menu_item_loop_open_element', + '<' . tag_escape( $markup[ "{$field}_tag" ] ) . $this->menu_item_loop_class( $markup[ "{$field}_class" ] ) . ">\n", + $field, + $markup, + $this->menu_item_loop_current_term + ); + } + + /** + * Outputs a Menu Item Markup element closing tag + * + * @param string $field - Menu Item Markup settings field. + * + * @return void + */ + public function menu_item_loop_close_element( $field ) { + $markup = $this->get_menu_item_loop_markup(); + /** + * Filter a menu item's element closing tag. + * + * @module custom-content-types + * + * @since 4.4.0 + * + * @param string $tag Menu item's element closing tag. + * @param string $field Menu Item Markup settings field. + * @param array $markup Array of markup elements for the menu item. + * @param false|object $term Taxonomy term for current menu item. + */ + echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- tag_escape is used. + 'jetpack_nova_menu_item_loop_close_element', + '\n", + $field, + $markup, + $this->menu_item_loop_current_term + ); + } + + /** + * Returns a Menu Item Markup element's class attribute. + * + * @param string $class Class name. + * + * @return string HTML class attribute with leading whitespace. + */ + public function menu_item_loop_class( $class ) { + if ( ! $class ) { + return ''; + } + + /** + * Filter a menu Item Markup element's class attribute. + * + * @module custom-content-types + * + * @since 4.4.0 + * + * @param string $tag Menu Item Markup element's class attribute. + * @param string $class Menu Item Class name. + * @param false|object $term Taxonomy term for current menu item. + */ + return apply_filters( + 'jetpack_nova_menu_item_loop_class', + ' class="' . esc_attr( $class ) . '"', + $class, + $this->menu_item_loop_current_term + ); + } + } + +} diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/css/edit-items.css b/projects/packages/classic-theme-helper/src/custom-post-types/css/edit-items.css new file mode 100644 index 0000000000000..85fbbe96ef231 --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/css/edit-items.css @@ -0,0 +1,24 @@ +.widefat .menu-label-row td { + border-bottom-width: 1px; +} +.widefat .menu-label-row td h3 { + padding-left: 30px; +} +.widefat .menu-order-value { + width: 2.5em; + text-align: center; +} +.widefat .menu-label-row, .widefat .menu-label-row td { + background-color: #d6d6d6; + color: #111; + border: 0 none; +} +.ui-sortable .type-nova_menu_item { + cursor: move; +} +.tablenav .button-reorder { + margin-top: 4px; +} +.tablenav .view-switch a, .tablenav div.tablenav-pages { + display: none; +} \ No newline at end of file diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/css/many-items.css b/projects/packages/classic-theme-helper/src/custom-post-types/css/many-items.css new file mode 100644 index 0000000000000..c99324301743d --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/css/many-items.css @@ -0,0 +1,14 @@ +.many-items-table th, .many-items-table td { + width: 25%; +} + +.many-items-table input, .many-items-table textarea { + width: 100%; +} + +.many-items-table input[type=file] { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; +} diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/css/nova-font.css b/projects/packages/classic-theme-helper/src/custom-post-types/css/nova-font.css new file mode 100644 index 0000000000000..ac1b90679e656 --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/css/nova-font.css @@ -0,0 +1,30 @@ +@font-face { + font-family: 'nova-font'; + src: url('../fonts/nova.eot'); +} +@font-face { + font-family: 'nova-font'; + src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg5lAuAAAAC8AAAAYGNtYXDL9xqaAAABHAAAADxnYXNwAAAAEAAAAVgAAAAIZ2x5Zrlfj0YAAAFgAAABrGhlYWQAW+atAAADDAAAADZoaGVhB2ED4AAAA0QAAAAkaG10eAXcAGQAAANoAAAADGxvY2EACgDWAAADdAAAAAhtYXhwAAgAkQAAA3wAAAAgbmFtZXvEneAAAAOcAAABHnBvc3QAAwAAAAAEvAAAACAAAwPoAZAABQAAAooCvAAAAIwCigK8AAAB4AAxAQIAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAACDmAwOp/8L/wgOpAD4AAAAAAAAAAAAAAAAAAAAgAAAAAAACAAAAAwAAABQAAwABAAAAFAAEACgAAAAGAAQAAQACACDmA///AAAAIOYD////4Rn/AAEAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAQAZAAyA7YDhAAoAEUAfQCOAAABMhY6ATMyPgI/ASMHJz8BDwEnNycHDgMHFgYWBhcHMgYWIjMXNwUHDgMHHgMXHgMzMj4CPwEnKgMjFyc3Ni4CJy4DIyIOAgcOAxceAxceAz8BAR4DMzI+Ajc+Ayc2LgIvAQEmPgI3PgEeARceAxcnApQCBAQEAg4XFxQKtzGXH2oaM3UhlwHUCg0KBAEBAQIBAhQBAQEBAXIN/t3oCwwLAwEBAwsMCwgVFhkMDhgXEwuxcgICAwEC94wFBQMUIRkRLS0xFQsVFxULCQ8GAgQDDxMaDxUuNDIaDgEwBhAQFQkMExIQCAcMBgUBAQUGDAfm/i8CAwIHAhApLy0VCRMMCwLrAfUBBAoNCtWWIHYyGWsglDS5CRQWGQ0CBAQEAw8BAWkOKsIJFBYZDQ0YFxQJCg0KBAQKDQrWngp+DxczNTUXEx4VDAMHDAoKGh4iExMnJSMPFB4SBQQD/l8HCgYEBAcLCAcQEhQKCxMSEAjPAV8BBgcHBA4KCRgTCxgWFQlaAAAAAAEAAAABAAAD2anvXw889QALA+gAAAAAzsPRIgAAAADOw9EiAAAAAAO2A4QAAAAIAAIAAAAAAAAAAQAAA6n/wgAAA+gAAAAyA7YAAQAAAAAAAAAAAAAAAAAAAAMAAAAAAfQAAAPoAGQAAAAAAAoA1gABAAAAAwCPAAQAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEACAAAAAEAAAAAAAIADgAyAAEAAAAAAAMACAAeAAEAAAAAAAQACABAAAEAAAAAAAUAFgAIAAEAAAAAAAYABAAmAAEAAAAAAAoAKABIAAMAAQQJAAEACAAAAAMAAQQJAAIADgAyAAMAAQQJAAMACAAeAAMAAQQJAAQACABAAAMAAQQJAAUAFgAIAAMAAQQJAAYACAAqAAMAAQQJAAoAKABIAG4AbwB2AGEAVgBlAHIAcwBpAG8AbgAgADAALgAwAG4AbwB2AGFub3ZhAG4AbwB2AGEAUgBlAGcAdQBsAGEAcgBuAG8AdgBhAEcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=) format('truetype'), + url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAUoAAsAAAAABNwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgDmUC4GNtYXAAAAFoAAAAPAAAADzL9xqaZ2FzcAAAAaQAAAAIAAAACAAAABBnbHlmAAABrAAAAawAAAGsuV+PRmhlYWQAAANYAAAANgAAADYAW+ataGhlYQAAA5AAAAAkAAAAJAdhA+BobXR4AAADtAAAAAwAAAAMBdwAZGxvY2EAAAPAAAAACAAAAAgACgDWbWF4cAAAA8gAAAAgAAAAIAAIAJFuYW1lAAAD6AAAAR4AAAEee8Sd4HBvc3QAAAUIAAAAIAAAACAAAwAAAAMD6AGQAAUAAAKKArwAAACMAooCvAAAAeAAMQECAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg5gMDqf/C/8IDqQA+AAAAAAAAAAAAAAAAAAAAIAAAAAAAAgAAAAMAAAAUAAMAAQAAABQABAAoAAAABgAEAAEAAgAg5gP//wAAACDmA////+EZ/wABAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAAEAGQAMgO2A4QAKABFAH0AjgAAATIWOgEzMj4CPwEjByc/AQ8BJzcnBw4DBxYGFgYXBzIGFiIzFzcFBw4DBx4DFx4DMzI+Aj8BJyoDIxcnNzYuAicuAyMiDgIHDgMXHgMXHgM/AQEeAzMyPgI3PgMnNi4CLwEBJj4CNz4BHgEXHgMXJwKUAgQEBAIOFxcUCrcxlx9qGjN1IZcB1AoNCgQBAQECAQIUAQEBAQFyDf7d6AsMCwMBAQMLDAsIFRYZDA4YFxMLsXICAgMBAveMBQUDFCEZES0tMRULFRcVCwkPBgIEAw8TGg8VLjQyGg4BMAYQEBUJDBMSEAgHDAYFAQEFBgwH5v4vAgMCBwIQKS8tFQkTDAsC6wH1AQQKDQrVliB2MhlrIJQ0uQkUFhkNAgQEBAMPAQFpDirCCRQWGQ0NGBcUCQoNCgQECg0K1p4Kfg8XMzU1FxMeFQwDBwwKChoeIhMTJyUjDxQeEgUEA/5fBwoGBAQHCwgHEBIUCgsTEhAIzwFfAQYHBwQOCgkYEwsYFhUJWgAAAAABAAAAAQAAA9mp718PPPUACwPoAAAAAM7D0SIAAAAAzsPRIgAAAAADtgOEAAAACAACAAAAAAAAAAEAAAOp/8IAAAPoAAAAMgO2AAEAAAAAAAAAAAAAAAAAAAADAAAAAAH0AAAD6ABkAAAAAAAKANYAAQAAAAMAjwAEAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAAgAAAABAAAAAAACAA4AMgABAAAAAAADAAgAHgABAAAAAAAEAAgAQAABAAAAAAAFABYACAABAAAAAAAGAAQAJgABAAAAAAAKACgASAADAAEECQABAAgAAAADAAEECQACAA4AMgADAAEECQADAAgAHgADAAEECQAEAAgAQAADAAEECQAFABYACAADAAEECQAGAAgAKgADAAEECQAKACgASABuAG8AdgBhAFYAZQByAHMAaQBvAG4AIAAwAC4AMABuAG8AdgBhbm92YQBuAG8AdgBhAFIAZQBnAHUAbABhAHIAbgBvAHYAYQBHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format('woff'); + font-weight: normal; + font-style: normal; +} + +#menu-posts-nova_menu_item:before, +#dashboard_right_now .nova-menu-count a:before, +#dashboard_right_now .nova-menu-count span:before { + font-family: 'nova-font'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +#dashboard_right_now .nova-menu-count a:before, #dashboard_right_now .nova-menu-count span:before { + content: '\e603'; +} \ No newline at end of file diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/css/nova.css b/projects/packages/classic-theme-helper/src/custom-post-types/css/nova.css new file mode 100644 index 0000000000000..616433a3c47a0 --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/css/nova.css @@ -0,0 +1,110 @@ +/* edit-items.css +-------------------------------------------------------------- */ + +.widefat .menu-label-row td { + border-bottom-width: 1px; +} +.widefat .menu-label-row td h3 { + padding-left: 30px; +} +.widefat .menu-label-row td h3 .edit-nova-section { + font-size: .8em; + font-weight: normal; + margin-left: 5px; +} +.widefat .menu-order-value { + width: 2.5em; + text-align: center; +} +.widefat .menu-label-row, .widefat .menu-label-row td { + background-color: #f0f0f1; + color: #111; + border: 0 none; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.05); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.05); +} +.ui-sortable .type-nova_menu_item { + cursor: move; + background-color: #fff; +} +.ui-sortable .type-nova_menu_item:nth-child(even) { + background-color: #f6f7f7; +} +.ui-sortable .type-nova_menu_item.ui-sortable-helper { + -webkit-box-shadow: 0 0 1px rgba(0, 0, 0, 0.1); + box-shadow: 0 0 1px rgba(0, 0, 0, 0.1); +} +.tablenav .button-reorder { + margin-top: 4px; +} +.tablenav .view-switch a, .tablenav div.tablenav-pages { + display: none; +} + + +/* many-items.css +-------------------------------------------------------------- */ + +.many-items-table th, .many-items-table td { + width: 30%; +} + +.many-items-table th.nova-price, .many-items-table td.nova-price { + width: 10%; +} + +.many-items-table input, .many-items-table textarea { + width: 100%; +} + +.many-items-table input[type=file] { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; +} + + +/* new +-------------------------------------------------------------- */ + +#the-list tr td:nth-of-type(2) { + padding-top: 15px; +} + +.nova-move-menu-up:before, +.nova-move-menu-down:before { + margin-right: 5px; + font: normal 10px/1 'dashicons' !important; + speak: none; +} + +.nova-move-menu-up:before { + content: "\f342"; +} + +.nova-move-menu-down:before { + content: "\f346"; +} + +.dashicon:before { + font: normal 20px/1 'dashicons'; + speak: none; + top: 5px; + display: inline-block; + position: relative; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-decoration: none !important; + vertical-align: top; +} + +.dashicon-plus:before { + content: "\f132"; +} + +.dashicon-edit:before { + margin: 2px 5px 0 10px; + content: "\f327"; + font-size: 10px; +} diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/js/many-items.js b/projects/packages/classic-theme-helper/src/custom-post-types/js/many-items.js new file mode 100644 index 0000000000000..3cbc17466d79c --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/js/many-items.js @@ -0,0 +1,139 @@ +( function () { + let menuSelector, nonceInput; + const initializedTables = new Set(); + + const methods = { + init: function ( table ) { + let tbody = table.lastElementChild; + while ( tbody && tbody.tagName !== 'TBODY' ) { + tbody = tbody.previousElementSibling; + } + const row = tbody.querySelector( 'tr:first-child' ).cloneNode( true ); + + table.dataset.form = table.closest( 'form' ); + table.dataset.tbody = tbody; + table.dataset.row = row; + table.dataset.currentRow = row; + + menuSelector = document.getElementById( 'nova-menu-tax' ); + nonceInput = document.getElementById( '_wpnonce' ); + + table.addEventListener( 'keypress', function ( event ) { + if ( event.which !== 13 ) return; + + event.preventDefault(); + if ( typeof FormData === 'function' ) { + methods.submitRow.call( table ); + } + methods.addRow.call( table ); + } ); + + table.addEventListener( 'focusin', function ( event ) { + if ( event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA' ) { + table.dataset.currentRow = event.target.closest( 'tr' ); + } + } ); + + initializedTables.add( table ); + return table; + }, + + destroy: function ( table ) { + table.removeEventListener( 'keypress', methods.keypressHandler ); + table.removeEventListener( 'focusin', methods.focusinHandler ); + initializedTables.delete( table ); + return table; + }, + + submitRow: function ( table ) { + const submittedRow = table.dataset.currentRow; + const currentInputs = submittedRow.querySelectorAll( 'input, textarea, select' ); + const form = document.querySelector( table.dataset.form ); + const allInputs = Array.from( form.querySelectorAll( 'input, textarea, select' ) ); + + currentInputs.forEach( input => ( input.disabled = true ) ); + allInputs + .filter( input => ! currentInputs.includes( input ) ) + .forEach( input => ( input.disabled = true ) ); + + const partialFormData = new FormData( form ); + partialFormData.append( 'ajax', '1' ); + partialFormData.append( 'nova_menu_tax', menuSelector.value ); + partialFormData.append( '_wpnonce', nonceInput.value ); + + fetch( '', { + method: 'POST', + body: partialFormData, + } ) + .then( response => response.text() ) + .then( responseText => { + submittedRow.innerHTML = responseText; + } ); + + allInputs.forEach( input => ( input.disabled = false ) ); + + return table; + }, + + addRow: function ( table ) { + const row = table.dataset.row.cloneNode( true ); + + const tbody = table.dataset.tbody; + tbody.appendChild( row ); + + const firstInput = row.querySelector( 'input, textarea, select' ); + if ( firstInput ) firstInput.focus(); + + return table; + }, + + clickAddRow: function ( table ) { + let tbody = table.lastElementChild; + + while ( tbody && tbody.tagName !== 'TBODY' ) { + tbody = tbody.previousElementSibling; + } + const row = tbody.querySelector( 'tr:first-child' ).cloneNode( true ); + + row.querySelectorAll( 'input, textarea' ).forEach( input => { + input.value = ''; + } ); + + tbody.appendChild( row ); + }, + }; + + const observeTableRemoval = function () { + const observer = new MutationObserver( mutations => { + mutations.forEach( mutation => { + mutation.removedNodes.forEach( node => { + if ( node.matches && node.matches( '.many-items-table' ) ) { + methods.destroy( node ); + } + } ); + } ); + } ); + + observer.observe( document.body, { childList: true, subtree: true } ); + }; + + // Initialization for many-items-table + document.addEventListener( 'focusin', event => { + const table = event.target.closest( '.many-items-table' ); + if ( table && ! initializedTables.has( table ) ) { + methods.init( table ); + } + } ); + + document.addEventListener( 'click', event => { + if ( event.target.matches( 'a.nova-new-row' ) ) { + const table = event.target.closest( '.many-items-table' ); + if ( table ) { + event.preventDefault(); + methods.clickAddRow( table ); + } + } + } ); + + observeTableRemoval(); +} )(); diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/js/menu-checkboxes.js b/projects/packages/classic-theme-helper/src/custom-post-types/js/menu-checkboxes.js new file mode 100644 index 0000000000000..ed3418d76ccdf --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/js/menu-checkboxes.js @@ -0,0 +1,75 @@ +( function () { + const NovaCheckBoxes = { + inputs: null, + popInputs: null, + + initialize: function () { + // Get all checkboxes in the "nova_menuchecklist-pop" + NovaCheckBoxes.popInputs = document.querySelectorAll( + '#nova_menuchecklist-pop input[type="checkbox"]' + ); + + // Get all checkboxes in the "nova_menuchecklist" and add event listeners + NovaCheckBoxes.inputs = document.querySelectorAll( + '#nova_menuchecklist input[type="checkbox"]' + ); + NovaCheckBoxes.inputs.forEach( input => { + input.addEventListener( 'change', NovaCheckBoxes.checkOne ); + input.addEventListener( 'change', NovaCheckBoxes.syncPop ); + } ); + + // If no checkboxes are checked, check the first one + if ( ! NovaCheckBoxes.isChecked() ) { + NovaCheckBoxes.checkFirst(); + } + + // Sync the state of the "pop" inputs + NovaCheckBoxes.syncPop(); + }, + + syncPop: function () { + NovaCheckBoxes.popInputs.forEach( popInput => { + const linkedInput = document.querySelector( `#in-nova_menu-${ popInput.value }` ); + popInput.checked = linkedInput ? linkedInput.checked : false; + } ); + }, + + isChecked: function () { + return Array.from( NovaCheckBoxes.inputs ).some( input => input.checked ); + }, + + checkFirst: function () { + const firstInput = NovaCheckBoxes.inputs[ 0 ]; + if ( firstInput ) { + firstInput.checked = true; + } + }, + + checkOne: function () { + const currentInput = this; + + // If the current checkbox is checked, uncheck all other checkboxes + if ( currentInput.checked ) { + NovaCheckBoxes.inputs.forEach( input => { + if ( input !== currentInput ) { + input.checked = false; + } + } ); + return; + } + const checklist = document.querySelector( '#nova_menuchecklist' ); + + // If at least one checkbox is still checked, uncheck the current one + if ( checklist.querySelectorAll( 'input[type="checkbox"]:checked' ).length > 0 ) { + currentInput.checked = false; + return; + } + + // Otherwise, check the first checkbox + NovaCheckBoxes.checkFirst(); + }, + }; + + // Initialize when the DOM is fully loaded + document.addEventListener( 'DOMContentLoaded', NovaCheckBoxes.initialize ); +} )(); diff --git a/projects/packages/classic-theme-helper/src/custom-post-types/js/nova-drag-drop.js b/projects/packages/classic-theme-helper/src/custom-post-types/js/nova-drag-drop.js new file mode 100644 index 0000000000000..8629bbe092803 --- /dev/null +++ b/projects/packages/classic-theme-helper/src/custom-post-types/js/nova-drag-drop.js @@ -0,0 +1,85 @@ +/* eslint-disable no-undef */ +/* global _novaDragDrop */ + +( function ( $ ) { + let list; + + /** + * Initialize the drag and drop functionality. + */ + function init() { + list = $( '#the-list' ); + dragMenus(); + addNonce(); + addSubmitButton(); + changeToPost(); + } + + /** + * Allow the menu items to be dragged. + */ + function dragMenus() { + list.sortable( { + cancel: '.no-items, .inline-edit-row', + stop: function ( event, ui ) { + if ( ui.item.is( ':first-child' ) ) { + return list.sortable( 'cancel' ); + } + // + reOrder(); + }, + } ); + } + + /** + * Allow the menu items to be reordered. + */ + function reOrder() { + list.find( '.menu-label-row' ).each( function () { + const term_id = $( this ).data( 'term_id' ); + $( this ) + .nextUntil( '.menu-label-row' ) + .each( function ( i ) { + const row = $( this ); + row.find( '.menu-order-value' ).val( i ); + row.find( '.nova-menu-term' ).val( term_id ); + } ); + } ); + } + + /** + * Ensure the submit button is added to the page. + */ + function addSubmitButton() { + $( '.tablenav' ).prepend( + '' + ); + } + + /** + * Add the nonce to the form. + */ + function addNonce() { + $( '#posts-filter' ).append( + '' + ); + } + + /** + * Change the form method to POST. + */ + function changeToPost() { + $( '#posts-filter' ).attr( 'method', 'post' ); + } + + // do it + $( document ).ready( init ); +} )( jQuery ); diff --git a/projects/packages/jetpack-mu-wpcom/.phan/baseline.php b/projects/packages/jetpack-mu-wpcom/.phan/baseline.php index f8dea226fcd09..6865fcfd0af51 100644 --- a/projects/packages/jetpack-mu-wpcom/.phan/baseline.php +++ b/projects/packages/jetpack-mu-wpcom/.phan/baseline.php @@ -9,46 +9,31 @@ */ return [ // # Issue statistics: - // PhanTypeMismatchArgument : 35+ occurrences - // PhanPluginDuplicateConditionalNullCoalescing : 20+ occurrences - // PhanUndeclaredClassMethod : 20+ occurrences + // PhanTypeMismatchArgument : 15+ occurrences // PhanTypeMismatchArgumentProbablyReal : 15+ occurrences - // PhanTypeMismatchReturnProbablyReal : 10+ occurrences - // PhanUndeclaredFunction : 10+ occurrences - // PhanTypeMismatchReturn : 8 occurrences - // PhanUndeclaredConstant : 7 occurrences - // PhanTypeArraySuspiciousNullable : 6 occurrences + // PhanPluginDuplicateConditionalNullCoalescing : 8 occurrences + // PhanTypeMismatchReturn : 6 occurrences + // PhanUndeclaredClassMethod : 6 occurrences // PhanNoopNew : 5 occurrences - // PhanParamTooMany : 4 occurrences - // PhanUnextractableAnnotationSuffix : 4 occurrences - // PhanDeprecatedProperty : 3 occurrences - // PhanPluginDuplicateExpressionAssignmentOperation : 3 occurrences + // PhanTypeMismatchArgumentInternal : 4 occurrences + // PhanTypeMismatchReturnProbablyReal : 4 occurrences // PhanTypePossiblyInvalidDimOffset : 3 occurrences - // PhanTypeSuspiciousNonTraversableForeach : 3 occurrences - // PhanUndeclaredClassReference : 3 occurrences - // PhanUndeclaredGlobalVariable : 3 occurrences // PhanEmptyFQSENInCallable : 2 occurrences - // PhanPluginMixedKeyNoKey : 2 occurrences + // PhanParamTooMany : 2 occurrences // PhanTypeArraySuspicious : 2 occurrences - // PhanTypeMismatchArgumentInternal : 2 occurrences + // PhanTypeArraySuspiciousNullable : 2 occurrences // PhanTypeMismatchDefault : 2 occurrences // PhanTypeMissingReturn : 2 occurrences + // PhanUndeclaredFunction : 2 occurrences // PhanDeprecatedFunction : 1 occurrence // PhanImpossibleTypeComparison : 1 occurrence // PhanNonClassMethodCall : 1 occurrence // PhanNoopArrayAccess : 1 occurrence - // PhanPluginRedundantAssignment : 1 occurrence // PhanPluginSimplifyExpressionBool : 1 occurrence - // PhanPossiblyUndeclaredVariable : 1 occurrence // PhanRedefineFunction : 1 occurrence // PhanRedundantCondition : 1 occurrence // PhanTypeComparisonToArray : 1 occurrence - // PhanTypeInvalidLeftOperandOfBitwiseOp : 1 occurrence - // PhanTypeInvalidRightOperandOfBitwiseOp : 1 occurrence - // PhanTypeMismatchArgumentNullable : 1 occurrence - // PhanTypeMismatchArgumentNullableInternal : 1 occurrence // PhanTypeMismatchDimFetch : 1 occurrence - // PhanTypeMismatchProperty : 1 occurrence // PhanTypeMismatchReturnNullable : 1 occurrence // PhanTypeNonVarPassByRef : 1 occurrence // PhanTypeVoidArgument : 1 occurrence @@ -68,14 +53,6 @@ 'src/features/launchpad/launchpad.php' => ['PhanTypeArraySuspiciousNullable', 'PhanTypeMismatchArgument', 'PhanTypeMismatchReturn', 'PhanTypeMismatchReturnProbablyReal'], 'src/features/marketplace-products-updater/class-marketplace-products-updater.php' => ['PhanTypeMismatchDimFetch', 'PhanTypeMismatchReturn'], 'src/features/media/heif-support.php' => ['PhanPluginSimplifyExpressionBool'], - 'src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php' => ['PhanParamTooMany', 'PhanPluginDuplicateConditionalNullCoalescing', 'PhanPossiblyUndeclaredVariable', 'PhanTypeMismatchArgument', 'PhanTypeMismatchReturnProbablyReal'], - 'src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/class-wp-rest-newspack-articles-controller.php' => ['PhanTypeArraySuspiciousNullable'], - 'src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/templates/article.php' => ['PhanTypeMismatchArgument', 'PhanUndeclaredGlobalVariable'], - 'src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/templates/articles-list.php' => ['PhanUndeclaredGlobalVariable'], - 'src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/templates/articles-loop.php' => ['PhanUndeclaredGlobalVariable'], - 'src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/view.php' => ['PhanPluginDuplicateExpressionAssignmentOperation', 'PhanTypeArraySuspiciousNullable', 'PhanTypeMismatchArgumentNullableInternal', 'PhanTypeMismatchReturnProbablyReal', 'PhanUndeclaredClassMethod'], - 'src/features/newspack-blocks/synced-newspack-blocks/class-newspack-blocks-api.php' => ['PhanParamTooMany', 'PhanTypeMismatchArgument', 'PhanTypeMismatchReturn', 'PhanTypeMismatchReturnProbablyReal', 'PhanUndeclaredClassMethod', 'PhanUndeclaredFunction', 'PhanUnextractableAnnotationSuffix'], - 'src/features/newspack-blocks/synced-newspack-blocks/class-newspack-blocks.php' => ['PhanDeprecatedProperty', 'PhanPluginDuplicateConditionalNullCoalescing', 'PhanPluginDuplicateExpressionAssignmentOperation', 'PhanPluginMixedKeyNoKey', 'PhanPluginRedundantAssignment', 'PhanTypeInvalidLeftOperandOfBitwiseOp', 'PhanTypeInvalidRightOperandOfBitwiseOp', 'PhanTypeMismatchArgument', 'PhanTypeMismatchArgumentNullable', 'PhanTypeMismatchProperty', 'PhanTypeMismatchReturn', 'PhanTypeMismatchReturnProbablyReal', 'PhanTypeSuspiciousNonTraversableForeach', 'PhanUndeclaredClassMethod', 'PhanUndeclaredClassReference', 'PhanUndeclaredConstant', 'PhanUndeclaredFunction'], 'src/features/verbum-comments/class-verbum-comments.php' => ['PhanImpossibleTypeComparison', 'PhanNoopNew', 'PhanParamTooMany', 'PhanTypeMismatchArgumentProbablyReal', 'PhanUndeclaredFunction'], 'src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-launchpad.php' => ['PhanPluginDuplicateConditionalNullCoalescing'], 'src/features/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-site-migration-migrate-guru-key.php' => ['PhanUndeclaredClassMethod'], diff --git a/projects/packages/jetpack-mu-wpcom/.phan/config.php b/projects/packages/jetpack-mu-wpcom/.phan/config.php index 8680d589c96a7..b69eda7cb1aef 100644 --- a/projects/packages/jetpack-mu-wpcom/.phan/config.php +++ b/projects/packages/jetpack-mu-wpcom/.phan/config.php @@ -32,6 +32,8 @@ // This file breaks analysis, Phan gets lost recursing in trying to figure out some types. // @todo Add type declarations so Phan won't have to do it itself. Or update to a modern less lib. 'src/features/custom-css/custom-css/preprocessors/lessc.inc.php', + // This is synced from the Newspack Blocks repo. + 'src/features/newspack-blocks/synced-newspack-blocks/', ), ) ); diff --git a/projects/packages/jetpack-mu-wpcom/.phpcsignore b/projects/packages/jetpack-mu-wpcom/.phpcsignore new file mode 100644 index 0000000000000..6a0ddf86aeb10 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/.phpcsignore @@ -0,0 +1 @@ +src/features/newspack-blocks/synced-newspack-blocks diff --git a/projects/packages/jetpack-mu-wpcom/bin/sync-newspack-blocks.sh b/projects/packages/jetpack-mu-wpcom/bin/sync-newspack-blocks.sh index 55fda6a5bac10..89a1cf6b9c348 100755 --- a/projects/packages/jetpack-mu-wpcom/bin/sync-newspack-blocks.sh +++ b/projects/packages/jetpack-mu-wpcom/bin/sync-newspack-blocks.sh @@ -14,7 +14,7 @@ fi # pick up value considering that the argument # has the --key=value shape. -key_value=$(echo ${1} | cut -d'=' -f 2) +key_value=$(echo "${1}" | cut -d'=' -f 2) # Set mode depending on first argument if [[ $1 =~ ^--release= ]] then @@ -44,7 +44,7 @@ then echo --branch=master echo "--nodemodules (to use defined in package.json)" echo "--path=/path/to/newspack-blocks" - echo --release=v2.0.0 + echo --release=v4.0.0 echo echo You can find the latest release ID on https://github.com/Automattic/newspack-blocks/releases/latest echo @@ -52,7 +52,6 @@ then fi TARGET=./src/features/newspack-blocks/synced-newspack-blocks -ENTRY=./src/features/newspack-blocks/index.php if [[ ( "$MODE" != "path" ) && ( "$MODE" != "npm" ) ]]; then @@ -62,20 +61,23 @@ then if [[ "$CURRENT_VERSION" == "$NAME" ]]; then echo "The current version $CURRENT_VERSION of the newspack-blocks is synced." - exit 0 + read -rp "Do you want to proceed anyway? (y/N): " proceed + if [[ ! "$proceed" =~ ^[Yy]$ ]]; then + exit 0 + fi fi fi # make a temp directory - TEMP_DIR=`mktemp -d` + TEMP_DIR=$(mktemp -d) CODE=$TEMP_DIR/code # download zip file - echo Downloading $MODE $NAME into $TEMP_DIR - (cd $TEMP_DIR && curl -L --fail -s -O $URL) + echo "Downloading $MODE $NAME into $TEMP_DIR" + (cd "$TEMP_DIR" && curl -L --fail -s -O "$URL") # handle download error - ZIPS=( $TEMP_DIR/*.zip ) + ZIPS=( "$TEMP_DIR"/*.zip ) ZIP=${ZIPS[0]} if [ ! -f "$ZIP" ]; then echo "Tried to download $URL" @@ -90,13 +92,13 @@ then fi # extract zip - echo Extracting into $CODE - mkdir -p $CODE - unzip -q $ZIP -d $CODE + echo "Extracting into $CODE" + mkdir -p "$CODE" + unzip -q "$ZIP" -d "$CODE" # find the main file and use its directory as the root of our source dir - MAIN_FILE=`find $CODE -name "newspack-blocks.php"` - CODE=`dirname $MAIN_FILE` + MAIN_FILE=$(find "$CODE" -name "newspack-blocks.php") + CODE=$(dirname "$MAIN_FILE") # handle unzip error if [ ! -f "$CODE/newspack-blocks.php" ]; then @@ -127,18 +129,22 @@ mkdir -p $TARGET/components mkdir -p $TARGET/shared mkdir -p $TARGET/types -# copy files and directories -NEW_VERSION=v`jq -r .version $CODE/package.json` +# Update Newspack Blocks version number in the package. +NEW_VERSION=v$(jq -r .version "$CODE"/package.json) echo "$NEW_VERSION" > $TARGET/version.txt -cp $CODE/includes/class-newspack-blocks-api.php $TARGET/ -cp $CODE/includes/class-newspack-blocks.php $TARGET/ -cp -R $CODE/src/blocks/homepage-articles $TARGET/blocks/ -cp -R $CODE/src/blocks/carousel $TARGET/blocks/ -cp -R $CODE/src/shared $TARGET/ -cp -R $CODE/src/components $TARGET/ +sed -E -i.bak "s|^define\( 'NEWSPACK_BLOCKS__VERSION', '.*' \);$|define( 'NEWSPACK_BLOCKS__VERSION', '$NEW_VERSION' );|" "$TARGET"/../index.php +rm "$TARGET"/../index.php.bak + +# copy files and directories +cp "$CODE"/includes/class-newspack-blocks-api.php $TARGET/ +cp "$CODE"/includes/class-newspack-blocks.php $TARGET/ +cp -R "$CODE"/src/blocks/homepage-articles $TARGET/blocks/ +cp -R "$CODE"/src/blocks/carousel $TARGET/blocks/ +cp -R "$CODE"/src/shared $TARGET/ +cp -R "$CODE"/src/components $TARGET/ # Get Typescript working by copying the main type defs over. -cp $CODE/src/types/index.d.ts $TARGET/types/ +cp "$CODE"/src/types/index.d.ts $TARGET/types/ # Function types need to be capitalized in our system. We only match " function" # beginning with a space to avoid matching it as a substring. (Not perfect, but # imperfections will be caught by CI with failing tsc, etc.) @@ -146,12 +152,61 @@ sed "${sedi[@]}" -e "s| function| Function|g" "$TARGET/types/index.d.ts" # Note: I would have used eslint-nibble, but it doesn't support autofixing via the CLI. echo "Changing JS textdomain to match jetpack-mu-wpcom..." -pnpm --package=eslint@8.57.0 dlx eslint --no-ignore --rule '"@wordpress/i18n-text-domain":["error",{"allowedTextDomain":"jetpack-mu-wpcom"}]' --fix $TARGET > /dev/null +BASE=$(cd "$(dirname "${BASH_SOURCE[0]}")"/../../../.. && pwd) +FULLTARGET="$PWD/$TARGET" + +# Add a temporary single-rule eslint.config.mjs file. +cat > "$TARGET/eslint.config.mjs" < ( { ...block, rules: {} } ) ), + // Enable just this one rule. + { + rules: { + "@wordpress/i18n-text-domain": [ "error", { allowedTextDomain: "jetpack-mu-wpcom" } ], + } + }, +]; +EOF +( cd "$BASE" && pnpm run lint-file --no-inline-config --no-ignore --fix "$FULLTARGET" ) +rm "$TARGET/eslint.config.mjs" echo "Changing JS translation function call to avoid bad minification..." pnpm --package=jscodeshift dlx jscodeshift -t ./bin/sync-newspack-blocks-formatter.js --extensions=js $TARGET +# Add temporary PHPCS config file. +PHPCSSTANDARDFILE="$TARGET/phpcs.tmp.xml" +cat > "$PHPCSSTANDARDFILE" < + + + + + + + + + + + + + + + + +EOF echo "Changing PHP textdomain to match jetpack-mu-wpcom..." -../../../vendor/bin/phpcbf --standard=./.phpcs.dir.xml --filter=../../../vendor/automattic/jetpack-phpcs-filter/src/PhpcsFilter.php --runtime-set jetpack-filter-no-ignore -q $TARGET - +"$BASE"/vendor/bin/phpcbf --standard="$PHPCSSTANDARDFILE" "$TARGET" +rm "$PHPCSSTANDARDFILE" + +# Add textdomain to block.json +echo "Adding textdomain to all block.json files..." +for block_json_file in "$TARGET"/blocks/*/block.json; do + TMPFILE=$(mktemp) + jq --tab '. += {"textdomain": "jetpack-mu-wpcom"}' "$block_json_file" > "$TMPFILE" + mv "$TMPFILE" "$block_json_file" +done echo Sync done. diff --git a/projects/packages/jetpack-mu-wpcom/changelog/add-set-default-category-quick-action b/projects/packages/jetpack-mu-wpcom/changelog/add-set-default-category-quick-action new file mode 100644 index 0000000000000..562cea91dca9a --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/add-set-default-category-quick-action @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Post categories: Add quick action to change default category diff --git a/projects/packages/jetpack-mu-wpcom/changelog/fix-php-fatal-in-wpcom b/projects/packages/jetpack-mu-wpcom/changelog/fix-php-fatal-in-wpcom new file mode 100644 index 0000000000000..e460fda027a8f --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/fix-php-fatal-in-wpcom @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Add a function_exists guard for wpcom_is_duplicate_views_experiment_enabled function diff --git a/projects/packages/jetpack-mu-wpcom/changelog/fix-rdv-regressions-on-treatment b/projects/packages/jetpack-mu-wpcom/changelog/fix-rdv-regressions-on-treatment new file mode 100644 index 0000000000000..5ce69e6f92aaf --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/fix-rdv-regressions-on-treatment @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Fixed several regressions for Stats, Blaze and notices for RDV experiment diff --git a/projects/packages/jetpack-mu-wpcom/changelog/renovate-playwright-monorepo b/projects/packages/jetpack-mu-wpcom/changelog/renovate-playwright-monorepo new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/renovate-playwright-monorepo @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/packages/jetpack-mu-wpcom/changelog/update-synced_newspack_blocks b/projects/packages/jetpack-mu-wpcom/changelog/update-synced_newspack_blocks new file mode 100644 index 0000000000000..15206e680281d --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/update-synced_newspack_blocks @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Newspack Blocks: Updated to version 4.5.2. diff --git a/projects/packages/jetpack-mu-wpcom/package.json b/projects/packages/jetpack-mu-wpcom/package.json index 4123ddacdca47..8dad01437fe0a 100644 --- a/projects/packages/jetpack-mu-wpcom/package.json +++ b/projects/packages/jetpack-mu-wpcom/package.json @@ -33,7 +33,7 @@ "@babel/core": "7.26.0", "@babel/plugin-transform-react-jsx": "7.25.9", "@babel/preset-react": "7.25.9", - "@playwright/test": "1.45.1", + "@playwright/test": "1.48.2", "@types/node": "^20.4.2", "@types/react": "^18.2.28", "@types/react-dom": "18.3.1", 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 26cf4192d604a..5bbc4ad0e9d6c 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 @@ -107,6 +107,7 @@ public static function load_features() { require_once __DIR__ . '/features/import-customizations/import-customizations.php'; require_once __DIR__ . '/features/marketplace-products-updater/class-marketplace-products-updater.php'; require_once __DIR__ . '/features/media/heif-support.php'; + require_once __DIR__ . '/features/post-categories/quick-actions.php'; require_once __DIR__ . '/features/site-editor-dashboard-link/site-editor-dashboard-link.php'; require_once __DIR__ . '/features/wpcom-admin-dashboard/wpcom-admin-dashboard.php'; require_once __DIR__ . '/features/wpcom-block-editor/class-jetpack-wpcom-block-editor.php'; @@ -451,7 +452,7 @@ public static function load_verbum_comments_admin() { * Load Odyssey Stats in Simple sites. */ public static function load_wpcom_simple_odyssey_stats() { - if ( get_option( 'wpcom_admin_interface' ) === 'wp-admin' ) { + if ( get_option( 'wpcom_admin_interface' ) === 'wp-admin' || ( function_exists( 'wpcom_is_duplicate_views_experiment_enabled' ) && wpcom_is_duplicate_views_experiment_enabled() ) ) { require_once __DIR__ . '/features/wpcom-simple-odyssey-stats/wpcom-simple-odyssey-stats.php'; } } diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/README.md b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/README.md index a4cc12220990b..b712c4428c0cd 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/README.md +++ b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/README.md @@ -1,56 +1,56 @@ # Newspack Blocks -Some of the Newspack blocks were added to this repository in order to make it available to other parts of the FSE plugin, such as Starter Page Templates where these blocks will be used. +Some of the Newspack blocks were added to this repository so they would be available to other parts of the FSE plugin where these blocks will be used, such as Starter Page Templates. ## Block Posts Block This block allows you to list your posts in various layouts and filter them by criteria like category, tag or author. -It originally comes from the [Newspack Blocks collection](https://github.com/automattic/newspack-blocks) and the block is still being developed there. +It originally comes from the [Newspack Blocks collection](https://github.com/automattic/newspack-blocks) and the block is still being developed there as the `homepage-articles` block. -## Post Carousel Block +## Carousel Block -This block allows you to create a carousel of post's featured images and filter them by criteria like category, tag or author. +This block allows you to create a carousel of post featured images and filter them by criteria (e.g. category, tag or author). -It originally comes from the [Newspack Blocks collection](https://github.com/automattic/newspack-blocks) and the block is still being developed there. +It originally comes from the [Newspack Blocks collection](https://github.com/automattic/newspack-blocks) and the block is still being developed there as the `carousel` block. ## Structure ``` index.php — main entry file, registers the blocks on backend -blog-posts-block-editor.min.js — assets for the blog-posts-block editor -blog-posts-block-view.min.js — assets for the blog-posts-block rendered on frontend -carousel-block-editor.min.js — assets for the carousel-block editor -carousel-block-view.min.js — assets for the carousel-block rendered on frontend -synced-newspack-blocks/** — source code synced from the Newspack Blocks repository, not tracked in Jetpack git repo +blog-posts/ — assets for the blog-posts block frontend and editor +carousel/ — assets for the carousel block frontend and editor +synced-newspack-blocks/ — source code synced from the Newspack Blocks repository ``` -`blog-posts-block-editor.min.js`, `blog-posts-block-view.min.js`, `carousel-block-editor.min.js`, `carousel-block-view.min.js` and `index.php` are files written in order to bridge the parent plugin with the Newspack Blocks. It changes the block names to an `a8c/` namespace and does things like registering REST fields or styles and scripts. In these files we are free to do all those changes because they are not shared with Newspack and only live here in this repository. +Other than the `synced-newspack-blocks` directory, the above are files written in order to bridge the parent plugin with Newspack Blocks. They change the block names to an `a8c/` namespace and register REST fields, styles, and scripts. In these files we are free to make changes because they are not shared with Newspack and only live here in this repository. + ### Synchronizing the code -You can see that `synced-newspack-blocks` is being synced with the Newspack Blocks repository. Please make all improvements and additions on the Newspack side. Please don't make any direct changes to files in this directory as the next synchronization will overwrite them. Synced files are not being tracked in git and they are always downloaded fresh using the sync script explained further in this document. +The `synced-newspack-blocks` is synced with the Newspack Blocks repository. *Please make all improvements and additions upstream in the Newspack Blocks repo. Do not make any direct changes to files in this directory, as the next synchronization will overwrite them.* -Once your changes land on the Newspack side, coordinate with the team (over issues/PRs) to [make a new release](https://github.com/Automattic/newspack-blocks/releases) and once you have the release ID, you can pull the code into here. +Once your changes land in the Newspack Blocks repo, coordinate with the team (over issues/PRs) to [make a new release](https://github.com/Automattic/newspack-blocks/releases). Once you have the release ID (e.g. `v4.0.0`, you start a sync. -While being in `projects/packages/jetpack-mu-wpcom` directory, you can run: +While in the `projects/packages/jetpack-mu-wpcom` directory, run the following: ``` pnpm run sync:newspack-blocks --release= ``` -This will pull the code from the release and integrate it into this repository. Please: - 1. Review changes - 2. Keep the PHPCS config if still necessary - 3. Ensure [htmlentities uses ENT_COMPAT](https://github.com/Automattic/jetpack/pull/38873/commits/16f57e6f01b6eed98a19cd0299261ce5ac075b8e) - 4. Update the phan baseline with `jetpack phan --update-baseline packages/jetpack-mu-wpcom` - 4. Update `NEWSPACK_BLOCKS__VERSION` in [index.php](./index.php) - 5. Ensure that the blocks `block.json` has `"textdomain": "jetpack-mu-wpcom"` - 6. Commit. +This will pull the code from the release into this repository and perform the following tasks: +* Copies TypeScript types into place. +* Changes JS and PHP textdomain refs to `jetpack-mu-wpcom`. +* Adjusts JS translation function calls to avoid minification issues. +* Checks for potential places where `ENT_COMPAT` should be used. + +Once the script has completed: +1. Ensure the changes shown match the changes in the release. +2. Commit. ### Local development -Sometimes, probably, you will need to sync the NHA code straight in your local environment. It means you will get working on both projects at the same time. For this situation, you'd like to reference the code source through the `path` bin script argument. +Sometimes, probably, you will need to sync the code straight in your local environment. It means you will get working on both projects at the same time. For this situation, you'd like to reference the code source through the `path` bin script argument. ``` pnpm run sync:newspack-blocks --path=/Absolute/path/of/newspack-blocks/ diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/index.php b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/index.php index 83bbe4855c5d9..f5e9e3b03bdbe 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/index.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/index.php @@ -11,7 +11,7 @@ define( 'NEWSPACK_BLOCKS__BLOCKS_DIRECTORY', Jetpack_Mu_Wpcom::BASE_DIR . 'build/' ); define( 'NEWSPACK_BLOCKS__PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); -define( 'NEWSPACK_BLOCKS__VERSION', '4.0.1' ); +define( 'NEWSPACK_BLOCKS__VERSION', 'v4.5.2' ); require_once __DIR__ . '/../../utils.php'; require_once __DIR__ . '/synced-newspack-blocks/class-newspack-blocks.php'; diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/.phpcs.dir.xml b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/.phpcs.dir.xml deleted file mode 100644 index c49f51983d12d..0000000000000 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/.phpcs.dir.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/create-swiper.js b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/create-swiper.js index 151b9a0000201..661d61244b2ae 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/create-swiper.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/create-swiper.js @@ -14,8 +14,8 @@ const autoplayClassName = 'wp-block-newspack-blocks-carousel__autoplay-playing'; /** * A helper for IE11-compatible iteration over NodeList elements. * - * @param {object} nodeList - List of nodes to be iterated over. - * @param {Function} cb - Invoked for each iteratee. + * @param {Object} nodeList List of nodes to be iterated over. + * @param {Function} cb Invoked for each iteratee. */ function forEachNode( nodeList, cb ) { /** @@ -29,7 +29,7 @@ function forEachNode( nodeList, cb ) { /** * Modifies attributes on slide HTML to make it accessible. * - * @param {HTMLElement} slide - Slide DOM element + * @param {HTMLElement} slide Slide DOM element */ function activateSlide( slide ) { if ( slide ) { @@ -41,7 +41,7 @@ function activateSlide( slide ) { /** * Modifies attributes on slide HTML to make it accessible. * - * @param {HTMLElement} slide - Slide DOM element + * @param {HTMLElement} slide Slide DOM element */ function deactivateSlide( slide ) { if ( slide ) { @@ -54,15 +54,15 @@ function deactivateSlide( slide ) { * Creates a Swiper instance with predefined config used by the Articles * Carousel block in both front-end and editor. * - * @param {object} els - Swiper elements - * @param {Element} els.block - Block element - * @param {Element} els.container - Swiper container element - * @param {Element} els.next - Next button element - * @param {Element} els.prev - Previous button element - * @param {Element} els.play - Play button element - * @param {Element} els.pause - Pause button element - * @param {Element} els.pagination - Pagination element - * @param {Object} config - Swiper config + * @param {Object} els Swiper elements + * @param {Element} els.block Block element + * @param {Element} els.container Swiper container element + * @param {Element} els.next Next button element + * @param {Element} els.prev Previous button element + * @param {Element} els.play Play button element + * @param {Element} els.pause Pause button element + * @param {Element} els.pagination Pagination element + * @param {Object} config Swiper config * @return {Object} Swiper instance */ export default function createSwiper( els, config = {} ) { @@ -161,13 +161,13 @@ export default function createSwiper( els, config = {} ) { escapeHTML( `${ currentSlide.innerText }, ${ - alt - ? /* translators: the title of the image. */ sprintf( - __( 'Image: %s, ', 'jetpack-mu-wpcom' ), - alt - ) - : '' - } + alt + ? /* translators: the title of the image. */ sprintf( + __( 'Image: %s, ', 'jetpack-mu-wpcom' ), + alt + ) + : '' +} ${ slideInfo }` ), 'assertive' diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/edit.js b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/edit.js index c4a066ae61f1f..62a738282263b 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/edit.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/edit.js @@ -3,12 +3,17 @@ /** * External dependencies */ +import { isEqual } from 'lodash'; +import classnames from 'classnames'; /** * WordPress dependencies */ +import { __ } from '@wordpress/i18n'; import { InspectorControls } from '@wordpress/block-editor'; // eslint-disable-next-line @wordpress/no-unsafe-wp-apis +import { dateI18n, __experimentalGetSettings } from '@wordpress/date'; +import { Component, createRef, Fragment, RawHTML } from '@wordpress/element'; import { BaseControl, Button, @@ -20,20 +25,16 @@ import { Spinner, ToggleControl, } from '@wordpress/components'; -import { compose } from '@wordpress/compose'; import { withDispatch, withSelect } from '@wordpress/data'; -import { dateI18n, __experimentalGetSettings } from '@wordpress/date'; -import { Component, createRef, Fragment, RawHTML } from '@wordpress/element'; +import { compose } from '@wordpress/compose'; import { decodeEntities } from '@wordpress/html-entities'; -import { __ } from '@wordpress/i18n'; -import classnames from 'classnames'; -import { isEqual } from 'lodash'; /** * Internal dependencies */ -import { PostTypesPanel, PostStatusesPanel } from '../../components/editor-panels'; import QueryControls from '../../components/query-controls'; +import { PostTypesPanel, PostStatusesPanel } from '../../components/editor-panels'; +import createSwiper from './create-swiper'; import { formatAvatars, formatByline, @@ -43,7 +44,6 @@ import { } from '../../shared/js/utils'; // Use same posts store as Homepage Posts block. import { postsBlockSelector, postsBlockDispatch, shouldReflow } from '../homepage-articles/utils'; -import createSwiper from './create-swiper'; // Max number of slides that can be shown at once. const MAX_NUMBER_OF_SLIDES = 6; @@ -242,9 +242,7 @@ class Edit extends Component {
    { hasNoPosts && ( -
    - { __( 'Sorry, no posts were found.', 'jetpack-mu-wpcom' ) } -
    +
    { __( 'Sorry, no posts were found.', 'jetpack-mu-wpcom' ) }
    ) } { ( ! this.state.swiperInitialized || ! latestPosts ) && ( @@ -302,9 +300,7 @@ class Edit extends Component { ) } { showCategory && ( ! post.newspack_post_sponsors || - post.newspack_sponsors_show_categories ) && ( - { decodeEntities( post.newspack_category_info ) } - ) } + post.newspack_sponsors_show_categories ) && ( { decodeEntities( post.newspack_category_info ) } ) }
    ) } { showTitle && ( @@ -337,15 +333,14 @@ class Edit extends Component { { dateI18n( dateFormat, post.date ) } ) } - { ( showCaption || showCredit ) && - post.newspack_featured_image_caption && ( -
    - ) } + { ( showCaption || showCredit ) && post.newspack_featured_image_caption && ( +
    + ) }
    ) } @@ -433,10 +428,9 @@ class Edit extends Component { help={ 'cover' === imageFit ? __( - 'The image will fill the entire slide and will be cropped if necessary.', - 'jetpack-mu-wpcom' - ) - : __( + 'The image will fill the entire slide and will be cropped if necessary.', + 'jetpack-mu-wpcom' + ) : __( 'The image will be resized to fit inside the slide without being cropped.', 'jetpack-mu-wpcom', 0 @@ -470,10 +464,7 @@ class Edit extends Component { { setAttributes( { hideControls: _hideControls } ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/index.js b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/index.js index 37b562d71a759..796eacfc27460 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/index.js +++ b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/index.js @@ -36,11 +36,7 @@ export const settings = { foreground: '#36f', }, category: 'newspack', - keywords: [ - __( 'posts', 'jetpack-mu-wpcom' ), - __( 'slideshow', 'jetpack-mu-wpcom' ), - __( 'carousel', 'jetpack-mu-wpcom' ), - ], + keywords: [ __( 'posts', 'jetpack-mu-wpcom' ), __( 'slideshow', 'jetpack-mu-wpcom' ), __( 'carousel', 'jetpack-mu-wpcom' ) ], description: __( 'A carousel of posts.', 'jetpack-mu-wpcom' ), attributes: { className: { diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php index 87000f1e301fd..3d32080ba2eb2 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/carousel/view.php @@ -19,7 +19,7 @@ function newspack_blocks_render_block_carousel( $attributes ) { // This will let the FSE plugin know we need CSS/JS now. do_action( 'newspack_blocks_render_post_carousel' ); - ++$newspack_blocks_carousel_id; + $newspack_blocks_carousel_id++; $autoplay = isset( $attributes['autoplay'] ) ? $attributes['autoplay'] : false; $delay = isset( $attributes['delay'] ) ? absint( $attributes['delay'] ) : 3; $authors = isset( $attributes['authors'] ) ? $attributes['authors'] : array(); @@ -47,10 +47,10 @@ function newspack_blocks_render_block_carousel( $attributes ) { $authors = Newspack_Blocks::prepare_authors(); $newspack_blocks_post_id[ $post_id ] = true; - $article_classes = array( + $article_classes = [ 'post-has-image', 'swiper-slide', - ); + ]; // Add classes based on the post's assigned categories and tags. $article_classes[] = Newspack_Blocks::get_term_classes( $post_id ); @@ -58,7 +58,7 @@ function newspack_blocks_render_block_carousel( $attributes ) { // Get sponsors for this post. $sponsors = Newspack_Blocks::get_all_sponsors( $post_id ); - ++$counter; + $counter++; $has_featured_image = has_post_thumbnail(); $post_type = get_post_type(); $post_link = Newspack_Blocks::get_post_link( $post_id ); @@ -72,7 +72,7 @@ function newspack_blocks_render_block_carousel( $attributes ) { $show_credit = $attributes['showCredit']; // Validate the value of the "image fit" attribute. - $image_fits = array( 'cover', 'contain' ); + $image_fits = [ 'cover', 'contain' ]; $image_fit = in_array( $attributes['imageFit'], $image_fits, true ) ? $attributes['imageFit'] : $image_fits[0]; ?> @@ -129,7 +129,7 @@ function newspack_blocks_render_block_carousel( $attributes ) { ) } { RichText.isEmpty( sectionHeader ) ? ( @@ -206,11 +207,16 @@ class Edit extends Component< HomepageArticlesProps > { { post.meta.newspack_post_subtitle || '' } ) } - { showExcerpt && ( + { showExcerpt && ! showFullContent && ( { post.excerpt.rendered } ) } + { ! showExcerpt && showFullContent && ( + + { post.full_content } + + ) } { showReadMore && post.post_link && ( { readMoreLabel } @@ -280,7 +286,9 @@ class Edit extends Component< HomepageArticlesProps > { mobileStack, minHeight, moreButton, + infiniteScroll, showExcerpt, + showFullContent, showReadMore, readMoreLabel, excerptLength, @@ -434,11 +442,20 @@ class Edit extends Component< HomepageArticlesProps > { ) : ( ! specificMode && ( - setAttributes( { moreButton: ! moreButton } ) } - /> + <> + setAttributes( { moreButton: ! moreButton } ) } + /> + { moreButton && ( + setAttributes( { infiniteScroll: ! infiniteScroll } ) } + /> + ) } + ) ) } { setAttributes( { showExcerpt: ! showExcerpt } ) } + onChange={ () => { + setAttributes({ + showExcerpt: !showExcerpt, + showFullContent: showFullContent ? false : showFullContent + }) + } } /> { showExcerpt && ( - setAttributes( { excerptLength: value } ) } - min={ 10 } - max={ 100 } - /> + + setAttributes( { excerptLength: value } ) } + min={ 10 } + max={ 100 } + /> + ) } - setAttributes( { showReadMore: ! showReadMore } ) } - /> + + { + setAttributes({ + showFullContent: !showFullContent, + showExcerpt: showExcerpt ? false : showExcerpt + }) + } } + /> + + + setAttributes( { showReadMore: ! showReadMore } ) } + /> + { showReadMore && ( { yield put( { type: 'DISABLE_UI' } ); - // Ensure innerBlocks are populated for widget area blocks. - // See https://github.com/WordPress/gutenberg/issues/32607#issuecomment-890728216. - const blocks = getBlocks().map( block => { - const innerBlocks = select( 'core/block-editor' ).getBlocks( block.clientId ); - return { - ...block, - innerBlocks, - }; - } ); + const blocks = recursivelyGetBlocks( getBlocks ); const blockQueries = getBlockQueries( blocks, blockNames ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/templates/article.php b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/templates/article.php index 2a0f2e109f2e0..eb74101785a04 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/templates/article.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/newspack-blocks/synced-newspack-blocks/blocks/homepage-articles/templates/article.php @@ -7,7 +7,7 @@ */ call_user_func( - function ( $data ) { + function( $data ) { $attributes = apply_filters( 'newspack_blocks_homepage_posts_block_attributes', $data['attributes'] ); $authors = Newspack_Blocks::prepare_authors(); $classes = array(); @@ -42,14 +42,14 @@ function ( $data ) { // This global will be used by the newspack_blocks_filter_hpb_srcset filter. global $newspack_blocks_hpb_rendering_context; - $newspack_blocks_hpb_rendering_context = array( 'attrs' => $attributes ); + $newspack_blocks_hpb_rendering_context = [ 'attrs' => $attributes ]; // Disable lazy loading by using an arbitraty `loading` attribute other than `lazy`. // Empty string or `false` would still result in `lazy`. if ( $attributes['disableImageLazyLoad'] ) { $thumbnail_args['loading'] = 'none'; } - if ( $attributes['fetchPriority'] && in_array( $attributes['fetchPriority'], array( 'high', 'low', 'auto' ), true ) ) { + if ( $attributes['fetchPriority'] && in_array( $attributes['fetchPriority'], [ 'high', 'low', 'auto' ], true ) ) { $thumbnail_args['fetchpriority'] = $attributes['fetchPriority']; } @@ -147,9 +147,12 @@ class="" @@ -162,7 +165,7 @@ class="" - - ) } - - - - - @@ -385,7 +387,7 @@ export default function FeaturedImage( { ) } 0 ? handleRegenerate : handleGenerate } + onGenerate={ + pointer?.current > 0 || postFeaturedMediaId ? handleRegenerate : handleGenerate + } generating={ currentPointer?.generating } notEnoughRequests={ notEnoughRequests } requireUpgrade={ requireUpgrade } diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/hooks/use-ai-image.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/hooks/use-ai-image.ts index b8897913d99a4..0edcb0176663b 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/hooks/use-ai-image.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/hooks/use-ai-image.ts @@ -7,8 +7,8 @@ import { ImageStyle, askQuestionSync, } from '@automattic/jetpack-ai-client'; -import { useDispatch } from '@wordpress/data'; -import { useCallback, useRef, useState } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { useCallback, useEffect, useRef, useState } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { cleanForSlug } from '@wordpress/url'; /** @@ -19,7 +19,7 @@ import useSaveToMediaLibrary from '../../../hooks/use-save-to-media-library'; /** * Types */ -import { FEATURED_IMAGE_FEATURE_NAME, GENERAL_IMAGE_FEATURE_NAME } from '../types'; +import { CoreSelectors, FEATURED_IMAGE_FEATURE_NAME, GENERAL_IMAGE_FEATURE_NAME } from '../types'; import type { CarrouselImageData, CarrouselImages } from '../components/carrousel'; import type { RoleType } from '@automattic/jetpack-ai-client'; import type { FeatureControl } from 'extensions/store/wordpress-com/types.js'; @@ -42,11 +42,13 @@ export default function useAiImage( { type, cost, autoStart = true, + previousMediaId, }: { feature: AiImageFeature; type: AiImageType; cost: number; autoStart?: boolean; + previousMediaId?: number; } ) { const { generateImageWithParameters } = useImageGenerator(); const { increaseRequestsCount, featuresControl } = useAiFeature(); @@ -54,7 +56,10 @@ export default function useAiImage( { const { createNotice } = useDispatch( 'core/notices' ); /* Images Control */ + // pointer keeps track of request/generation iteration const pointer = useRef( 0 ); + // and current keeps track of what is the image exposed at the moment + // TODO: should current be any relevant here? It's just modal/carrousel logic after all const [ current, setCurrent ] = useState( 0 ); const [ images, setImages ] = useState< CarrouselImages >( [ { generating: autoStart } ] ); @@ -75,6 +80,25 @@ export default function useAiImage( { } ); }, [] ); + // the selec/useEffect combo... + const loadedMedia = useSelect( + ( select: ( store ) => CoreSelectors ) => select( 'core' )?.getMedia?.( previousMediaId ), + [ previousMediaId ] + ); + useEffect( () => { + if ( loadedMedia ) { + updateImages( + { + image: loadedMedia.source_url, + libraryId: loadedMedia.id, + libraryUrl: loadedMedia.source_url, + generating: false, + }, + pointer.current + ); + } + }, [ loadedMedia, updateImages ] ); + /* * Function to show a snackbar notice on the editor. */ @@ -123,6 +147,9 @@ export default function useAiImage( { style?: string; } ) => { return new Promise< ImageResponse >( ( resolve, reject ) => { + if ( previousMediaId && pointer.current === 0 ) { + pointer.current++; + } updateImages( { generating: true, error: null }, pointer.current ); // Ensure the site has enough requests to generate the image. @@ -167,7 +194,9 @@ export default function useAiImage( { .then( result => { if ( result.data.length > 0 ) { const image = 'data:image/png;base64,' + result.data[ 0 ].b64_json; - updateImages( { image }, pointer.current ); + const prompt = userPrompt || null; + const revisedPrompt = result.data[ 0 ].revised_prompt || null; + updateImages( { image, prompt, revisedPrompt }, pointer.current ); updateRequestsCount(); saveToMediaLibrary( image, name ) .then( savedImage => { @@ -181,7 +210,7 @@ export default function useAiImage( { image, libraryId: savedImage?.id, libraryUrl: savedImage?.url, - revisedPrompt: result.data[ 0 ].revised_prompt || '', + revisedPrompt, } ); } ) .catch( () => { @@ -206,12 +235,13 @@ export default function useAiImage( { saveToMediaLibrary, showSnackbarNotice, getImageNameSuggestion, + previousMediaId, ] ); const handlePreviousImage = useCallback( () => { setCurrent( Math.max( current - 1, 0 ) ); - }, [ current, setCurrent ] ); + }, [ current ] ); const handleNextImage = useCallback( () => { setCurrent( Math.min( current + 1, images.length - 1 ) ); diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/types.ts b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/types.ts index f0c0c5ae3d125..77f688f7dd94a 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/types.ts +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/ai-image/types.ts @@ -4,3 +4,16 @@ export const IMAGE_GENERATION_MODEL_STABLE_DIFFUSION = 'stable-diffusion' as con export const IMAGE_GENERATION_MODEL_DALL_E_3 = 'dall-e-3' as const; export const PLACEMENT_MEDIA_SOURCE_DROPDOWN = 'media-source-dropdown' as const; export const PLACEMENT_BLOCK_PLACEHOLDER_BUTTON = 'block-placeholder-button' as const; + +export interface EditorSelectors { + // actually getEditedPostAttribute can bring different values, but for our current use, number is fine (media ID) + getEditedPostAttribute: ( attribute: string ) => number; + isEditorPanelOpened: ( panel: string ) => boolean; +} + +export interface CoreSelectors { + getMedia: ( mediaId: number ) => { + id: number; + source_url: string; + } | null; +} diff --git a/projects/plugins/jetpack/jetpack.php b/projects/plugins/jetpack/jetpack.php index 06f51f1637176..c7fddd793e7ab 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: 14.2-a.3 + * Version: 14.2-a.5 * Author URI: https://jetpack.com * License: GPL2+ * Text Domain: jetpack @@ -34,7 +34,7 @@ define( 'JETPACK__MINIMUM_WP_VERSION', '6.6' ); define( 'JETPACK__MINIMUM_PHP_VERSION', '7.2' ); -define( 'JETPACK__VERSION', '14.2-a.3' ); +define( 'JETPACK__VERSION', '14.2-a.5' ); /** * Constant used to fetch the connection owner token diff --git a/projects/plugins/jetpack/modules/custom-post-types/nova.php b/projects/plugins/jetpack/modules/custom-post-types/nova.php index 69256670c29aa..b01bc0287b0bb 100644 --- a/projects/plugins/jetpack/modules/custom-post-types/nova.php +++ b/projects/plugins/jetpack/modules/custom-post-types/nova.php @@ -28,1717 +28,1721 @@ use Automattic\Jetpack\Assets; use Automattic\Jetpack\Roles; -/** - * Create the new Nova CPT. - */ -class Nova_Restaurant { - const MENU_ITEM_POST_TYPE = 'nova_menu_item'; - const MENU_ITEM_LABEL_TAX = 'nova_menu_item_label'; - const MENU_TAX = 'nova_menu'; +if ( ! class_exists( '\Nova_Restaurant' ) ) { /** - * Version number used when enqueuing all resources (css and js). - * - * @var string + * Create the new Nova CPT. */ - public $version = '20210303'; + class Nova_Restaurant { + const MENU_ITEM_POST_TYPE = 'nova_menu_item'; + const MENU_ITEM_LABEL_TAX = 'nova_menu_item_label'; + const MENU_TAX = 'nova_menu'; - /** - * Default markup for the menu items. - * - * @var array - */ - protected $default_menu_item_loop_markup = array( - 'menu_tag' => 'section', - 'menu_class' => 'menu-items', - 'menu_header_tag' => 'header', - 'menu_header_class' => 'menu-group-header', - 'menu_title_tag' => 'h1', - 'menu_title_class' => 'menu-group-title', - 'menu_description_tag' => 'div', - 'menu_description_class' => 'menu-group-description', - ); + /** + * Version number used when enqueuing all resources (css and js). + * + * @var string + */ + public $version = '20210303'; - /** - * Array of markup for the menu items. - * - * @var array - */ - protected $menu_item_loop_markup = array(); + /** + * Default markup for the menu items. + * + * @var array + */ + protected $default_menu_item_loop_markup = array( + 'menu_tag' => 'section', + 'menu_class' => 'menu-items', + 'menu_header_tag' => 'header', + 'menu_header_class' => 'menu-group-header', + 'menu_title_tag' => 'h1', + 'menu_title_class' => 'menu-group-title', + 'menu_description_tag' => 'div', + 'menu_description_class' => 'menu-group-description', + ); - /** - * Last term ID of a loop of menu items. - * - * @var bool|int - */ - protected $menu_item_loop_last_term_id = false; + /** + * Array of markup for the menu items. + * + * @var array + */ + protected $menu_item_loop_markup = array(); - /** - * Current term ID of a loop of menu items. - * - * @var bool|int - */ - protected $menu_item_loop_current_term = false; + /** + * Last term ID of a loop of menu items. + * + * @var bool|int + */ + protected $menu_item_loop_last_term_id = false; - /** - * Initialize class. - * - * @param array $menu_item_loop_markup Array of markup for the menu items. - * - * @return self - */ - public static function init( $menu_item_loop_markup = array() ) { - static $instance = false; + /** + * Current term ID of a loop of menu items. + * + * @var bool|int + */ + protected $menu_item_loop_current_term = false; - if ( ! $instance ) { - $instance = new Nova_Restaurant(); - } + /** + * Initialize class. + * + * @param array $menu_item_loop_markup Array of markup for the menu items. + * + * @return self + */ + public static function init( $menu_item_loop_markup = array() ) { + static $instance = false; - if ( $menu_item_loop_markup ) { - $instance->menu_item_loop_markup = wp_parse_args( $menu_item_loop_markup, $instance->default_menu_item_loop_markup ); - } + if ( ! $instance ) { + $instance = new Nova_Restaurant(); + } - return $instance; - } + if ( $menu_item_loop_markup ) { + $instance->menu_item_loop_markup = wp_parse_args( $menu_item_loop_markup, $instance->default_menu_item_loop_markup ); + } - /** - * Constructor. - * Hook into WordPress to create CPT and utilities if needed. - */ - public function __construct() { - if ( ! $this->site_supports_nova() ) { - return; + return $instance; } - $this->register_taxonomies(); - $this->register_post_types(); - add_action( 'admin_menu', array( $this, 'add_admin_menus' ) ); - add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_nova_styles' ) ); - add_action( 'admin_head', array( $this, 'set_custom_font_icon' ) ); - - // Always sort menu items correctly - add_action( 'parse_query', array( $this, 'sort_menu_item_queries_by_menu_order' ) ); - add_filter( 'posts_results', array( $this, 'sort_menu_item_queries_by_menu_taxonomy' ), 10, 2 ); + /** + * Constructor. + * Hook into WordPress to create CPT and utilities if needed. + */ + public function __construct() { + if ( ! $this->site_supports_nova() ) { + return; + } - add_action( 'wp_insert_post', array( $this, 'add_post_meta' ) ); + $this->register_taxonomies(); + $this->register_post_types(); + add_action( 'admin_menu', array( $this, 'add_admin_menus' ) ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_nova_styles' ) ); + add_action( 'admin_head', array( $this, 'set_custom_font_icon' ) ); - $this->menu_item_loop_markup = $this->default_menu_item_loop_markup; + // Always sort menu items correctly + add_action( 'parse_query', array( $this, 'sort_menu_item_queries_by_menu_order' ) ); + add_filter( 'posts_results', array( $this, 'sort_menu_item_queries_by_menu_taxonomy' ), 10, 2 ); - // Only output our Menu Item Loop Markup on a real blog view. Not feeds, XML-RPC, admin, etc. - add_filter( 'template_include', array( $this, 'setup_menu_item_loop_markup__in_filter' ) ); + add_action( 'wp_insert_post', array( $this, 'add_post_meta' ) ); - add_filter( 'enter_title_here', array( $this, 'change_default_title' ) ); - add_filter( 'post_updated_messages', array( $this, 'updated_messages' ) ); - add_filter( 'dashboard_glance_items', array( $this, 'add_to_dashboard' ) ); - } + $this->menu_item_loop_markup = $this->default_menu_item_loop_markup; - /** - * Should this Custom Post Type be made available? - * - * @return bool - */ - public function site_supports_nova() { - // If we're on WordPress.com, and it has the menu site vertical. - if ( function_exists( 'site_vertical' ) && 'nova_menu' === site_vertical() ) { - return true; - } + // Only output our Menu Item Loop Markup on a real blog view. Not feeds, XML-RPC, admin, etc. + add_filter( 'template_include', array( $this, 'setup_menu_item_loop_markup__in_filter' ) ); - // Else, if the current theme requests it. - if ( current_theme_supports( self::MENU_ITEM_POST_TYPE ) ) { - return true; + add_filter( 'enter_title_here', array( $this, 'change_default_title' ) ); + add_filter( 'post_updated_messages', array( $this, 'updated_messages' ) ); + add_filter( 'dashboard_glance_items', array( $this, 'add_to_dashboard' ) ); } - // Otherwise, say no unless something wants to filter us to say yes. /** - * Allow something else to hook in and enable this CPT. + * Should this Custom Post Type be made available? * - * @module custom-content-types - * - * @since 2.6.0 - * - * @param bool false Whether or not to enable this CPT. - * @param string $var The slug for this CPT. + * @return bool */ - return (bool) apply_filters( 'jetpack_enable_cpt', false, self::MENU_ITEM_POST_TYPE ); - } + public function site_supports_nova() { + // If we're on WordPress.com, and it has the menu site vertical. + if ( function_exists( 'site_vertical' ) && 'nova_menu' === site_vertical() ) { + return true; + } - /* Setup */ + // Else, if the current theme requests it. + if ( current_theme_supports( self::MENU_ITEM_POST_TYPE ) ) { + return true; + } - /** - * Register Taxonomies and Post Type - */ - public function register_taxonomies() { - if ( ! taxonomy_exists( self::MENU_ITEM_LABEL_TAX ) ) { - register_taxonomy( - self::MENU_ITEM_LABEL_TAX, - self::MENU_ITEM_POST_TYPE, - array( - 'labels' => array( - /* translators: this is about a food menu */ - 'name' => __( 'Menu Item Labels', 'jetpack' ), - /* translators: this is about a food menu */ - 'singular_name' => __( 'Menu Item Label', 'jetpack' ), - /* translators: this is about a food menu */ - 'search_items' => __( 'Search Menu Item Labels', 'jetpack' ), - 'popular_items' => __( 'Popular Labels', 'jetpack' ), - /* translators: this is about a food menu */ - 'all_items' => __( 'All Menu Item Labels', 'jetpack' ), - /* translators: this is about a food menu */ - 'edit_item' => __( 'Edit Menu Item Label', 'jetpack' ), - /* translators: this is about a food menu */ - 'view_item' => __( 'View Menu Item Label', 'jetpack' ), - /* translators: this is about a food menu */ - 'update_item' => __( 'Update Menu Item Label', 'jetpack' ), - /* translators: this is about a food menu */ - 'add_new_item' => __( 'Add New Menu Item Label', 'jetpack' ), - /* translators: this is about a food menu */ - 'new_item_name' => __( 'New Menu Item Label Name', 'jetpack' ), - 'separate_items_with_commas' => __( 'For example, spicy, favorite, etc.
    Separate Labels with commas', 'jetpack' ), - 'add_or_remove_items' => __( 'Add or remove Labels', 'jetpack' ), - 'choose_from_most_used' => __( 'Choose from the most used Labels', 'jetpack' ), - 'items_list_navigation' => __( 'Menu item label list navigation', 'jetpack' ), - 'items_list' => __( 'Menu item labels list', 'jetpack' ), - ), - 'no_tagcloud' => __( 'No Labels found', 'jetpack' ), - 'hierarchical' => false, - ) - ); + // Otherwise, say no unless something wants to filter us to say yes. + /** + * Allow something else to hook in and enable this CPT. + * + * @module custom-content-types + * + * @since 2.6.0 + * + * @param bool false Whether or not to enable this CPT. + * @param string $var The slug for this CPT. + */ + return (bool) apply_filters( 'jetpack_enable_cpt', false, self::MENU_ITEM_POST_TYPE ); } - if ( ! taxonomy_exists( self::MENU_TAX ) ) { - register_taxonomy( - self::MENU_TAX, + /* Setup */ + + /** + * Register Taxonomies and Post Type + */ + public function register_taxonomies() { + if ( ! taxonomy_exists( self::MENU_ITEM_LABEL_TAX ) ) { + register_taxonomy( + self::MENU_ITEM_LABEL_TAX, + self::MENU_ITEM_POST_TYPE, + array( + 'labels' => array( + /* translators: this is about a food menu */ + 'name' => __( 'Menu Item Labels', 'jetpack' ), + /* translators: this is about a food menu */ + 'singular_name' => __( 'Menu Item Label', 'jetpack' ), + /* translators: this is about a food menu */ + 'search_items' => __( 'Search Menu Item Labels', 'jetpack' ), + 'popular_items' => __( 'Popular Labels', 'jetpack' ), + /* translators: this is about a food menu */ + 'all_items' => __( 'All Menu Item Labels', 'jetpack' ), + /* translators: this is about a food menu */ + 'edit_item' => __( 'Edit Menu Item Label', 'jetpack' ), + /* translators: this is about a food menu */ + 'view_item' => __( 'View Menu Item Label', 'jetpack' ), + /* translators: this is about a food menu */ + 'update_item' => __( 'Update Menu Item Label', 'jetpack' ), + /* translators: this is about a food menu */ + 'add_new_item' => __( 'Add New Menu Item Label', 'jetpack' ), + /* translators: this is about a food menu */ + 'new_item_name' => __( 'New Menu Item Label Name', 'jetpack' ), + 'separate_items_with_commas' => __( 'For example, spicy, favorite, etc.
    Separate Labels with commas', 'jetpack' ), + 'add_or_remove_items' => __( 'Add or remove Labels', 'jetpack' ), + 'choose_from_most_used' => __( 'Choose from the most used Labels', 'jetpack' ), + 'items_list_navigation' => __( 'Menu item label list navigation', 'jetpack' ), + 'items_list' => __( 'Menu item labels list', 'jetpack' ), + ), + 'no_tagcloud' => __( 'No Labels found', 'jetpack' ), + 'hierarchical' => false, + ) + ); + } + + if ( ! taxonomy_exists( self::MENU_TAX ) ) { + register_taxonomy( + self::MENU_TAX, + self::MENU_ITEM_POST_TYPE, + array( + 'labels' => array( + /* translators: this is about a food menu */ + 'name' => __( 'Menu Sections', 'jetpack' ), + /* translators: this is about a food menu */ + 'singular_name' => __( 'Menu Section', 'jetpack' ), + /* translators: this is about a food menu */ + 'search_items' => __( 'Search Menu Sections', 'jetpack' ), + /* translators: this is about a food menu */ + 'all_items' => __( 'All Menu Sections', 'jetpack' ), + /* translators: this is about a food menu */ + 'parent_item' => __( 'Parent Menu Section', 'jetpack' ), + /* translators: this is about a food menu */ + 'parent_item_colon' => __( 'Parent Menu Section:', 'jetpack' ), + /* translators: this is about a food menu */ + 'edit_item' => __( 'Edit Menu Section', 'jetpack' ), + /* translators: this is about a food menu */ + 'view_item' => __( 'View Menu Section', 'jetpack' ), + /* translators: this is about a food menu */ + 'update_item' => __( 'Update Menu Section', 'jetpack' ), + /* translators: this is about a food menu */ + 'add_new_item' => __( 'Add New Menu Section', 'jetpack' ), + /* translators: this is about a food menu */ + 'new_item_name' => __( 'New Menu Sections Name', 'jetpack' ), + 'items_list_navigation' => __( 'Menu section list navigation', 'jetpack' ), + 'items_list' => __( 'Menu section list', 'jetpack' ), + ), + 'rewrite' => array( + 'slug' => 'menu', + 'with_front' => false, + 'hierarchical' => true, + ), + 'hierarchical' => true, + 'show_tagcloud' => false, + 'query_var' => 'menu', + ) + ); + } + } + + /** + * Register our Post Type. + */ + public function register_post_types() { + if ( post_type_exists( self::MENU_ITEM_POST_TYPE ) ) { + return; + } + + register_post_type( self::MENU_ITEM_POST_TYPE, array( - 'labels' => array( + 'description' => __( "Items on your restaurant's menu", 'jetpack' ), + + 'labels' => array( + /* translators: this is about a food menu */ + 'name' => __( 'Menu Items', 'jetpack' ), /* translators: this is about a food menu */ - 'name' => __( 'Menu Sections', 'jetpack' ), + 'singular_name' => __( 'Menu Item', 'jetpack' ), /* translators: this is about a food menu */ - 'singular_name' => __( 'Menu Section', 'jetpack' ), + 'menu_name' => __( 'Food Menus', 'jetpack' ), /* translators: this is about a food menu */ - 'search_items' => __( 'Search Menu Sections', 'jetpack' ), + 'all_items' => __( 'Menu Items', 'jetpack' ), /* translators: this is about a food menu */ - 'all_items' => __( 'All Menu Sections', 'jetpack' ), + 'add_new' => __( 'Add One Item', 'jetpack' ), /* translators: this is about a food menu */ - 'parent_item' => __( 'Parent Menu Section', 'jetpack' ), + 'add_new_item' => __( 'Add Menu Item', 'jetpack' ), /* translators: this is about a food menu */ - 'parent_item_colon' => __( 'Parent Menu Section:', 'jetpack' ), + 'edit_item' => __( 'Edit Menu Item', 'jetpack' ), /* translators: this is about a food menu */ - 'edit_item' => __( 'Edit Menu Section', 'jetpack' ), + 'new_item' => __( 'New Menu Item', 'jetpack' ), /* translators: this is about a food menu */ - 'view_item' => __( 'View Menu Section', 'jetpack' ), + 'view_item' => __( 'View Menu Item', 'jetpack' ), /* translators: this is about a food menu */ - 'update_item' => __( 'Update Menu Section', 'jetpack' ), + 'search_items' => __( 'Search Menu Items', 'jetpack' ), /* translators: this is about a food menu */ - 'add_new_item' => __( 'Add New Menu Section', 'jetpack' ), + 'not_found' => __( 'No Menu Items found', 'jetpack' ), /* translators: this is about a food menu */ - 'new_item_name' => __( 'New Menu Sections Name', 'jetpack' ), - 'items_list_navigation' => __( 'Menu section list navigation', 'jetpack' ), - 'items_list' => __( 'Menu section list', 'jetpack' ), + 'not_found_in_trash' => __( 'No Menu Items found in Trash', 'jetpack' ), + 'filter_items_list' => __( 'Filter menu items list', 'jetpack' ), + 'items_list_navigation' => __( 'Menu item list navigation', 'jetpack' ), + 'items_list' => __( 'Menu items list', 'jetpack' ), + ), + 'supports' => array( + 'title', + 'editor', + 'thumbnail', + 'excerpt', ), - 'rewrite' => array( - 'slug' => 'menu', - 'with_front' => false, - 'hierarchical' => true, + 'rewrite' => array( + 'slug' => 'item', + 'with_front' => false, + 'feeds' => false, + 'pages' => false, ), - 'hierarchical' => true, - 'show_tagcloud' => false, - 'query_var' => 'menu', + 'register_meta_box_cb' => array( $this, 'register_menu_item_meta_boxes' ), + + 'public' => true, + 'show_ui' => true, // set to false to replace with custom UI + 'menu_position' => 20, // below Pages + 'capability_type' => 'page', + 'map_meta_cap' => true, + 'has_archive' => false, + 'query_var' => 'item', ) ); } - } - - /** - * Register our Post Type. - */ - public function register_post_types() { - if ( post_type_exists( self::MENU_ITEM_POST_TYPE ) ) { - return; - } - register_post_type( - self::MENU_ITEM_POST_TYPE, - array( - 'description' => __( "Items on your restaurant's menu", 'jetpack' ), - - 'labels' => array( - /* translators: this is about a food menu */ - 'name' => __( 'Menu Items', 'jetpack' ), - /* translators: this is about a food menu */ - 'singular_name' => __( 'Menu Item', 'jetpack' ), - /* translators: this is about a food menu */ - 'menu_name' => __( 'Food Menus', 'jetpack' ), - /* translators: this is about a food menu */ - 'all_items' => __( 'Menu Items', 'jetpack' ), - /* translators: this is about a food menu */ - 'add_new' => __( 'Add One Item', 'jetpack' ), - /* translators: this is about a food menu */ - 'add_new_item' => __( 'Add Menu Item', 'jetpack' ), - /* translators: this is about a food menu */ - 'edit_item' => __( 'Edit Menu Item', 'jetpack' ), - /* translators: this is about a food menu */ - 'new_item' => __( 'New Menu Item', 'jetpack' ), - /* translators: this is about a food menu */ - 'view_item' => __( 'View Menu Item', 'jetpack' ), - /* translators: this is about a food menu */ - 'search_items' => __( 'Search Menu Items', 'jetpack' ), - /* translators: this is about a food menu */ - 'not_found' => __( 'No Menu Items found', 'jetpack' ), + /** + * Update messages for the Menu Item admin. + * + * @param array $messages Existing post update messages. + * + * @return array $messages Updated post update messages. + */ + public function updated_messages( $messages ) { + global $post; + + $messages[ self::MENU_ITEM_POST_TYPE ] = array( + 0 => '', // Unused. Messages start at index 1. + 1 => sprintf( + /* translators: this is about a food menu. Placeholder is a link to the food menu. */ + __( 'Menu item updated. View item', 'jetpack' ), + esc_url( get_permalink( $post->ID ) ) + ), + 2 => esc_html__( 'Custom field updated.', 'jetpack' ), + 3 => esc_html__( 'Custom field deleted.', 'jetpack' ), + /* translators: this is about a food menu */ + 4 => esc_html__( 'Menu item updated.', 'jetpack' ), + 5 => isset( $_GET['revision'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Copying core message handling. + ? sprintf( + /* translators: %s: date and time of the revision */ + esc_html__( 'Menu item restored to revision from %s', 'jetpack' ), + wp_post_revision_title( (int) $_GET['revision'], false ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Copying core message handling. + ) + : false, + 6 => sprintf( + /* translators: this is about a food menu. Placeholder is a link to the food menu. */ + __( 'Menu item published. View item', 'jetpack' ), + esc_url( get_permalink( $post->ID ) ) + ), + /* translators: this is about a food menu */ + 7 => esc_html__( 'Menu item saved.', 'jetpack' ), + 8 => sprintf( /* translators: this is about a food menu */ - 'not_found_in_trash' => __( 'No Menu Items found in Trash', 'jetpack' ), - 'filter_items_list' => __( 'Filter menu items list', 'jetpack' ), - 'items_list_navigation' => __( 'Menu item list navigation', 'jetpack' ), - 'items_list' => __( 'Menu items list', 'jetpack' ), + __( 'Menu item submitted. Preview item', 'jetpack' ), + esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) ), - 'supports' => array( - 'title', - 'editor', - 'thumbnail', - 'excerpt', + 9 => sprintf( + /* translators: this is about a food menu. 1. Publish box date format, see https://php.net/date 2. link to the food menu. */ + __( 'Menu item scheduled for: %1$s. Preview item', 'jetpack' ), + /* translators: Publish box date format, see https://php.net/date */ + date_i18n( __( 'M j, Y @ G:i', 'jetpack' ), strtotime( $post->post_date ) ), + esc_url( get_permalink( $post->ID ) ) ), - 'rewrite' => array( - 'slug' => 'item', - 'with_front' => false, - 'feeds' => false, - 'pages' => false, + 10 => sprintf( + /* translators: this is about a food menu. Placeholder is a link to the food menu. */ + __( 'Menu item draft updated. Preview item', 'jetpack' ), + esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) ), - 'register_meta_box_cb' => array( $this, 'register_menu_item_meta_boxes' ), - - 'public' => true, - 'show_ui' => true, // set to false to replace with custom UI - 'menu_position' => 20, // below Pages - 'capability_type' => 'page', - 'map_meta_cap' => true, - 'has_archive' => false, - 'query_var' => 'item', - ) - ); - } + ); - /** - * Update messages for the Menu Item admin. - * - * @param array $messages Existing post update messages. - * - * @return array $messages Updated post update messages. - */ - public function updated_messages( $messages ) { - global $post; - - $messages[ self::MENU_ITEM_POST_TYPE ] = array( - 0 => '', // Unused. Messages start at index 1. - 1 => sprintf( - /* translators: this is about a food menu. Placeholder is a link to the food menu. */ - __( 'Menu item updated. View item', 'jetpack' ), - esc_url( get_permalink( $post->ID ) ) - ), - 2 => esc_html__( 'Custom field updated.', 'jetpack' ), - 3 => esc_html__( 'Custom field deleted.', 'jetpack' ), - /* translators: this is about a food menu */ - 4 => esc_html__( 'Menu item updated.', 'jetpack' ), - 5 => isset( $_GET['revision'] ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Copying core message handling. - ? sprintf( - /* translators: %s: date and time of the revision */ - esc_html__( 'Menu item restored to revision from %s', 'jetpack' ), - wp_post_revision_title( (int) $_GET['revision'], false ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Copying core message handling. - ) - : false, - 6 => sprintf( - /* translators: this is about a food menu. Placeholder is a link to the food menu. */ - __( 'Menu item published. View item', 'jetpack' ), - esc_url( get_permalink( $post->ID ) ) - ), - /* translators: this is about a food menu */ - 7 => esc_html__( 'Menu item saved.', 'jetpack' ), - 8 => sprintf( - /* translators: this is about a food menu */ - __( 'Menu item submitted. Preview item', 'jetpack' ), - esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) - ), - 9 => sprintf( - /* translators: this is about a food menu. 1. Publish box date format, see https://php.net/date 2. link to the food menu. */ - __( 'Menu item scheduled for: %1$s. Preview item', 'jetpack' ), - /* translators: Publish box date format, see https://php.net/date */ - date_i18n( __( 'M j, Y @ G:i', 'jetpack' ), strtotime( $post->post_date ) ), - esc_url( get_permalink( $post->ID ) ) - ), - 10 => sprintf( - /* translators: this is about a food menu. Placeholder is a link to the food menu. */ - __( 'Menu item draft updated. Preview item', 'jetpack' ), - esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) - ), - ); + return $messages; + } - return $messages; - } + /** + * Nova styles and scripts. + * + * @param string $hook Page hook. + * + * @return void + */ + public function enqueue_nova_styles( $hook ) { + global $post_type; + $pages = array( 'edit.php', 'post.php', 'post-new.php' ); - /** - * Nova styles and scripts. - * - * @param string $hook Page hook. - * - * @return void - */ - public function enqueue_nova_styles( $hook ) { - global $post_type; - $pages = array( 'edit.php', 'post.php', 'post-new.php' ); + if ( in_array( $hook, $pages, true ) && $post_type === self::MENU_ITEM_POST_TYPE ) { + wp_enqueue_style( 'nova-style', plugins_url( 'css/nova.css', __FILE__ ), array(), $this->version ); + } - if ( in_array( $hook, $pages, true ) && $post_type === self::MENU_ITEM_POST_TYPE ) { - wp_enqueue_style( 'nova-style', plugins_url( 'css/nova.css', __FILE__ ), array(), $this->version ); + wp_enqueue_style( 'nova-font', plugins_url( 'css/nova-font.css', __FILE__ ), array(), $this->version ); } - wp_enqueue_style( 'nova-font', plugins_url( 'css/nova-font.css', __FILE__ ), array(), $this->version ); - } + /** + * Change ‘Enter Title Here’ text for the Menu Item. + * + * @param string $title Default title placeholder text. + * + * @return string + */ + public function change_default_title( $title ) { + if ( self::MENU_ITEM_POST_TYPE === get_post_type() ) { + /* translators: this is about a food menu */ + $title = esc_html__( "Enter the menu item's name here", 'jetpack' ); + } - /** - * Change ‘Enter Title Here’ text for the Menu Item. - * - * @param string $title Default title placeholder text. - * - * @return string - */ - public function change_default_title( $title ) { - if ( self::MENU_ITEM_POST_TYPE === get_post_type() ) { - /* translators: this is about a food menu */ - $title = esc_html__( "Enter the menu item's name here", 'jetpack' ); + return $title; } - return $title; - } + /** + * Add to Dashboard At A Glance + * + * @return void + */ + public function add_to_dashboard() { + $number_menu_items = wp_count_posts( self::MENU_ITEM_POST_TYPE ); - /** - * Add to Dashboard At A Glance - * - * @return void - */ - public function add_to_dashboard() { - $number_menu_items = wp_count_posts( self::MENU_ITEM_POST_TYPE ); - - $roles = new Roles(); - if ( current_user_can( $roles->translate_role_to_cap( 'administrator' ) ) ) { - $number_menu_items_published = sprintf( - '%2$s', - esc_url( - get_admin_url( - get_current_blog_id(), - 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE - ) - ), - sprintf( - /* translators: Placehoder is a number of items. */ - _n( - '%1$d Food Menu Item', - '%1$d Food Menu Items', - (int) $number_menu_items->publish, - 'jetpack' - ), - number_format_i18n( $number_menu_items->publish ) - ) - ); - } else { - $number_menu_items_published = sprintf( - '%1$s', - sprintf( - /* translators: Placehoder is a number of items. */ - _n( - '%1$d Food Menu Item', - '%1$d Food Menu Items', - (int) $number_menu_items->publish, - 'jetpack' + $roles = new Roles(); + if ( current_user_can( $roles->translate_role_to_cap( 'administrator' ) ) ) { + $number_menu_items_published = sprintf( + '%2$s', + esc_url( + get_admin_url( + get_current_blog_id(), + 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE + ) ), - number_format_i18n( $number_menu_items->publish ) - ) - ); + sprintf( + /* translators: Placehoder is a number of items. */ + _n( + '%1$d Food Menu Item', + '%1$d Food Menu Items', + (int) $number_menu_items->publish, + 'jetpack' + ), + number_format_i18n( $number_menu_items->publish ) + ) + ); + } else { + $number_menu_items_published = sprintf( + '%1$s', + sprintf( + /* translators: Placehoder is a number of items. */ + _n( + '%1$d Food Menu Item', + '%1$d Food Menu Items', + (int) $number_menu_items->publish, + 'jetpack' + ), + number_format_i18n( $number_menu_items->publish ) + ) + ); + } + + echo '
  • ' . $number_menu_items_published . '
  • '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- we escape things above. } - echo '
  • ' . $number_menu_items_published . '
  • '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- we escape things above. - } + /** + * If the WP query for our menu items. + * + * @param WP_Query $query WP Query. + * + * @return bool + */ + public function is_menu_item_query( $query ) { + if ( + ( isset( $query->query_vars['taxonomy'] ) && self::MENU_TAX === $query->query_vars['taxonomy'] ) + || + ( isset( $query->query_vars['post_type'] ) && self::MENU_ITEM_POST_TYPE === $query->query_vars['post_type'] ) + ) { + return true; + } - /** - * If the WP query for our menu items. - * - * @param WP_Query $query WP Query. - * - * @return bool - */ - public function is_menu_item_query( $query ) { - if ( - ( isset( $query->query_vars['taxonomy'] ) && self::MENU_TAX === $query->query_vars['taxonomy'] ) - || - ( isset( $query->query_vars['post_type'] ) && self::MENU_ITEM_POST_TYPE === $query->query_vars['post_type'] ) - ) { - return true; + return false; } - return false; - } + /** + * Custom sort the menu item queries by menu order. + * + * @param WP_Query $query WP Query. + * + * @return void + */ + public function sort_menu_item_queries_by_menu_order( $query ) { + if ( ! $this->is_menu_item_query( $query ) ) { + return; + } - /** - * Custom sort the menu item queries by menu order. - * - * @param WP_Query $query WP Query. - * - * @return void - */ - public function sort_menu_item_queries_by_menu_order( $query ) { - if ( ! $this->is_menu_item_query( $query ) ) { - return; + $query->query_vars['orderby'] = 'menu_order'; + $query->query_vars['order'] = 'ASC'; + + // For now, just turn off paging so we can sort by taxonmy later + // If we want paging in the future, we'll need to add the taxonomy sort here (or at least before the DB query is made) + $query->query_vars['posts_per_page'] = -1; } - $query->query_vars['orderby'] = 'menu_order'; - $query->query_vars['order'] = 'ASC'; + /** + * Custom sort the menu item queries by menu taxonomies. + * + * @param WP_Post[] $posts Array of post objects. + * @param WP_Query $query The WP_Query instance. + * + * @return WP_Post[] + */ + public function sort_menu_item_queries_by_menu_taxonomy( $posts, $query ) { + if ( ! $posts ) { + return $posts; + } - // For now, just turn off paging so we can sort by taxonmy later - // If we want paging in the future, we'll need to add the taxonomy sort here (or at least before the DB query is made) - $query->query_vars['posts_per_page'] = -1; - } + if ( ! $this->is_menu_item_query( $query ) ) { + return $posts; + } - /** - * Custom sort the menu item queries by menu taxonomies. - * - * @param WP_Post[] $posts Array of post objects. - * @param WP_Query $query The WP_Query instance. - * - * @return WP_Post[] - */ - public function sort_menu_item_queries_by_menu_taxonomy( $posts, $query ) { - if ( ! $posts ) { - return $posts; - } + $grouped_by_term = array(); - if ( ! $this->is_menu_item_query( $query ) ) { - return $posts; - } + foreach ( $posts as $post ) { + $term = $this->get_menu_item_menu_leaf( $post->ID ); + if ( ! $term || is_wp_error( $term ) ) { + $term_id = 0; + } else { + $term_id = $term->term_id; + } - $grouped_by_term = array(); + if ( ! isset( $grouped_by_term[ "$term_id" ] ) ) { + $grouped_by_term[ "$term_id" ] = array(); + } - foreach ( $posts as $post ) { - $term = $this->get_menu_item_menu_leaf( $post->ID ); - if ( ! $term || is_wp_error( $term ) ) { - $term_id = 0; - } else { - $term_id = $term->term_id; + $grouped_by_term[ "$term_id" ][] = $post; + } + + $term_order = get_option( 'nova_menu_order', array() ); + + $return = array(); + foreach ( $term_order as $term_id ) { + if ( isset( $grouped_by_term[ "$term_id" ] ) ) { + $return = array_merge( $return, $grouped_by_term[ "$term_id" ] ); + unset( $grouped_by_term[ "$term_id" ] ); + } } - if ( ! isset( $grouped_by_term[ "$term_id" ] ) ) { - $grouped_by_term[ "$term_id" ] = array(); + foreach ( $grouped_by_term as $term_id => $posts ) { + $return = array_merge( $return, $posts ); } - $grouped_by_term[ "$term_id" ][] = $post; + return $return; } - $term_order = get_option( 'nova_menu_order', array() ); + /** + * Add new "Add many items" submenu, custom colunmns, and custom bulk actions. + * + * @return void + */ + public function add_admin_menus() { + $hook = add_submenu_page( + 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE, + __( 'Add Many Items', 'jetpack' ), + __( 'Add Many Items', 'jetpack' ), + 'edit_pages', + 'add_many_nova_items', + array( $this, 'add_many_new_items_page' ) + ); - $return = array(); - foreach ( $term_order as $term_id ) { - if ( isset( $grouped_by_term[ "$term_id" ] ) ) { - $return = array_merge( $return, $grouped_by_term[ "$term_id" ] ); - unset( $grouped_by_term[ "$term_id" ] ); + add_action( "load-$hook", array( $this, 'add_many_new_items_page_load' ) ); + + add_action( 'current_screen', array( $this, 'current_screen_load' ) ); + + /* + * Adjust 'Add Many Items' submenu position + * We're making changes to the menu global, but no other choice unfortunately. + * phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited + */ + if ( isset( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] ) ) { + $submenu_item = array_pop( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] ); + $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ][11] = $submenu_item; + ksort( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] ); } + // phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited + + $this->setup_menu_item_columns(); + + wp_register_script( + 'nova-menu-checkboxes', + Assets::get_file_url_for_environment( + '_inc/build/custom-post-types/js/menu-checkboxes.min.js', + 'modules/custom-post-types/js/menu-checkboxes.js' + ), + array(), + $this->version, + true + ); } - foreach ( $grouped_by_term as $term_id => $posts ) { - $return = array_merge( $return, $posts ); + /** + * Custom Nova Icon CSS + * + * @return void + */ + public function set_custom_font_icon() { + ?> + + id ) { + return; + } - /** - * Add new "Add many items" submenu, custom colunmns, and custom bulk actions. - * - * @return void - */ - public function add_admin_menus() { - $hook = add_submenu_page( - 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE, - __( 'Add Many Items', 'jetpack' ), - __( 'Add Many Items', 'jetpack' ), - 'edit_pages', - 'add_many_nova_items', - array( $this, 'add_many_new_items_page' ) - ); + $this->edit_menu_items_page_load(); + add_filter( 'admin_notices', array( $this, 'admin_notices' ) ); + } - add_action( "load-$hook", array( $this, 'add_many_new_items_page_load' ) ); + /* Edit Items List */ - add_action( 'current_screen', array( $this, 'current_screen_load' ) ); + /** + * Display a notice in wp-admin after items have been changed. + * + * @return void + */ + public function admin_notices() { + if ( isset( $_GET['nova_reordered'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is only displaying a message with no dynamic values. + printf( + '

    %s

    ', + /* translators: this is about a food menu */ + esc_html__( 'Menu Items re-ordered.', 'jetpack' ) + ); + } + } - /* - * Adjust 'Add Many Items' submenu position - * We're making changes to the menu global, but no other choice unfortunately. - * phpcs:disable WordPress.WP.GlobalVariablesOverride.Prohibited + /** + * Do not allow sorting by title. + * + * @param array $columns An array of sortable columns. + * + * @return array $columns. */ - if ( isset( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] ) ) { - $submenu_item = array_pop( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] ); - $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ][11] = $submenu_item; - ksort( $GLOBALS['submenu'][ 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ] ); + public function no_title_sorting( $columns ) { + if ( isset( $columns['title'] ) ) { + unset( $columns['title'] ); + } + return $columns; } - // phpcs:enable WordPress.WP.GlobalVariablesOverride.Prohibited - - $this->setup_menu_item_columns(); - - wp_register_script( - 'nova-menu-checkboxes', - Assets::get_file_url_for_environment( - '_inc/build/custom-post-types/js/menu-checkboxes.min.js', - 'modules/custom-post-types/js/menu-checkboxes.js' - ), - array(), - $this->version, - true - ); - } - /** - * Custom Nova Icon CSS - * - * @return void - */ - public function set_custom_font_icon() { - ?> - - id ) { - return; + add_action( sprintf( 'manage_%s_posts_custom_column', self::MENU_ITEM_POST_TYPE ), array( $this, 'menu_item_column_callback' ), 10, 2 ); } - $this->edit_menu_items_page_load(); - add_filter( 'admin_notices', array( $this, 'admin_notices' ) ); - } + /** + * Add custom columns to the Nova menu item list. + * + * @param array $columns An array of columns. + * + * @return array $columns. + */ + public function menu_item_columns( $columns ) { + unset( $columns['date'], $columns['likes'] ); - /* Edit Items List */ + $columns['thumbnail'] = __( 'Thumbnail', 'jetpack' ); + $columns['labels'] = __( 'Labels', 'jetpack' ); + $columns['price'] = __( 'Price', 'jetpack' ); + $columns['order'] = __( 'Order', 'jetpack' ); - /** - * Display a notice in wp-admin after items have been changed. - * - * @return void - */ - public function admin_notices() { - if ( isset( $_GET['nova_reordered'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is only displaying a message with no dynamic values. - printf( - '

    %s

    ', - /* translators: this is about a food menu */ - esc_html__( 'Menu Items re-ordered.', 'jetpack' ) - ); + return $columns; } - } - /** - * Do not allow sorting by title. - * - * @param array $columns An array of sortable columns. - * - * @return array $columns. - */ - public function no_title_sorting( $columns ) { - if ( isset( $columns['title'] ) ) { - unset( $columns['title'] ); + /** + * Display custom data in each new custom column we created. + * + * @param string $column The name of the column to display. + * @param int $post_id The current post ID. + * + * @return void + */ + public function menu_item_column_callback( $column, $post_id ) { + $screen = get_current_screen(); + + switch ( $column ) { + case 'thumbnail': + echo get_the_post_thumbnail( $post_id, array( 50, 50 ) ); + break; + case 'labels': + $this->list_admin_labels( $post_id ); + break; + case 'price': + $this->display_price( $post_id ); + break; + case 'order': + $url = admin_url( $screen->parent_file ); + + $up_url = add_query_arg( + array( + 'action' => 'move-item-up', + 'post_id' => (int) $post_id, + ), + wp_nonce_url( $url, 'nova_move_item_up_' . $post_id ) + ); + + $down_url = add_query_arg( + array( + 'action' => 'move-item-down', + 'post_id' => (int) $post_id, + ), + wp_nonce_url( $url, 'nova_move_item_down_' . $post_id ) + ); + $menu_item = get_post( $post_id ); + $this->get_menu_by_post_id( $post_id ); + $term_id = $this->get_menu_by_post_id( $post_id ); + if ( $term_id ) { + $term_id = $term_id->term_id; + } + ?> + + + + +     — up +
    +     — down +
    + list_admin_labels( $post_id ); - break; - case 'price': - $this->display_price( $post_id ); - break; - case 'order': - $url = admin_url( $screen->parent_file ); - - $up_url = add_query_arg( - array( - 'action' => 'move-item-up', - 'post_id' => (int) $post_id, - ), - wp_nonce_url( $url, 'nova_move_item_up_' . $post_id ) - ); + // make sure we have the nonce. + if ( + empty( $_REQUEST['drag-drop-reorder'] ) + || ! wp_verify_nonce( sanitize_key( $_REQUEST['drag-drop-reorder'] ), 'drag-drop-reorder' ) + ) { + return; + } - $down_url = add_query_arg( - array( - 'action' => 'move-item-down', - 'post_id' => (int) $post_id, - ), - wp_nonce_url( $url, 'nova_move_item_down_' . $post_id ) - ); - $menu_item = get_post( $post_id ); - $this->get_menu_by_post_id( $post_id ); - $term_id = $this->get_menu_by_post_id( $post_id ); - if ( $term_id ) { - $term_id = $term_id->term_id; - } - ?> - - - - -     — up -
    -     — down -
    - $menu_order ) { + $id = absint( $id ); + unset( $order_pairs[ $id ] ); + if ( $id < 0 ) { + continue; + } - if ( ! is_array( $terms ) ) { - return false; - } + $post = get_post( $id ); + if ( ! $post ) { + continue; + } - return array_pop( $terms ); - } + // save a write if the order hasn't changed + if ( (int) $menu_order !== $post->menu_order ) { + $args = array( + 'ID' => $id, + 'menu_order' => $menu_order, + ); + wp_update_post( $args ); + } - /** - * Fires on a menu edit page. We might have drag-n-drop reordered - */ - public function maybe_reorder_menu_items() { - // make sure we clicked our button. - if ( - empty( $_REQUEST['menu_reorder_submit'] ) - || __( 'Save New Order', 'jetpack' ) !== $_REQUEST['menu_reorder_submit'] - ) { - return; - } + // save a write if the term hasn't changed + if ( (int) $term_pairs[ $id ] !== $this->get_menu_by_post_id( $id )->term_id ) { + wp_set_object_terms( $id, $term_pairs[ $id ], self::MENU_TAX ); + } + } - // make sure we have the nonce. - if ( - empty( $_REQUEST['drag-drop-reorder'] ) - || ! wp_verify_nonce( sanitize_key( $_REQUEST['drag-drop-reorder'] ), 'drag-drop-reorder' ) - ) { - return; + $redirect = add_query_arg( + array( + 'post_type' => self::MENU_ITEM_POST_TYPE, + 'nova_reordered' => '1', + ), + admin_url( 'edit.php' ) + ); + wp_safe_redirect( $redirect ); + exit; } - // make sure we have data to work with. - if ( empty( $_REQUEST['nova_menu_term'] ) || empty( $_REQUEST['nova_order'] ) ) { - return; - } + /** + * Handle changes to menu items. + * (process actions, update data, enqueue necessary scripts). + * + * @return void + */ + public function edit_menu_items_page_load() { + if ( isset( $_GET['action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we process the form and check nonces in handle_menu_item_actions. + $this->handle_menu_item_actions(); + } - $term_pairs = array_map( 'absint', $_REQUEST['nova_menu_term'] ); - $order_pairs = array_map( 'absint', $_REQUEST['nova_order'] ); + $this->maybe_reorder_menu_items(); - foreach ( $order_pairs as $id => $menu_order ) { - $id = absint( $id ); - unset( $order_pairs[ $id ] ); - if ( $id < 0 ) { - continue; - } + wp_enqueue_script( + 'nova-drag-drop', + Assets::get_file_url_for_environment( + '_inc/build/custom-post-types/js/nova-drag-drop.min.js', + 'modules/custom-post-types/js/nova-drag-drop.js' + ), + array( 'jquery', 'jquery-ui-sortable' ), + $this->version, + true + ); - $post = get_post( $id ); - if ( ! $post ) { - continue; - } + wp_localize_script( + 'nova-drag-drop', + '_novaDragDrop', + array( + 'nonce' => wp_create_nonce( 'drag-drop-reorder' ), + 'nonceName' => 'drag-drop-reorder', + 'reorder' => __( 'Save New Order', 'jetpack' ), + 'reorderName' => 'menu_reorder_submit', + ) + ); + add_action( 'the_post', array( $this, 'show_menu_titles_in_menu_item_list' ) ); + } - // save a write if the order hasn't changed - if ( (int) $menu_order !== $post->menu_order ) { - $args = array( - 'ID' => $id, - 'menu_order' => $menu_order, - ); - wp_update_post( $args ); + /** + * Process actions to move menu items around. + * + * @return void + */ + public function handle_menu_item_actions() { + if ( isset( $_GET['action'] ) ) { + $action = (string) wp_unslash( $_GET['action'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- we check for nonces below, and check against specific strings in switch statement. + } else { + return; } - // save a write if the term hasn't changed - if ( (int) $term_pairs[ $id ] !== $this->get_menu_by_post_id( $id )->term_id ) { - wp_set_object_terms( $id, $term_pairs[ $id ], self::MENU_TAX ); - } - } + switch ( $action ) { + case 'move-item-up': + case 'move-item-down': + $reorder = false; - $redirect = add_query_arg( - array( - 'post_type' => self::MENU_ITEM_POST_TYPE, - 'nova_reordered' => '1', - ), - admin_url( 'edit.php' ) - ); - wp_safe_redirect( $redirect ); - exit; - } + if ( empty( $_GET['post_id'] ) ) { + break; + } - /** - * Handle changes to menu items. - * (process actions, update data, enqueue necessary scripts). - * - * @return void - */ - public function edit_menu_items_page_load() { - if ( isset( $_GET['action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we process the form and check nonces in handle_menu_item_actions. - $this->handle_menu_item_actions(); - } + $post_id = (int) $_GET['post_id']; - $this->maybe_reorder_menu_items(); - - wp_enqueue_script( - 'nova-drag-drop', - Assets::get_file_url_for_environment( - '_inc/build/custom-post-types/js/nova-drag-drop.min.js', - 'modules/custom-post-types/js/nova-drag-drop.js' - ), - array( 'jquery', 'jquery-ui-sortable' ), - $this->version, - true - ); + $term = $this->get_menu_item_menu_leaf( $post_id ); - wp_localize_script( - 'nova-drag-drop', - '_novaDragDrop', - array( - 'nonce' => wp_create_nonce( 'drag-drop-reorder' ), - 'nonceName' => 'drag-drop-reorder', - 'reorder' => __( 'Save New Order', 'jetpack' ), - 'reorderName' => 'menu_reorder_submit', - ) - ); - add_action( 'the_post', array( $this, 'show_menu_titles_in_menu_item_list' ) ); - } + // Get all posts in that term. + $query = new WP_Query( + array( + 'taxonomy' => self::MENU_TAX, + 'term' => $term->slug, + ) + ); - /** - * Process actions to move menu items around. - * - * @return void - */ - public function handle_menu_item_actions() { - if ( isset( $_GET['action'] ) ) { - $action = (string) wp_unslash( $_GET['action'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- we check for nonces below, and check against specific strings in switch statement. - } else { - return; - } + $order = array(); + foreach ( $query->posts as $post ) { + $order[] = $post->ID; + } - switch ( $action ) { - case 'move-item-up': - case 'move-item-down': - $reorder = false; + if ( 'move-item-up' === $action ) { + check_admin_referer( 'nova_move_item_up_' . $post_id ); - if ( empty( $_GET['post_id'] ) ) { - break; - } + $first_post_id = $order[0]; + if ( $post_id === $first_post_id ) { + break; + } - $post_id = (int) $_GET['post_id']; + foreach ( $order as $menu_order => $order_post_id ) { + if ( $post_id !== $order_post_id ) { + continue; + } - $term = $this->get_menu_item_menu_leaf( $post_id ); + $swap_post_id = $order[ $menu_order - 1 ]; + $order[ $menu_order - 1 ] = $post_id; + $order[ $menu_order ] = $swap_post_id; - // Get all posts in that term. - $query = new WP_Query( - array( - 'taxonomy' => self::MENU_TAX, - 'term' => $term->slug, - ) - ); + $reorder = true; + break; + } + } else { + check_admin_referer( 'nova_move_item_down_' . $post_id ); - $order = array(); - foreach ( $query->posts as $post ) { - $order[] = $post->ID; - } + $last_post_id = end( $order ); + if ( $post_id === $last_post_id ) { + break; + } - if ( 'move-item-up' === $action ) { - check_admin_referer( 'nova_move_item_up_' . $post_id ); + foreach ( $order as $menu_order => $order_post_id ) { + if ( $post_id !== $order_post_id ) { + continue; + } - $first_post_id = $order[0]; - if ( $post_id === $first_post_id ) { - break; + $swap_post_id = $order[ $menu_order + 1 ]; + $order[ $menu_order + 1 ] = $post_id; + $order[ $menu_order ] = $swap_post_id; + + $reorder = true; + } } - foreach ( $order as $menu_order => $order_post_id ) { - if ( $post_id !== $order_post_id ) { - continue; + if ( $reorder ) { + foreach ( $order as $menu_order => $id ) { + wp_update_post( compact( 'id', 'menu_order' ) ); } + } - $swap_post_id = $order[ $menu_order - 1 ]; - $order[ $menu_order - 1 ] = $post_id; - $order[ $menu_order ] = $swap_post_id; + break; + case 'move-menu-up': + case 'move-menu-down': + $reorder = false; - $reorder = true; + if ( empty( $_GET['term_id'] ) ) { break; } - } else { - check_admin_referer( 'nova_move_item_down_' . $post_id ); - $last_post_id = end( $order ); - if ( $post_id === $last_post_id ) { - break; + $term_id = (int) $_GET['term_id']; + + $terms = $this->get_menus(); + + $order = array(); + foreach ( $terms as $term ) { + $order[] = $term->term_id; } - foreach ( $order as $menu_order => $order_post_id ) { - if ( $post_id !== $order_post_id ) { - continue; + if ( 'move-menu-up' === $action ) { + check_admin_referer( 'nova_move_menu_up_' . $term_id ); + + $first_term_id = $order[0]; + if ( $term_id === $first_term_id ) { + break; } - $swap_post_id = $order[ $menu_order + 1 ]; - $order[ $menu_order + 1 ] = $post_id; - $order[ $menu_order ] = $swap_post_id; + foreach ( $order as $menu_order => $order_term_id ) { + if ( $term_id !== $order_term_id ) { + continue; + } - $reorder = true; - } - } + $swap_term_id = $order[ $menu_order - 1 ]; + $order[ $menu_order - 1 ] = $term_id; + $order[ $menu_order ] = $swap_term_id; + + $reorder = true; + break; + } + } else { + check_admin_referer( 'nova_move_menu_down_' . $term_id ); + + $last_term_id = end( $order ); + if ( $term_id === $last_term_id ) { + break; + } + + foreach ( $order as $menu_order => $order_term_id ) { + if ( $term_id !== $order_term_id ) { + continue; + } - if ( $reorder ) { - foreach ( $order as $menu_order => $id ) { - wp_update_post( compact( 'id', 'menu_order' ) ); + $swap_term_id = $order[ $menu_order + 1 ]; + $order[ $menu_order + 1 ] = $term_id; + $order[ $menu_order ] = $swap_term_id; + + $reorder = true; + } } - } - break; - case 'move-menu-up': - case 'move-menu-down': - $reorder = false; + if ( $reorder ) { + update_option( 'nova_menu_order', $order ); + } - if ( empty( $_GET['term_id'] ) ) { break; - } + default: + return; + } - $term_id = (int) $_GET['term_id']; + $redirect = add_query_arg( + array( + 'post_type' => self::MENU_ITEM_POST_TYPE, + 'nova_reordered' => '1', + ), + admin_url( 'edit.php' ) + ); + wp_safe_redirect( $redirect ); + exit; + } - $terms = $this->get_menus(); + /** + * Add menu title rows to the list table + * + * @param WP_Post $post The Post object. + * + * @return void + */ + public function show_menu_titles_in_menu_item_list( $post ) { + global $wp_list_table; - $order = array(); - foreach ( $terms as $term ) { - $order[] = $term->term_id; - } + static $last_term_id = false; - if ( 'move-menu-up' === $action ) { - check_admin_referer( 'nova_move_menu_up_' . $term_id ); + $term = $this->get_menu_item_menu_leaf( $post->ID ); - $first_term_id = $order[0]; - if ( $term_id === $first_term_id ) { - break; - } + $term_id = $term instanceof WP_Term ? $term->term_id : null; - foreach ( $order as $menu_order => $order_term_id ) { - if ( $term_id !== $order_term_id ) { - continue; - } + if ( false !== $last_term_id && $last_term_id === $term_id ) { + return; + } - $swap_term_id = $order[ $menu_order - 1 ]; - $order[ $menu_order - 1 ] = $term_id; - $order[ $menu_order ] = $swap_term_id; + if ( $term_id === null ) { + $last_term_id = null; + $term_name = ''; + $parent_count = 0; + } else { + $last_term_id = $term->term_id; + $term_name = $term->name; + $parent_count = 0; + $current_term = $term; + while ( $current_term->parent ) { + ++$parent_count; + $current_term = get_term( $current_term->parent, self::MENU_TAX ); + } + } - $reorder = true; - break; - } - } else { - check_admin_referer( 'nova_move_menu_down_' . $term_id ); + $non_order_column_count = $wp_list_table->get_column_count() - 1; - $last_term_id = end( $order ); - if ( $term_id === $last_term_id ) { - break; - } + $screen = get_current_screen(); + + $url = admin_url( $screen->parent_file ); + + $up_url = add_query_arg( + array( + 'action' => 'move-menu-up', + 'term_id' => (int) $term_id, + ), + wp_nonce_url( $url, 'nova_move_menu_up_' . $term_id ) + ); + + $down_url = add_query_arg( + array( + 'action' => 'move-menu-down', + 'term_id' => (int) $term_id, + ), + wp_nonce_url( $url, 'nova_move_menu_down_' . $term_id ) + ); - foreach ( $order as $menu_order => $order_term_id ) { - if ( $term_id !== $order_term_id ) { - continue; - } + ?> + + +

    + ', '', $term ); - $reorder = true; + } else { + esc_html_e( 'Uncategorized', 'jetpack' ); } - } - - if ( $reorder ) { - update_option( 'nova_menu_order', $order ); - } - - break; - default: - return; + ?> +

    + + + + +
    + + + + + self::MENU_ITEM_POST_TYPE, - 'nova_reordered' => '1', - ), - admin_url( 'edit.php' ) - ); - wp_safe_redirect( $redirect ); - exit; - } - - /** - * Add menu title rows to the list table - * - * @param WP_Post $post The Post object. - * - * @return void - */ - public function show_menu_titles_in_menu_item_list( $post ) { - global $wp_list_table; - - static $last_term_id = false; + /* Edit Many Items */ - $term = $this->get_menu_item_menu_leaf( $post->ID ); + /** + * Handle form submissions that aim to add many menu items at once. + * (process posted data and enqueue necessary script). + * + * @return void + */ + public function add_many_new_items_page_load() { + if ( + isset( $_SERVER['REQUEST_METHOD'] ) + && 'POST' === strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) + ) { + $this->process_form_request(); + exit; + } - $term_id = $term instanceof WP_Term ? $term->term_id : null; + $this->enqueue_many_items_scripts(); + } - if ( false !== $last_term_id && $last_term_id === $term_id ) { - return; + /** + * Enqueue script to create many items at once. + * + * @return void + */ + public function enqueue_many_items_scripts() { + wp_enqueue_script( + 'nova-many-items', + Assets::get_file_url_for_environment( + '_inc/build/custom-post-types/js/many-items.min.js', + 'modules/custom-post-types/js/many-items.js' + ), + array(), + $this->version, + true + ); } - if ( $term_id === null ) { - $last_term_id = null; - $term_name = ''; - $parent_count = 0; - } else { - $last_term_id = $term->term_id; - $term_name = $term->name; - $parent_count = 0; - $current_term = $term; - while ( $current_term->parent ) { - ++$parent_count; - $current_term = get_term( $current_term->parent, self::MENU_TAX ); + /** + * Process form request to create many items at once. + * + * @return void + */ + public function process_form_request() { + if ( ! isset( $_POST['nova_title'] ) || ! is_array( $_POST['nova_title'] ) ) { + return; } - } - $non_order_column_count = $wp_list_table->get_column_count() - 1; + $is_ajax = ! empty( $_POST['ajax'] ); - $screen = get_current_screen(); + if ( $is_ajax ) { + check_ajax_referer( 'nova_many_items' ); + } else { + check_admin_referer( 'nova_many_items' ); + } - $url = admin_url( $screen->parent_file ); + /* + * $_POST is already slashed + * phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash + */ + foreach ( array_keys( $_POST['nova_title'] ) as $key ) : // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- we sanitize below. + $post_details = array( + 'post_status' => 'publish', + 'post_type' => self::MENU_ITEM_POST_TYPE, + 'post_content' => ! empty( $_POST['nova_content'] ) && ! empty( $_POST['nova_content'][ $key ] ) + ? sanitize_text_field( $_POST['nova_content'][ $key ] ) + : '', + 'post_title' => isset( $_POST['nova_title'][ $key ] ) + ? sanitize_title( $_POST['nova_title'][ $key ] ) + : '', + 'tax_input' => array( + self::MENU_ITEM_LABEL_TAX => isset( $_POST['nova_labels'][ $key ] ) + ? sanitize_meta( self::MENU_ITEM_LABEL_TAX, $_POST['nova_labels'][ $key ], 'term' ) + : null, + self::MENU_TAX => isset( $_POST['nova_menu_tax'] ) + ? sanitize_meta( self::MENU_TAX, $_POST['nova_menu_tax'], 'term' ) + : null, + ), + ); - $up_url = add_query_arg( - array( - 'action' => 'move-menu-up', - 'term_id' => (int) $term_id, - ), - wp_nonce_url( $url, 'nova_move_menu_up_' . $term_id ) - ); + $post_id = wp_insert_post( $post_details ); + if ( ! $post_id || is_wp_error( $post_id ) ) { + continue; + } - $down_url = add_query_arg( - array( - 'action' => 'move-menu-down', - 'term_id' => (int) $term_id, - ), - wp_nonce_url( $url, 'nova_move_menu_down_' . $term_id ) - ); + $this->set_price( + $post_id, + isset( $_POST['nova_price'][ $key ] ) + ? sanitize_meta( 'nova_price', $_POST['nova_price'][ $key ], 'post' ) + : '' + ); + // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash - ?> - - -

    - ', '', $term ); + ?> + + display_price(); ?> + list_labels( $post_id ); ?> + + -

    - - - - -
    - - - - - process_form_request(); + wp_safe_redirect( admin_url( 'edit.php?post_type=' . self::MENU_ITEM_POST_TYPE ) ); exit; } - $this->enqueue_many_items_scripts(); - } + /** + * Admin page contents for adding many menu items at once. + * + * @return void + */ + public function add_many_new_items_page() { + ?> +
    +

    - /** - * Enqueue script to create many items at once. - * - * @return void - */ - public function enqueue_many_items_scripts() { - wp_enqueue_script( - 'nova-many-items', - Assets::get_file_url_for_environment( - '_inc/build/custom-post-types/js/many-items.min.js', - 'modules/custom-post-types/js/many-items.js' - ), - array(), - $this->version, - true - ); - } +

    + TAB key on your keyboard to move between colums and the ENTER or RETURN key to save each row and move on to the next.', 'jetpack' ), + array( + 'kbd' => array(), + ) + ); + ?> +

    - /** - * Process form request to create many items at once. - * - * @return void - */ - public function process_form_request() { - if ( ! isset( $_POST['nova_title'] ) || ! is_array( $_POST['nova_title'] ) ) { - return; +
    +

    +

    + 'nova-menu-tax', + 'name' => 'nova_menu_tax', + 'taxonomy' => self::MENU_TAX, + 'hide_empty' => false, + 'hierarchical' => true, + ) + ); + ?> +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + spicy, favorite, etc. Separate Labels with commas', 'jetpack' ), + array( + 'small' => array(), + 'em' => array(), + ) + ); + ?> +
    +
    +
    + +

    + + +

    +
    +
    + 'publish', - 'post_type' => self::MENU_ITEM_POST_TYPE, - 'post_content' => ! empty( $_POST['nova_content'] ) && ! empty( $_POST['nova_content'][ $key ] ) - ? sanitize_text_field( $_POST['nova_content'][ $key ] ) - : '', - 'post_title' => isset( $_POST['nova_title'][ $key ] ) - ? sanitize_title( $_POST['nova_title'][ $key ] ) - : '', - 'tax_input' => array( - self::MENU_ITEM_LABEL_TAX => isset( $_POST['nova_labels'][ $key ] ) - ? sanitize_meta( self::MENU_ITEM_LABEL_TAX, $_POST['nova_labels'][ $key ], 'term' ) - : null, - self::MENU_TAX => isset( $_POST['nova_menu_tax'] ) - ? sanitize_meta( self::MENU_TAX, $_POST['nova_menu_tax'], 'term' ) - : null, - ), + public function menu_item_price_meta_box( $post ) { + printf( + '', + (int) $post->ID, + esc_html__( 'Price', 'jetpack' ), + esc_attr( $this->get_price( (int) $post->ID ) ) ); + } - $post_id = wp_insert_post( $post_details ); - if ( ! $post_id || is_wp_error( $post_id ) ) { - continue; + /** + * Save the price of a menu item. + * + * @param int $post_id Post ID. + */ + public function add_post_meta( $post_id ) { + if ( ! isset( $_POST['nova_price'][ $post_id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce handling happens via core, since we hook into wp_insert_post. + return; } $this->set_price( $post_id, - isset( $_POST['nova_price'][ $key ] ) - ? sanitize_meta( 'nova_price', $_POST['nova_price'][ $key ], 'post' ) - : '' + sanitize_meta( 'nova_price', wp_unslash( $_POST['nova_price'][ $post_id ] ), 'post' ) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce handling happens via core, since we hook into wp_insert_post. ); - // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash - - if ( $is_ajax ) : - $post = get_post( $post_id ); - $GLOBALS['post'] = $post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - setup_postdata( $post ); - - ?> - - display_price(); ?> - list_labels( $post_id ); ?> - - -
    -

    - -

    - TAB key on your keyboard to move between colums and the ENTER or RETURN key to save each row and move on to the next.', 'jetpack' ), - array( - 'kbd' => array(), - ) - ); - ?> -

    + /** + * Get ordered array of menu items. + * + * @param array $args Optional argumments. + * + * @return array + */ + public function get_menus( $args = array() ) { + $args = wp_parse_args( + $args, + array( + 'hide_empty' => false, + ) + ); + $args['taxonomy'] = self::MENU_TAX; -
    -

    -

    - 'nova-menu-tax', - 'name' => 'nova_menu_tax', - 'taxonomy' => self::MENU_TAX, - 'hide_empty' => false, - 'hierarchical' => true, - ) - ); - ?> -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - spicy, favorite, etc. Separate Labels with commas', 'jetpack' ), - array( - 'small' => array(), - 'em' => array(), - ) - ); - ?> -
    -
    -
    - -

    - - -

    -
    -
    - term_id}" ] = $term; + } - /** - * Create admin meta box to save price for a menu item, - * and add script to add extra checkboxes to the UI. - * - * @return void - */ - public function register_menu_item_meta_boxes() { - wp_enqueue_script( 'nova-menu-checkboxes' ); - - add_meta_box( - 'menu_item_price', - __( 'Price', 'jetpack' ), - array( $this, 'menu_item_price_meta_box' ), - null, - 'side', - 'high' - ); - } + $term_order = get_option( 'nova_menu_order', array() ); - /** - * Meta box to edit the price of a menu item. - * - * @param WP_Post $post The post object. - * - * @return void - */ - public function menu_item_price_meta_box( $post ) { - printf( - '', - (int) $post->ID, - esc_html__( 'Price', 'jetpack' ), - esc_attr( $this->get_price( (int) $post->ID ) ) - ); - } + $return = array(); + foreach ( $term_order as $term_id ) { + if ( isset( $terms_by_id[ "$term_id" ] ) ) { + $return[] = $terms_by_id[ "$term_id" ]; + unset( $terms_by_id[ "$term_id" ] ); + } + } - /** - * Save the price of a menu item. - * - * @param int $post_id Post ID. - */ - public function add_post_meta( $post_id ) { - if ( ! isset( $_POST['nova_price'][ $post_id ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce handling happens via core, since we hook into wp_insert_post. - return; + foreach ( $terms_by_id as $term_id => $term ) { + $return[] = $term; + } + + return $return; } - $this->set_price( - $post_id, - sanitize_meta( 'nova_price', wp_unslash( $_POST['nova_price'][ $post_id ] ), 'post' ) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce handling happens via core, since we hook into wp_insert_post. - ); - } + /** + * Get first menu taxonomy "leaf". + * + * @param int $post_id Post ID. + * + * @return bool|WP_Term|WP_Error|null + */ + public function get_menu_item_menu_leaf( $post_id ) { + // Get first menu taxonomy "leaf". + $term_ids = wp_get_object_terms( $post_id, self::MENU_TAX, array( 'fields' => 'ids' ) ); - /* Data */ + foreach ( $term_ids as $term_id ) { + $children = get_term_children( $term_id, self::MENU_TAX ); + if ( ! $children ) { + break; + } + } - /** - * Get ordered array of menu items. - * - * @param array $args Optional argumments. - * - * @return array - */ - public function get_menus( $args = array() ) { - $args = wp_parse_args( - $args, - array( - 'hide_empty' => false, - ) - ); - $args['taxonomy'] = self::MENU_TAX; + if ( ! isset( $term_id ) ) { + return false; + } - $terms = get_terms( $args ); - if ( ! $terms || is_wp_error( $terms ) ) { - return array(); + return get_term( $term_id, self::MENU_TAX ); } - $terms_by_id = array(); - foreach ( $terms as $term ) { - $terms_by_id[ "{$term->term_id}" ] = $term; + /** + * Get a list of the labels linked to a menu item. + * + * @param int $post_id Post ID. + * + * @return void + */ + public function list_labels( $post_id = 0 ) { + $post = get_post( $post_id ); + echo get_the_term_list( $post->ID, self::MENU_ITEM_LABEL_TAX, '', _x( ', ', 'Nova label separator', 'jetpack' ), '' ); } - $term_order = get_option( 'nova_menu_order', array() ); + /** + * Get a list of the labels linked to a menu item, with links to manage them. + * + * @param int $post_id Post ID. + * + * @return void + */ + public function list_admin_labels( $post_id = 0 ) { + $post = get_post( $post_id ); + $labels = get_the_terms( $post->ID, self::MENU_ITEM_LABEL_TAX ); + if ( ! empty( $labels ) ) { + $out = array(); + foreach ( $labels as $label ) { + $out[] = sprintf( + '%s', + esc_url( + add_query_arg( + array( + 'post_type' => self::MENU_ITEM_POST_TYPE, + 'taxonomy' => self::MENU_ITEM_LABEL_TAX, + 'term' => $label->slug, + ), + 'edit.php' + ) + ), + esc_html( + sanitize_term_field( 'name', $label->name, $label->term_id, self::MENU_ITEM_LABEL_TAX, 'display' ) + ) + ); + } - $return = array(); - foreach ( $term_order as $term_id ) { - if ( isset( $terms_by_id[ "$term_id" ] ) ) { - $return[] = $terms_by_id[ "$term_id" ]; - unset( $terms_by_id[ "$term_id" ] ); + echo implode( _x( ', ', 'Nova label separator', 'jetpack' ), $out ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- we build $out ourselves and escape things there. + } else { + esc_html_e( 'No Labels', 'jetpack' ); } } - foreach ( $terms_by_id as $term_id => $term ) { - $return[] = $term; + /** + * Update post meta with the price defined in meta box. + * + * @param int $post_id Post ID. + * @param string $price Price. + * + * @return int|bool + */ + public function set_price( $post_id = 0, $price = '' ) { + return update_post_meta( $post_id, 'nova_price', $price ); } - return $return; - } - - /** - * Get first menu taxonomy "leaf". - * - * @param int $post_id Post ID. - * - * @return bool|WP_Term|WP_Error|null - */ - public function get_menu_item_menu_leaf( $post_id ) { - // Get first menu taxonomy "leaf". - $term_ids = wp_get_object_terms( $post_id, self::MENU_TAX, array( 'fields' => 'ids' ) ); - - foreach ( $term_ids as $term_id ) { - $children = get_term_children( $term_id, self::MENU_TAX ); - if ( ! $children ) { - break; - } + /** + * Get the price of a menu item. + * + * @param int $post_id Post ID. + * + * @return bool|string + */ + public function get_price( $post_id = 0 ) { + return get_post_meta( $post_id, 'nova_price', true ); } - if ( ! isset( $term_id ) ) { - return false; + /** + * Echo the price of a menu item. + * + * @param int $post_id Post ID. + * + * @return void + */ + public function display_price( $post_id = 0 ) { + echo esc_html( $this->get_price( $post_id ) ); } - return get_term( $term_id, self::MENU_TAX ); - } - - /** - * Get a list of the labels linked to a menu item. - * - * @param int $post_id Post ID. - * - * @return void - */ - public function list_labels( $post_id = 0 ) { - $post = get_post( $post_id ); - echo get_the_term_list( $post->ID, self::MENU_ITEM_LABEL_TAX, '', _x( ', ', 'Nova label separator', 'jetpack' ), '' ); - } - - /** - * Get a list of the labels linked to a menu item, with links to manage them. - * - * @param int $post_id Post ID. - * - * @return void - */ - public function list_admin_labels( $post_id = 0 ) { - $post = get_post( $post_id ); - $labels = get_the_terms( $post->ID, self::MENU_ITEM_LABEL_TAX ); - if ( ! empty( $labels ) ) { - $out = array(); - foreach ( $labels as $label ) { - $out[] = sprintf( - '%s', - esc_url( - add_query_arg( - array( - 'post_type' => self::MENU_ITEM_POST_TYPE, - 'taxonomy' => self::MENU_ITEM_LABEL_TAX, - 'term' => $label->slug, - ), - 'edit.php' - ) - ), - esc_html( - sanitize_term_field( 'name', $label->name, $label->term_id, self::MENU_ITEM_LABEL_TAX, 'display' ) - ) - ); - } + /* Menu Item Loop Markup */ - echo implode( _x( ', ', 'Nova label separator', 'jetpack' ), $out ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- we build $out ourselves and escape things there. - } else { - esc_html_e( 'No Labels', 'jetpack' ); + /** + * Get markup for a menu item. + * Note: Does not support nested loops. + * + * @param null|string $field The field to get the value for. + * + * @return array + */ + public function get_menu_item_loop_markup( $field = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + return $this->menu_item_loop_markup; } - } - - /** - * Update post meta with the price defined in meta box. - * - * @param int $post_id Post ID. - * @param string $price Price. - * - * @return int|bool - */ - public function set_price( $post_id = 0, $price = '' ) { - return update_post_meta( $post_id, 'nova_price', $price ); - } - - /** - * Get the price of a menu item. - * - * @param int $post_id Post ID. - * - * @return bool|string - */ - public function get_price( $post_id = 0 ) { - return get_post_meta( $post_id, 'nova_price', true ); - } - - /** - * Echo the price of a menu item. - * - * @param int $post_id Post ID. - * - * @return void - */ - public function display_price( $post_id = 0 ) { - echo esc_html( $this->get_price( $post_id ) ); - } - - /* Menu Item Loop Markup */ - /** - * Get markup for a menu item. - * Note: Does not support nested loops. - * - * @param null|string $field The field to get the value for. - * - * @return array - */ - public function get_menu_item_loop_markup( $field = null ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable - return $this->menu_item_loop_markup; - } - - /** - * Sets up the loop markup. - * Attached to the 'template_include' *filter*, - * which fires only during a real blog view (not in admin, feeds, etc.) - * - * @param string $template Template File. - * - * @return string Template File. VERY Important. - */ - public function setup_menu_item_loop_markup__in_filter( $template ) { - add_action( 'loop_start', array( $this, 'start_menu_item_loop' ) ); - - return $template; - } + /** + * Sets up the loop markup. + * Attached to the 'template_include' *filter*, + * which fires only during a real blog view (not in admin, feeds, etc.) + * + * @param string $template Template File. + * + * @return string Template File. VERY Important. + */ + public function setup_menu_item_loop_markup__in_filter( $template ) { + add_action( 'loop_start', array( $this, 'start_menu_item_loop' ) ); - /** - * If the Query is a Menu Item Query, start outputing the Menu Item Loop Marku - * Attached to the 'loop_start' action. - * - * @param WP_Query $query Post query. - * - * @return void - */ - public function start_menu_item_loop( $query ) { - if ( ! $this->is_menu_item_query( $query ) ) { - return; + return $template; } - $this->menu_item_loop_last_term_id = false; - $this->menu_item_loop_current_term = false; + /** + * If the Query is a Menu Item Query, start outputing the Menu Item Loop Marku + * Attached to the 'loop_start' action. + * + * @param WP_Query $query Post query. + * + * @return void + */ + public function start_menu_item_loop( $query ) { + if ( ! $this->is_menu_item_query( $query ) ) { + return; + } - add_action( 'the_post', array( $this, 'menu_item_loop_each_post' ) ); - add_action( 'loop_end', array( $this, 'stop_menu_item_loop' ) ); - } + $this->menu_item_loop_last_term_id = false; + $this->menu_item_loop_current_term = false; - /** - * Outputs the Menu Item Loop Marku - * Attached to the 'the_post' action. - * - * @param WP_Post $post Post object. - * - * @return void - */ - public function menu_item_loop_each_post( $post ) { - $this->menu_item_loop_current_term = $this->get_menu_item_menu_leaf( $post->ID ); - - if ( - false === $this->menu_item_loop_current_term - || null === $this->menu_item_loop_current_term - || is_wp_error( $this->menu_item_loop_current_term ) - ) { - return; + add_action( 'the_post', array( $this, 'menu_item_loop_each_post' ) ); + add_action( 'loop_end', array( $this, 'stop_menu_item_loop' ) ); } - if ( false === $this->menu_item_loop_last_term_id ) { - // We're at the very beginning of the loop + /** + * Outputs the Menu Item Loop Marku + * Attached to the 'the_post' action. + * + * @param WP_Post $post Post object. + * + * @return void + */ + public function menu_item_loop_each_post( $post ) { + $this->menu_item_loop_current_term = $this->get_menu_item_menu_leaf( $post->ID ); + + if ( + false === $this->menu_item_loop_current_term + || null === $this->menu_item_loop_current_term + || is_wp_error( $this->menu_item_loop_current_term ) + ) { + return; + } - $this->menu_item_loop_open_element( 'menu' ); // Start a new menu section - $this->menu_item_loop_header(); // Output the menu's header - } elseif ( $this->menu_item_loop_last_term_id !== $this->menu_item_loop_current_term->term_id ) { - // We're not at the very beginning but still need to start a new menu section. End the previous menu section first. + if ( false === $this->menu_item_loop_last_term_id ) { + // We're at the very beginning of the loop - $this->menu_item_loop_close_element( 'menu' ); // End the previous menu section - $this->menu_item_loop_open_element( 'menu' ); // Start a new menu section - $this->menu_item_loop_header(); // Output the menu's header - } + $this->menu_item_loop_open_element( 'menu' ); // Start a new menu section + $this->menu_item_loop_header(); // Output the menu's header + } elseif ( $this->menu_item_loop_last_term_id !== $this->menu_item_loop_current_term->term_id ) { + // We're not at the very beginning but still need to start a new menu section. End the previous menu section first. - $this->menu_item_loop_last_term_id = $this->menu_item_loop_current_term->term_id; - } + $this->menu_item_loop_close_element( 'menu' ); // End the previous menu section + $this->menu_item_loop_open_element( 'menu' ); // Start a new menu section + $this->menu_item_loop_header(); // Output the menu's header + } - /** - * If the Query is a Menu Item Query, stop outputing the Menu Item Loop Marku - * Attached to the 'loop_end' action. - * - * @param WP_Query $query Post query. - * - * @return void - */ - public function stop_menu_item_loop( $query ) { - if ( ! $this->is_menu_item_query( $query ) ) { - return; + $this->menu_item_loop_last_term_id = $this->menu_item_loop_current_term->term_id; } - remove_action( 'the_post', array( $this, 'menu_item_loop_each_post' ) ); - remove_action( 'loop_start', array( $this, 'start_menu_item_loop' ) ); - remove_action( 'loop_end', array( $this, 'stop_menu_item_loop' ) ); + /** + * If the Query is a Menu Item Query, stop outputing the Menu Item Loop Marku + * Attached to the 'loop_end' action. + * + * @param WP_Query $query Post query. + * + * @return void + */ + public function stop_menu_item_loop( $query ) { + if ( ! $this->is_menu_item_query( $query ) ) { + return; + } - $this->menu_item_loop_close_element( 'menu' ); // End the last menu section - } + remove_action( 'the_post', array( $this, 'menu_item_loop_each_post' ) ); + remove_action( 'loop_start', array( $this, 'start_menu_item_loop' ) ); + remove_action( 'loop_end', array( $this, 'stop_menu_item_loop' ) ); - /** - * Outputs the Menu Group Header - * - * @return void - */ - public function menu_item_loop_header() { - $this->menu_item_loop_open_element( 'menu_header' ); - $this->menu_item_loop_open_element( 'menu_title' ); - echo esc_html( $this->menu_item_loop_current_term->name ); // @todo tax filter - $this->menu_item_loop_close_element( 'menu_title' ); - if ( $this->menu_item_loop_current_term->description ) : - $this->menu_item_loop_open_element( 'menu_description' ); - echo esc_html( $this->menu_item_loop_current_term->description ); // @todo kses, tax filter - $this->menu_item_loop_close_element( 'menu_description' ); - endif; - $this->menu_item_loop_close_element( 'menu_header' ); - } + $this->menu_item_loop_close_element( 'menu' ); // End the last menu section + } - /** - * Outputs a Menu Item Markup element opening tag - * - * @param string $field - Menu Item Markup settings field. - * - * @return void - */ - public function menu_item_loop_open_element( $field ) { - $markup = $this->get_menu_item_loop_markup(); /** - * Filter a menu item's element opening tag. + * Outputs the Menu Group Header * - * @module custom-content-types + * @return void + */ + public function menu_item_loop_header() { + $this->menu_item_loop_open_element( 'menu_header' ); + $this->menu_item_loop_open_element( 'menu_title' ); + echo esc_html( $this->menu_item_loop_current_term->name ); // @todo tax filter + $this->menu_item_loop_close_element( 'menu_title' ); + if ( $this->menu_item_loop_current_term->description ) : + $this->menu_item_loop_open_element( 'menu_description' ); + echo esc_html( $this->menu_item_loop_current_term->description ); // @todo kses, tax filter + $this->menu_item_loop_close_element( 'menu_description' ); + endif; + $this->menu_item_loop_close_element( 'menu_header' ); + } + + /** + * Outputs a Menu Item Markup element opening tag * - * @since 4.4.0 + * @param string $field - Menu Item Markup settings field. * - * @param string $tag Menu item's element opening tag. - * @param string $field Menu Item Markup settings field. - * @param array $markup Array of markup elements for the menu item. - * @param false|object $term Taxonomy term for current menu item. + * @return void */ - echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- it's escaped in menu_item_loop_class. - 'jetpack_nova_menu_item_loop_open_element', - '<' . tag_escape( $markup[ "{$field}_tag" ] ) . $this->menu_item_loop_class( $markup[ "{$field}_class" ] ) . ">\n", - $field, - $markup, - $this->menu_item_loop_current_term - ); - } + public function menu_item_loop_open_element( $field ) { + $markup = $this->get_menu_item_loop_markup(); + /** + * Filter a menu item's element opening tag. + * + * @module custom-content-types + * + * @since 4.4.0 + * + * @param string $tag Menu item's element opening tag. + * @param string $field Menu Item Markup settings field. + * @param array $markup Array of markup elements for the menu item. + * @param false|object $term Taxonomy term for current menu item. + */ + echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- it's escaped in menu_item_loop_class. + 'jetpack_nova_menu_item_loop_open_element', + '<' . tag_escape( $markup[ "{$field}_tag" ] ) . $this->menu_item_loop_class( $markup[ "{$field}_class" ] ) . ">\n", + $field, + $markup, + $this->menu_item_loop_current_term + ); + } - /** - * Outputs a Menu Item Markup element closing tag - * - * @param string $field - Menu Item Markup settings field. - * - * @return void - */ - public function menu_item_loop_close_element( $field ) { - $markup = $this->get_menu_item_loop_markup(); /** - * Filter a menu item's element closing tag. + * Outputs a Menu Item Markup element closing tag * - * @module custom-content-types + * @param string $field - Menu Item Markup settings field. * - * @since 4.4.0 - * - * @param string $tag Menu item's element closing tag. - * @param string $field Menu Item Markup settings field. - * @param array $markup Array of markup elements for the menu item. - * @param false|object $term Taxonomy term for current menu item. + * @return void */ - echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- tag_escape is used. - 'jetpack_nova_menu_item_loop_close_element', - '\n", - $field, - $markup, - $this->menu_item_loop_current_term - ); - } - - /** - * Returns a Menu Item Markup element's class attribute. - * - * @param string $class Class name. - * - * @return string HTML class attribute with leading whitespace. - */ - public function menu_item_loop_class( $class ) { - if ( ! $class ) { - return ''; + public function menu_item_loop_close_element( $field ) { + $markup = $this->get_menu_item_loop_markup(); + /** + * Filter a menu item's element closing tag. + * + * @module custom-content-types + * + * @since 4.4.0 + * + * @param string $tag Menu item's element closing tag. + * @param string $field Menu Item Markup settings field. + * @param array $markup Array of markup elements for the menu item. + * @param false|object $term Taxonomy term for current menu item. + */ + echo apply_filters( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- tag_escape is used. + 'jetpack_nova_menu_item_loop_close_element', + '\n", + $field, + $markup, + $this->menu_item_loop_current_term + ); } /** - * Filter a menu Item Markup element's class attribute. - * - * @module custom-content-types + * Returns a Menu Item Markup element's class attribute. * - * @since 4.4.0 + * @param string $class Class name. * - * @param string $tag Menu Item Markup element's class attribute. - * @param string $class Menu Item Class name. - * @param false|object $term Taxonomy term for current menu item. + * @return string HTML class attribute with leading whitespace. */ - return apply_filters( - 'jetpack_nova_menu_item_loop_class', - ' class="' . esc_attr( $class ) . '"', - $class, - $this->menu_item_loop_current_term - ); + public function menu_item_loop_class( $class ) { + if ( ! $class ) { + return ''; + } + + /** + * Filter a menu Item Markup element's class attribute. + * + * @module custom-content-types + * + * @since 4.4.0 + * + * @param string $tag Menu Item Markup element's class attribute. + * @param string $class Menu Item Class name. + * @param false|object $term Taxonomy term for current menu item. + */ + return apply_filters( + 'jetpack_nova_menu_item_loop_class', + ' class="' . esc_attr( $class ) . '"', + $class, + $this->menu_item_loop_current_term + ); + } } -} -add_action( 'init', array( 'Nova_Restaurant', 'init' ) ); + add_action( 'init', array( 'Nova_Restaurant', 'init' ) ); + +} \ No newline at end of file diff --git a/projects/plugins/jetpack/modules/shortcodes/instagram.php b/projects/plugins/jetpack/modules/shortcodes/instagram.php index ff002aeabd327..f6542354d3283 100644 --- a/projects/plugins/jetpack/modules/shortcodes/instagram.php +++ b/projects/plugins/jetpack/modules/shortcodes/instagram.php @@ -309,7 +309,7 @@ function jetpack_shortcode_instagram( $atts ) { } if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) { - $url_pattern = '#http(s?)://(www\.)?instagr(\.am|am\.com)/p/([^/?]+)#i'; + $url_pattern = '#http(s?)://(www\.)?instagr(\.am|am\.com)/(p|tv|reel)/([^/?]+)#i'; preg_match( $url_pattern, $atts['url'], $matches ); if ( ! $matches ) { return sprintf( diff --git a/projects/plugins/jetpack/package.json b/projects/plugins/jetpack/package.json index 948861ec400b5..7d28dc3057c4c 100644 --- a/projects/plugins/jetpack/package.json +++ b/projects/plugins/jetpack/package.json @@ -1,6 +1,6 @@ { "name": "Jetpack", - "version": "14.2.0-a.3", + "version": "14.2.0-a.5", "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 a43dca27e6b46..fe0e776446b89 100644 --- a/projects/plugins/jetpack/readme.txt +++ b/projects/plugins/jetpack/readme.txt @@ -326,18 +326,10 @@ Jetpack Backup can do a full website migration to a new host, migrate theme file == Changelog == -### 14.2-a.3 - 2024-12-16 -#### Enhancements -- Social: Improved Jetpack likes behavior for better theme integration if the post has likes. - -#### Improved compatibility -- Jetpack Testimonials: Ensure feature loads via the Classic Theme Helper package instead of the module. - +### 14.2-a.5 - 2024-12-23 #### Bug fixes -- Facebook Embeds: Add a white background to embeds to avoid transparent background interfering with readability. -- Form Block: fixed validation of URL input types to allow query strings. -- Import: Correctly setting the WP_IMPORTING constant when doing an import. -- SEO: Ensure that SEO fields are not visible when another SEO plugin is active. +- Shortcode embeds: Ensure Instagram reels are properly displayed in AMP views. +- Slideshow block: Fix block display when added within a Stack block. -------- diff --git a/projects/plugins/jetpack/sal/class.json-api-metadata.php b/projects/plugins/jetpack/sal/class.json-api-metadata.php index 801859a4263d3..2622d6b6b0ecd 100644 --- a/projects/plugins/jetpack/sal/class.json-api-metadata.php +++ b/projects/plugins/jetpack/sal/class.json-api-metadata.php @@ -59,6 +59,7 @@ public static function is_internal_only( $key ) { $whitelist = array( '_jetpack_newsletter_access', '_jetpack_newsletter_tier_id', + '_jetpack_dont_email_post_to_subs', ); if ( in_array( $key, $whitelist, true ) ) { diff --git a/projects/plugins/jetpack/tests/e2e/package.json b/projects/plugins/jetpack/tests/e2e/package.json index 5ef5ed5c030c1..3af8c58958fc4 100644 --- a/projects/plugins/jetpack/tests/e2e/package.json +++ b/projects/plugins/jetpack/tests/e2e/package.json @@ -34,7 +34,7 @@ "test-encrypt-config": "openssl enc -md sha1 -aes-256-cbc -pass env:CONFIG_KEY -in config/local.cjs -out ./config/encrypted.enc" }, "devDependencies": { - "@playwright/test": "1.45.1", + "@playwright/test": "1.48.2", "allure-playwright": "2.9.2", "config": "3.3.7", "jetpack-e2e-commons": "workspace:*" diff --git a/projects/plugins/jetpack/tests/php/sync/test_class.jetpack-sync-themes.php b/projects/plugins/jetpack/tests/php/sync/test_class.jetpack-sync-themes.php index 6a779671f29ec..690560a74bd3c 100644 --- a/projects/plugins/jetpack/tests/php/sync/test_class.jetpack-sync-themes.php +++ b/projects/plugins/jetpack/tests/php/sync/test_class.jetpack-sync-themes.php @@ -250,8 +250,11 @@ private function perform_network_enable_disable_assertions( $test_themes, $event } public function test_install_edit_delete_theme_sync() { - $theme_slug = 'itek'; - $theme_name = 'iTek'; + // This requires a theme that isn't directly available on WordPress.com: + // https://public-api.wordpress.com/rest/v1.2/themes/astra?http_envelope=1 + // Any theme with the "Community" badge in the WordPress.com theme search would work here. + $theme_slug = 'astra'; + $theme_name = 'Astra'; delete_theme( $theme_slug ); // Ensure theme is not lingering on file system $this->server_event_storage->reset(); @@ -295,7 +298,7 @@ public function test_install_edit_delete_theme_sync() { $event_data = $this->server_event_storage->get_most_recent_event( 'jetpack_deleted_theme' ); - $this->assertEquals( 'itek', $event_data->args[0] ); + $this->assertEquals( $theme_slug, $event_data->args[0] ); } public function test_update_themes_sync() { diff --git a/projects/plugins/mu-wpcom-plugin/changelog/prerelease#7 b/projects/plugins/mu-wpcom-plugin/changelog/prerelease#7 new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/mu-wpcom-plugin/changelog/prerelease#7 @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/mu-wpcom-plugin/changelog/update-synced_newspack_blocks b/projects/plugins/mu-wpcom-plugin/changelog/update-synced_newspack_blocks new file mode 100644 index 0000000000000..15206e680281d --- /dev/null +++ b/projects/plugins/mu-wpcom-plugin/changelog/update-synced_newspack_blocks @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Newspack Blocks: Updated to version 4.5.2. diff --git a/projects/plugins/mu-wpcom-plugin/composer.lock b/projects/plugins/mu-wpcom-plugin/composer.lock index 84180127a014a..7efdaf8df3709 100644 --- a/projects/plugins/mu-wpcom-plugin/composer.lock +++ b/projects/plugins/mu-wpcom-plugin/composer.lock @@ -1585,7 +1585,7 @@ "dist": { "type": "path", "url": "../../packages/sync", - "reference": "60ef0ba8f14f719aa2f69b33c2526fb066596627" + "reference": "ece2cb5be16c8bc399fb6681a61ffa42b42e3cf5" }, "require": { "automattic/jetpack-connection": "@dev", @@ -1618,7 +1618,7 @@ "link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "4.1.x-dev" + "dev-trunk": "4.2.x-dev" }, "dependencies": { "test-only": [ diff --git a/projects/plugins/protect/changelog/prerelease#10 b/projects/plugins/protect/changelog/prerelease#10 new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/protect/changelog/prerelease#10 @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/protect/composer.lock b/projects/plugins/protect/composer.lock index 559b7a0a02b50..19028ae6033fb 100644 --- a/projects/plugins/protect/composer.lock +++ b/projects/plugins/protect/composer.lock @@ -1125,7 +1125,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "0ab26c7e6e5f2c4a5f9038772514da7b408ff7bc" + "reference": "1356c22d5af932cc2f331bb5d2762a31609b14ed" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -1163,7 +1163,7 @@ "link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}" }, "branch-alias": { - "dev-trunk": "5.1.x-dev" + "dev-trunk": "5.2.x-dev" }, "version-constants": { "::PACKAGE_VERSION": "src/class-initializer.php" @@ -1732,7 +1732,7 @@ "dist": { "type": "path", "url": "../../packages/sync", - "reference": "60ef0ba8f14f719aa2f69b33c2526fb066596627" + "reference": "ece2cb5be16c8bc399fb6681a61ffa42b42e3cf5" }, "require": { "automattic/jetpack-connection": "@dev", @@ -1765,7 +1765,7 @@ "link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "4.1.x-dev" + "dev-trunk": "4.2.x-dev" }, "dependencies": { "test-only": [ diff --git a/projects/plugins/search/changelog/prerelease#17 b/projects/plugins/search/changelog/prerelease#17 new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/search/changelog/prerelease#17 @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/search/changelog/renovate-playwright-monorepo b/projects/plugins/search/changelog/renovate-playwright-monorepo new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/plugins/search/changelog/renovate-playwright-monorepo @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/search/composer.lock b/projects/plugins/search/composer.lock index 746ec148d328b..844752e99441f 100644 --- a/projects/plugins/search/composer.lock +++ b/projects/plugins/search/composer.lock @@ -1065,7 +1065,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "0ab26c7e6e5f2c4a5f9038772514da7b408ff7bc" + "reference": "1356c22d5af932cc2f331bb5d2762a31609b14ed" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -1103,7 +1103,7 @@ "link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}" }, "branch-alias": { - "dev-trunk": "5.1.x-dev" + "dev-trunk": "5.2.x-dev" }, "version-constants": { "::PACKAGE_VERSION": "src/class-initializer.php" @@ -1827,7 +1827,7 @@ "dist": { "type": "path", "url": "../../packages/sync", - "reference": "60ef0ba8f14f719aa2f69b33c2526fb066596627" + "reference": "ece2cb5be16c8bc399fb6681a61ffa42b42e3cf5" }, "require": { "automattic/jetpack-connection": "@dev", @@ -1860,7 +1860,7 @@ "link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "4.1.x-dev" + "dev-trunk": "4.2.x-dev" }, "dependencies": { "test-only": [ diff --git a/projects/plugins/search/tests/e2e/package.json b/projects/plugins/search/tests/e2e/package.json index c289436e0db42..6440f8675589e 100644 --- a/projects/plugins/search/tests/e2e/package.json +++ b/projects/plugins/search/tests/e2e/package.json @@ -26,7 +26,7 @@ "test:run": ". ./node_modules/jetpack-e2e-commons/bin/app-password.sh && playwright install && NODE_CONFIG_DIR='./config' ALLURE_RESULTS_DIR=./output/allure-results NODE_PATH=\"$PWD/node_modules\" playwright test --config=playwright.config.mjs" }, "devDependencies": { - "@playwright/test": "1.45.1", + "@playwright/test": "1.48.2", "allure-playwright": "2.9.2", "config": "3.3.7", "jetpack-e2e-commons": "workspace:*" diff --git a/projects/plugins/social/changelog/prerelease#10 b/projects/plugins/social/changelog/prerelease#10 new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/social/changelog/prerelease#10 @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/social/changelog/renovate-playwright-monorepo b/projects/plugins/social/changelog/renovate-playwright-monorepo new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/plugins/social/changelog/renovate-playwright-monorepo @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/social/composer.lock b/projects/plugins/social/composer.lock index 6091335a79fa0..5f2d3aa774d7c 100644 --- a/projects/plugins/social/composer.lock +++ b/projects/plugins/social/composer.lock @@ -1065,7 +1065,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "0ab26c7e6e5f2c4a5f9038772514da7b408ff7bc" + "reference": "1356c22d5af932cc2f331bb5d2762a31609b14ed" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -1103,7 +1103,7 @@ "link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}" }, "branch-alias": { - "dev-trunk": "5.1.x-dev" + "dev-trunk": "5.2.x-dev" }, "version-constants": { "::PACKAGE_VERSION": "src/class-initializer.php" @@ -1564,7 +1564,7 @@ "dist": { "type": "path", "url": "../../packages/publicize", - "reference": "ab76db662348ffb061c122ec58fb2b5f34c68bbc" + "reference": "0fa23aea603e89c8fce0e22ac921356ca84b1044" }, "require": { "automattic/jetpack-assets": "@dev", @@ -1593,7 +1593,7 @@ "link-template": "https://github.com/Automattic/jetpack-publicize/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "0.56.x-dev" + "dev-trunk": "0.57.x-dev" } }, "autoload": { @@ -1819,7 +1819,7 @@ "dist": { "type": "path", "url": "../../packages/sync", - "reference": "60ef0ba8f14f719aa2f69b33c2526fb066596627" + "reference": "ece2cb5be16c8bc399fb6681a61ffa42b42e3cf5" }, "require": { "automattic/jetpack-connection": "@dev", @@ -1852,7 +1852,7 @@ "link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "4.1.x-dev" + "dev-trunk": "4.2.x-dev" }, "dependencies": { "test-only": [ diff --git a/projects/plugins/social/tests/e2e/package.json b/projects/plugins/social/tests/e2e/package.json index dd164c8273870..b478405ef1752 100644 --- a/projects/plugins/social/tests/e2e/package.json +++ b/projects/plugins/social/tests/e2e/package.json @@ -30,7 +30,7 @@ "test:run": ". ./node_modules/jetpack-e2e-commons/bin/app-password.sh && playwright install && NODE_CONFIG_DIR='./config' ALLURE_RESULTS_DIR=./output/allure-results NODE_PATH=\"$PWD/node_modules\" playwright test --config=./playwright.config.mjs" }, "devDependencies": { - "@playwright/test": "1.45.1", + "@playwright/test": "1.48.2", "allure-playwright": "2.9.2", "config": "3.3.7", "jetpack-e2e-commons": "workspace:*" diff --git a/projects/plugins/starter-plugin/changelog/prerelease#2 b/projects/plugins/starter-plugin/changelog/prerelease#2 new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/starter-plugin/changelog/prerelease#2 @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/starter-plugin/changelog/renovate-playwright-monorepo b/projects/plugins/starter-plugin/changelog/renovate-playwright-monorepo new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/plugins/starter-plugin/changelog/renovate-playwright-monorepo @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/starter-plugin/composer.lock b/projects/plugins/starter-plugin/composer.lock index 7f001ae2e5af7..2b5cee01e55e6 100644 --- a/projects/plugins/starter-plugin/composer.lock +++ b/projects/plugins/starter-plugin/composer.lock @@ -1065,7 +1065,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "0ab26c7e6e5f2c4a5f9038772514da7b408ff7bc" + "reference": "1356c22d5af932cc2f331bb5d2762a31609b14ed" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -1103,7 +1103,7 @@ "link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}" }, "branch-alias": { - "dev-trunk": "5.1.x-dev" + "dev-trunk": "5.2.x-dev" }, "version-constants": { "::PACKAGE_VERSION": "src/class-initializer.php" @@ -1672,7 +1672,7 @@ "dist": { "type": "path", "url": "../../packages/sync", - "reference": "60ef0ba8f14f719aa2f69b33c2526fb066596627" + "reference": "ece2cb5be16c8bc399fb6681a61ffa42b42e3cf5" }, "require": { "automattic/jetpack-connection": "@dev", @@ -1705,7 +1705,7 @@ "link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "4.1.x-dev" + "dev-trunk": "4.2.x-dev" }, "dependencies": { "test-only": [ diff --git a/projects/plugins/starter-plugin/tests/e2e/package.json b/projects/plugins/starter-plugin/tests/e2e/package.json index 0d1fbd85b424e..68195c8b5978c 100644 --- a/projects/plugins/starter-plugin/tests/e2e/package.json +++ b/projects/plugins/starter-plugin/tests/e2e/package.json @@ -28,7 +28,7 @@ "test:run": ". ./node_modules/jetpack-e2e-commons/bin/app-password.sh && playwright install && NODE_CONFIG_DIR='./config' ALLURE_RESULTS_DIR=./output/allure-results NODE_PATH=\"$PWD/node_modules\" playwright test --config=./playwright.config.mjs" }, "devDependencies": { - "@playwright/test": "1.45.1", + "@playwright/test": "1.48.2", "allure-playwright": "2.9.2", "config": "3.3.7", "jetpack-e2e-commons": "workspace:*" diff --git a/projects/plugins/videopress/changelog/prerelease#17 b/projects/plugins/videopress/changelog/prerelease#17 new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/videopress/changelog/prerelease#17 @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/videopress/changelog/renovate-playwright-monorepo b/projects/plugins/videopress/changelog/renovate-playwright-monorepo new file mode 100644 index 0000000000000..c47cb18e82997 --- /dev/null +++ b/projects/plugins/videopress/changelog/renovate-playwright-monorepo @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Updated package dependencies. diff --git a/projects/plugins/videopress/composer.lock b/projects/plugins/videopress/composer.lock index 10ab6945ab542..bdfef9121ba65 100644 --- a/projects/plugins/videopress/composer.lock +++ b/projects/plugins/videopress/composer.lock @@ -1065,7 +1065,7 @@ "dist": { "type": "path", "url": "../../packages/my-jetpack", - "reference": "0ab26c7e6e5f2c4a5f9038772514da7b408ff7bc" + "reference": "1356c22d5af932cc2f331bb5d2762a31609b14ed" }, "require": { "automattic/jetpack-admin-ui": "@dev", @@ -1103,7 +1103,7 @@ "link-template": "https://github.com/Automattic/jetpack-my-jetpack/compare/${old}...${new}" }, "branch-alias": { - "dev-trunk": "5.1.x-dev" + "dev-trunk": "5.2.x-dev" }, "version-constants": { "::PACKAGE_VERSION": "src/class-initializer.php" @@ -1672,7 +1672,7 @@ "dist": { "type": "path", "url": "../../packages/sync", - "reference": "60ef0ba8f14f719aa2f69b33c2526fb066596627" + "reference": "ece2cb5be16c8bc399fb6681a61ffa42b42e3cf5" }, "require": { "automattic/jetpack-connection": "@dev", @@ -1705,7 +1705,7 @@ "link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "4.1.x-dev" + "dev-trunk": "4.2.x-dev" }, "dependencies": { "test-only": [ diff --git a/projects/plugins/videopress/tests/e2e/package.json b/projects/plugins/videopress/tests/e2e/package.json index 52351fe539eb1..c7c9f604db86b 100644 --- a/projects/plugins/videopress/tests/e2e/package.json +++ b/projects/plugins/videopress/tests/e2e/package.json @@ -28,7 +28,7 @@ "test:run": ". ./node_modules/jetpack-e2e-commons/bin/app-password.sh && playwright install && NODE_CONFIG_DIR='./config' ALLURE_RESULTS_DIR=./output/allure-results NODE_PATH=\"$PWD/node_modules\" playwright test --config=./playwright.config.mjs" }, "devDependencies": { - "@playwright/test": "1.45.1", + "@playwright/test": "1.48.2", "allure-playwright": "2.9.2", "config": "3.3.7", "jetpack-e2e-commons": "workspace:*" diff --git a/projects/plugins/wpcomsh/changelog/fix-launch-banner-in-iframe b/projects/plugins/wpcomsh/changelog/fix-launch-banner-in-iframe new file mode 100644 index 0000000000000..3d25f5d5bcb47 --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/fix-launch-banner-in-iframe @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Gutenberg 19.9: don't show launch banner when the site is previewed in Appearance -> Design diff --git a/projects/plugins/wpcomsh/changelog/prerelease#3 b/projects/plugins/wpcomsh/changelog/prerelease#3 new file mode 100644 index 0000000000000..9aa70e3ec1f75 --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/prerelease#3 @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Updated composer.lock. + + diff --git a/projects/plugins/wpcomsh/changelog/update-stats-wpcom-features b/projects/plugins/wpcomsh/changelog/update-stats-wpcom-features new file mode 100644 index 0000000000000..6ea8667450618 --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/update-stats-wpcom-features @@ -0,0 +1,5 @@ +Significance: patch +Type: added +Comment: Sync changes from D167647 + + diff --git a/projects/plugins/wpcomsh/changelog/update-synced_newspack_blocks b/projects/plugins/wpcomsh/changelog/update-synced_newspack_blocks new file mode 100644 index 0000000000000..15206e680281d --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/update-synced_newspack_blocks @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Newspack Blocks: Updated to version 4.5.2. diff --git a/projects/plugins/wpcomsh/composer.lock b/projects/plugins/wpcomsh/composer.lock index 5956b66464ed3..b1f66cadbde5d 100644 --- a/projects/plugins/wpcomsh/composer.lock +++ b/projects/plugins/wpcomsh/composer.lock @@ -1787,7 +1787,7 @@ "dist": { "type": "path", "url": "../../packages/sync", - "reference": "60ef0ba8f14f719aa2f69b33c2526fb066596627" + "reference": "ece2cb5be16c8bc399fb6681a61ffa42b42e3cf5" }, "require": { "automattic/jetpack-connection": "@dev", @@ -1820,7 +1820,7 @@ "link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}" }, "branch-alias": { - "dev-trunk": "4.1.x-dev" + "dev-trunk": "4.2.x-dev" }, "dependencies": { "test-only": [ diff --git a/projects/plugins/wpcomsh/private-site/logged-in-banner.php b/projects/plugins/wpcomsh/private-site/logged-in-banner.php index 28bd6cdeb0560..b478ff329c70c 100644 --- a/projects/plugins/wpcomsh/private-site/logged-in-banner.php +++ b/projects/plugins/wpcomsh/private-site/logged-in-banner.php @@ -199,6 +199,11 @@ function show_logged_in_banner() { el.style.display = 'none'; document.addEventListener( 'DOMContentLoaded', function() { + // Don't show the banner if the site is previewed via an iframe. + if ( window.top !== window.self ) { + return; + } + var el = document.querySelector( '#wpcom-launch-banner-wrapper' ); if ( el ) { el.style.display = null; diff --git a/projects/plugins/wpcomsh/wpcom-features/class-wpcom-features.php b/projects/plugins/wpcomsh/wpcom-features/class-wpcom-features.php index 94cb012d150b3..d6ee7cf00c60f 100644 --- a/projects/plugins/wpcomsh/wpcom-features/class-wpcom-features.php +++ b/projects/plugins/wpcomsh/wpcom-features/class-wpcom-features.php @@ -432,6 +432,8 @@ class WPCOM_Features { public const SPACE_UPGRADED_STORAGE = 'space-upgraded-storage'; public const SSH = 'ssh'; public const STAGING_SITES = 'staging-sites'; + public const STATS_BASIC_TEMP = 'stats-basic'; + public const STATS_COMMERCIAL = 'stats-commercial'; public const STATS_FREE = 'stats-free'; public const STATS_PAID = 'stats-paid'; public const STUDIO_SYNC = 'studio-sync'; @@ -1146,23 +1148,49 @@ class WPCOM_Features { self::WPCOM_ECOMMERCE_TRIAL_PLANS, ), ), + // Gives near full access to all stats features. All features except new commercial level modules like UTM and device stats. self::STATS_FREE => array( self::JETPACK_STATS_PLANS, self::JETPACK_GROWTH_PLANS, - ), - self::STATS_PAID => array( + // Provides legacy access for free and personal sites created before 2024-01-09. + // Can be removed once we are ready to paywall all free and/or old personal sites. array( 'before' => '2024-01-09', - self::WPCOM_PERSONAL_PLANS, + self::WPCOM_PERSONAL_AND_HIGHER_PLANS, + self::WPCOM_ALL_SITES, + ), + ), + // Provides limited stats for free and personal sites created before 2024-12-06. + // Features: Posts/Locations/Emails/File downloads + // Can be removed once we are ready to paywall all free sites. + self::STATS_BASIC_TEMP => array( + array( + 'before' => '2024-12-12', self::WPCOM_ALL_SITES, ), + ), + // Provides personal sites and higher access to all stats features except commercial level modules. + // Features: Posts/Locations/Emails/File downloads/Referrers/Clicks + self::STATS_PAID => array( + self::WPCOM_PERSONAL_AND_HIGHER_PLANS, self::WP_P2_PLUS_MONTHLY, - self::WPCOM_PREMIUM_AND_HIGHER_PLANS, self::JETPACK_STATS_PWYW, self::JETPACK_STATS_MONTHLY, self::JETPACK_STATS_BI_YEARLY, self::JETPACK_STATS_YEARLY, self::JETPACK_COMPLETE_PLANS, + self::JETPACK_BUSINESS_PLANS, + self::JETPACK_GROWTH_PLANS, + ), + // Provides premium sites and higher access to all stats features. + // Features: STATS_PAID + UTM & Devices modules + self::STATS_COMMERCIAL => array( + self::WPCOM_PREMIUM_AND_HIGHER_PLANS, + self::JETPACK_STATS_MONTHLY, + self::JETPACK_STATS_BI_YEARLY, + self::JETPACK_STATS_YEARLY, + self::JETPACK_COMPLETE_PLANS, + self::JETPACK_BUSINESS_PLANS, self::JETPACK_GROWTH_PLANS, ), self::STUDIO_SYNC => array( diff --git a/tools/e2e-commons/package.json b/tools/e2e-commons/package.json index 87de25ba9f28f..69952b4a03e72 100644 --- a/tools/e2e-commons/package.json +++ b/tools/e2e-commons/package.json @@ -19,7 +19,7 @@ "jetpack-connect": "node bin/e2e-jetpack-connector.js" }, "devDependencies": { - "@playwright/test": "1.45.1", + "@playwright/test": "1.48.2", "@slack/web-api": "7.3.2", "@types/lodash-es": "4.17.12", "allure-playwright": "2.9.2",