-
+ { icon &&
}
{ children }
{ isLoading &&
}
{ action &&
{ action }
}
diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/components/banner/style.scss b/projects/packages/videopress/src/client/block-editor/blocks/video/components/banner/style.scss
index 3cf78ee0e8ce9..8964f84b82a95 100644
--- a/projects/packages/videopress/src/client/block-editor/blocks/video/components/banner/style.scss
+++ b/projects/packages/videopress/src/client/block-editor/blocks/video/components/banner/style.scss
@@ -1,15 +1,39 @@
.block-banner {
display: flex;
- height: 48px;
+ justify-content: space-between;
font-size: 14px;
align-self: center;
align-items: center;
- background: #F6F7F7;
- padding: 0 16px;
+ background: black;
+ border-radius: 2px;
+ padding: 0 20px;
+ box-shadow: 0 0 1px inset white;
.block-banner__content {
- color: #01283d;
- flex-grow: 2;
- margin: 0 8px;
+ color: white;
+ margin: 10px 10px 10px 0;
+ }
+
+ .block-banner__action {
+ padding: 0;
+
+ .components-button.is-primary {
+ background: white;
+ color: black;
+ font-weight: 600;
+ font-size: 14px;
+ padding: 4px 8px;
+ height: 28px;
+ margin: 8px 0 8px auto;
+
+ &:hover:not(:disabled) {
+ background: #F6F7F7;
+ }
+ }
+
+ .components-button.is-primary.is-busy {
+ background-size: 100px 100%;
+ background-image: linear-gradient(-45deg, #e34c84 28%, #ab235a 28%, #ab235a 72%, #e34c84 72%);
+ }
}
}
diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/components/videopress-uploader/index.js b/projects/packages/videopress/src/client/block-editor/blocks/video/components/videopress-uploader/index.js
index e7c2b99039535..660e7833e7721 100644
--- a/projects/packages/videopress/src/client/block-editor/blocks/video/components/videopress-uploader/index.js
+++ b/projects/packages/videopress/src/client/block-editor/blocks/video/components/videopress-uploader/index.js
@@ -10,6 +10,7 @@ import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
+import { usePermission } from '../../../../../admin/hooks/use-permission';
import useResumableUploader from '../../../../../hooks/use-resumable-uploader';
import { uploadFromLibrary } from '../../../../../hooks/use-uploader';
import { buildVideoPressURL, pickVideoBlockAttributesFromUrl } from '../../../../../lib/url';
@@ -29,12 +30,15 @@ const VideoPressUploader = ( {
fileToUpload,
isReplacing,
onReplaceCancel,
+ isActive,
} ) => {
const [ uploadPaused, setUploadPaused ] = useState( false );
const [ uploadedVideoData, setUploadedVideoData ] = useState( false );
const [ isUploadingInProgress, setIsUploadingInProgress ] = useState( false );
const [ isVerifyingLocalMedia, setIsVerifyingLocalMedia ] = useState( false );
+ const { hasConnectedOwner } = usePermission();
+
/*
* When the file to upload is set, start the upload process
* just after the component is mounted.
@@ -314,6 +318,24 @@ const VideoPressUploader = ( {
);
}
+ if ( ! isActive ) {
+ const needsConnectionText = __(
+ 'Connect your WordPress.com account to enable high-quality, ad-free video.',
+ 'jetpack-videopress-pkg'
+ );
+
+ const needsActivationText = __(
+ 'Activate the VideoPress module to enable high-quality, ad-free video.',
+ 'jetpack-videopress-pkg'
+ );
+
+ return (
+
+ { ! hasConnectedOwner ? needsConnectionText : needsActivationText }
+
+ );
+ }
+
// Default view to select file to upload
return (
{
if ( ! errorMessage ) {
@@ -87,6 +89,7 @@ export const PlaceholderWrapper = withNotices( function ( {
label={ title }
instructions={ disableInstructions ? null : instructions }
notices={ noticeUI }
+ className={ className }
>
{ children }
@@ -148,6 +151,7 @@ export default function VideoPressEdit( {
// Get the redirect URI for the connection flow.
const [ isRedirectingToMyJetpack, setIsRedirectingToMyJetpack ] = useState( false );
+ const { hasConnectedOwner } = usePermission();
// Detect if the chapter file is auto-generated.
const chapter = tracks?.filter( track => track.kind === 'chapters' )?.[ 0 ];
@@ -395,15 +399,15 @@ export default function VideoPressEdit( {
<>
{
setIsRedirectingToMyJetpack( true );
- if ( ! isStandalonePluginActive ) {
- return ( window.location.href = jetpackVideoPressSettingUrl );
+ if ( ! hasConnectedOwner ) {
+ return ( window.location.href = myJetpackConnectUrl );
}
- window.location.href = myJetpackConnectUrl;
+ window.location.href = jetpackVideoPressSettingUrl;
} }
/>
@@ -414,6 +418,7 @@ export default function VideoPressEdit( {
fileToUpload={ fileToUpload }
isReplacing={ isReplacingFile?.isReplacing }
onReplaceCancel={ cancelReplacingVideoFile }
+ isActive={ isActive }
/>
>
@@ -592,15 +597,15 @@ export default function VideoPressEdit( {
{
setIsRedirectingToMyJetpack( true );
- if ( ! isStandalonePluginActive ) {
- return ( window.location.href = jetpackVideoPressSettingUrl );
+ if ( ! hasConnectedOwner ) {
+ return ( window.location.href = myJetpackConnectUrl );
}
- window.location.href = myJetpackConnectUrl;
+ window.location.href = jetpackVideoPressSettingUrl;
} }
/>
diff --git a/projects/packages/videopress/src/client/block-editor/blocks/video/editor.scss b/projects/packages/videopress/src/client/block-editor/blocks/video/editor.scss
index 6dfbaa03c6fb1..a75c2c96c1592 100644
--- a/projects/packages/videopress/src/client/block-editor/blocks/video/editor.scss
+++ b/projects/packages/videopress/src/client/block-editor/blocks/video/editor.scss
@@ -52,6 +52,10 @@
}
}
+.components-placeholder.disabled {
+ min-height: 0;
+}
+
.loading-wrapper {
display: flex;
width: 100%;
diff --git a/projects/packages/waf/changelog/add-waf-body-processor b/projects/packages/waf/changelog/add-waf-body-processor
new file mode 100644
index 0000000000000..df675355a4f53
--- /dev/null
+++ b/projects/packages/waf/changelog/add-waf-body-processor
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Firewall Runtime: Added support for rule files to specify body parser type.
diff --git a/projects/packages/waf/src/class-waf-request.php b/projects/packages/waf/src/class-waf-request.php
index 5017a2e25f46c..bbb8bcf3e17f9 100644
--- a/projects/packages/waf/src/class-waf-request.php
+++ b/projects/packages/waf/src/class-waf-request.php
@@ -328,28 +328,59 @@ public function get_get_vars() {
return flatten_array( $_GET );
}
+ /**
+ * Returns the POST variables from a JSON body
+ *
+ * @return array{string, scalar}[]
+ */
+ private function get_json_post_vars() {
+ $decoded_json = json_decode( $this->get_body(), true ) ?? array();
+ return flatten_array( $decoded_json, 'json', true );
+ }
+
+ /**
+ * Returns the POST variables from a urlencoded body
+ *
+ * @return array{string, scalar}[]
+ */
+ private function get_urlencoded_post_vars() {
+ parse_str( $this->get_body(), $params );
+ return flatten_array( $params );
+ }
+
/**
* Returns the POST variables
*
+ * @param string $body_processor Manually specifiy the method to use to process the body. Options are 'URLENCODED' and 'JSON'.
+ *
* @return array{string, scalar}[]
*/
- public function get_post_vars() {
+ public function get_post_vars( string $body_processor = '' ) {
$content_type = $this->get_header( 'content-type' );
+
+ // If the body processor is specified by the rules file, trust it.
+ if ( 'URLENCODED' === $body_processor ) {
+ return $this->get_urlencoded_post_vars();
+ }
+ if ( 'JSON' === $body_processor ) {
+ return $this->get_json_post_vars();
+ }
+
+ // Otherwise, use $_POST if it's not empty.
if ( ! empty( $_POST ) ) {
- // If $_POST is populated, use it.
return flatten_array( $_POST );
- } elseif ( strpos( $content_type, 'application/json' ) !== false ) {
- // Attempt to decode JSON requests.
- $decoded_json = json_decode( $this->get_body(), true ) ?? array();
- return flatten_array( $decoded_json, 'json', true );
- } elseif ( strpos( $content_type, 'application/x-www-form-urlencoded' ) !== false ) {
- // Attempt to decode url-encoded data
- parse_str( $this->get_body(), $params );
- return flatten_array( $params );
- } else {
- // Don't try to parse any other content types
- return array();
}
+
+ // Lastly, try to parse the body based on the content type.
+ if ( strpos( $content_type, 'application/json' ) !== false ) {
+ return $this->get_json_post_vars();
+ }
+ if ( strpos( $content_type, 'application/x-www-form-urlencoded' ) !== false ) {
+ return $this->get_urlencoded_post_vars();
+ }
+
+ // Don't try to parse any other content types.
+ return array();
}
/**
diff --git a/projects/packages/waf/src/class-waf-runtime.php b/projects/packages/waf/src/class-waf-runtime.php
index 394d46aa5a6f1..19300ea804582 100644
--- a/projects/packages/waf/src/class-waf-runtime.php
+++ b/projects/packages/waf/src/class-waf-runtime.php
@@ -38,6 +38,14 @@ class Waf_Runtime {
*/
const NORMALIZE_ARRAY_MATCH_VALUES = 2;
+ /**
+ * The version of this runtime class. Used by rule files to ensure compatibility.
+ *
+ * @since $$next-version$$
+ *
+ * @var int
+ */
+ public $version = 1;
/**
* Last rule.
*
@@ -68,6 +76,12 @@ class Waf_Runtime {
* @var string
*/
public $matched_var_name = '';
+ /**
+ * Body Processor.
+ *
+ * @var string 'URLENCODED' | 'JSON' | ''
+ */
+ private $body_processor = '';
/**
* State.
@@ -438,7 +452,7 @@ public function meta( $key ) {
$value = $this->args_names( $this->meta( 'args_get' ) );
break;
case 'args_post':
- $value = $this->request->get_post_vars();
+ $value = $this->request->get_post_vars( $this->get_body_processor() );
break;
case 'args_post_names':
$value = $this->args_names( $this->meta( 'args_post' ) );
@@ -488,6 +502,28 @@ private function state_values( $prefix ) {
return $output;
}
+ /**
+ * Get the body processor.
+ *
+ * @return string
+ */
+ private function get_body_processor() {
+ return $this->body_processor;
+ }
+
+ /**
+ * Set the body processor.
+ *
+ * @param string $processor Processor to set. Either 'URLENCODED' or 'JSON'.
+ *
+ * @return void
+ */
+ public function set_body_processor( $processor ) {
+ if ( $processor === 'URLENCODED' || $processor === 'JSON' ) {
+ $this->body_processor = $processor;
+ }
+ }
+
/**
* Change a string to all lowercase and replace spaces and underscores with dashes.
*
diff --git a/projects/packages/waf/tests/php/unit/test-waf-request.php b/projects/packages/waf/tests/php/unit/test-waf-request.php
index ce409a4b9e5ac..01c68e5aa5bea 100644
--- a/projects/packages/waf/tests/php/unit/test-waf-request.php
+++ b/projects/packages/waf/tests/php/unit/test-waf-request.php
@@ -301,6 +301,64 @@ public function testGetVarsPost() {
$_POST = array();
}
+ /**
+ * Test that the Waf_Request class returns POST-ed data correctly decoded from JSON via Waf_Request::get_post_vars().
+ */
+ public function testGetVarsPostWithJsonBodyProcessor() {
+ $_SERVER['CONTENT_TYPE'] = 'irrelevant';
+
+ $request = $this->mock_request(
+ array(
+ 'body' => json_encode(
+ array(
+ 'str' => 'value',
+ 'arr' => array( 'a', 'b', 'c' ),
+ 'obj' => (object) array( 'foo' => 'bar' ),
+ )
+ ),
+ )
+ );
+ $value = $request->get_post_vars( 'JSON' );
+ $this->assertIsArray( $value );
+ $this->assertContains( array( 'json.str', 'value' ), $value );
+ $this->assertContains( array( 'json.arr.0', 'a' ), $value );
+ $this->assertContains( array( 'json.arr.1', 'b' ), $value );
+ $this->assertContains( array( 'json.arr.2', 'c' ), $value );
+ $this->assertContains( array( 'json.obj.foo', 'bar' ), $value );
+
+ unset( $_SERVER['CONTENT_TYPE'] );
+ }
+
+ /**
+ * Test that the Waf_Request class returns POST-ed data correctly decoded from URLENCODED body via Waf_Request::get_post_vars().
+ */
+ public function testGetVarsPostWithUrlencodedBodyProcessor() {
+ $_SERVER['CONTENT_TYPE'] = 'irrelevant';
+
+ $request = $this->mock_request(
+ array(
+ 'body' => (
+ http_build_query(
+ array(
+ 'str' => 'value',
+ 'arr' => array( 'a', 'b', 'c' ),
+ 'obj' => (object) array( 'foo' => 'bar' ),
+ )
+ )
+ ),
+ )
+ );
+ $value = $request->get_post_vars( 'URLENCODED' );
+ $this->assertIsArray( $value );
+ $this->assertContains( array( 'str', 'value' ), $value );
+ $this->assertContains( array( 'arr[0]', 'a' ), $value );
+ $this->assertContains( array( 'arr[1]', 'b' ), $value );
+ $this->assertContains( array( 'arr[2]', 'c' ), $value );
+ $this->assertContains( array( 'obj[foo]', 'bar' ), $value );
+
+ unset( $_SERVER['CONTENT_TYPE'] );
+ }
+
/**
* Test that the Waf_Request class returns POST-ed data correctly decoded from JSON via Waf_Request::get_post_vars().
*/
diff --git a/projects/packages/wordads/changelog/renovate-photon-4.x b/projects/packages/wordads/changelog/renovate-photon-4.x
new file mode 100644
index 0000000000000..c47cb18e82997
--- /dev/null
+++ b/projects/packages/wordads/changelog/renovate-photon-4.x
@@ -0,0 +1,4 @@
+Significance: patch
+Type: changed
+
+Updated package dependencies.
diff --git a/projects/packages/wordads/package.json b/projects/packages/wordads/package.json
index b83d2ab4bfaac..1182205ae4698 100644
--- a/projects/packages/wordads/package.json
+++ b/projects/packages/wordads/package.json
@@ -45,7 +45,7 @@
"clsx": "2.1.1",
"fast-json-stable-stringify": "2.1.0",
"lodash": "4.17.21",
- "photon": "4.0.0",
+ "photon": "4.1.1",
"preact": "10.22.1",
"prop-types": "15.7.2",
"q-flat": "1.0.7",
diff --git a/projects/plugins/boost/app/admin/class-admin.php b/projects/plugins/boost/app/admin/class-admin.php
index 81175df6157ec..522692c7f9513 100644
--- a/projects/plugins/boost/app/admin/class-admin.php
+++ b/projects/plugins/boost/app/admin/class-admin.php
@@ -14,9 +14,7 @@
use Automattic\Jetpack_Boost\Lib\Analytics;
use Automattic\Jetpack_Boost\Lib\Environment_Change_Detector;
use Automattic\Jetpack_Boost\Lib\Premium_Features;
-use Automattic\Jetpack_Boost\Modules\Modules_Index;
use Automattic\Jetpack_Boost\Modules\Modules_Setup;
-use Automattic\Jetpack_Boost\Modules\Optimizations\Critical_CSS\Critical_CSS;
class Admin {
/**
@@ -83,17 +81,6 @@ public function enqueue_scripts() {
*/
$internal_path = apply_filters( 'jetpack_boost_asset_internal_path', 'app/assets/dist/' );
- $critical_css_gen_handle = 'jetpack-boost-critical-css-gen';
-
- Assets::register_script(
- $critical_css_gen_handle,
- $internal_path . 'critical-css-gen.js',
- JETPACK_BOOST_PATH,
- array(
- 'in_footer' => true,
- )
- );
-
$admin_js_handle = 'jetpack-boost-admin';
$admin_js_dependencies = array(
@@ -101,11 +88,6 @@ public function enqueue_scripts() {
'wp-components',
);
- // Enqueue the critical CSS generator script if Critical CSS is available.
- if ( ( new Modules_Index() )->is_module_available( Critical_CSS::get_slug() ) ) {
- $admin_js_dependencies[] = $critical_css_gen_handle;
- }
-
Assets::register_script(
$admin_js_handle,
$internal_path . 'jetpack-boost.js',
diff --git a/projects/plugins/boost/app/assets/src/js/features/critical-css/lib/generate-critical-css.ts b/projects/plugins/boost/app/assets/src/js/features/critical-css/lib/generate-critical-css.ts
index 94bddd77edbd9..d53a5d93d3453 100644
--- a/projects/plugins/boost/app/assets/src/js/features/critical-css/lib/generate-critical-css.ts
+++ b/projects/plugins/boost/app/assets/src/js/features/critical-css/lib/generate-critical-css.ts
@@ -6,7 +6,6 @@ import { logPreCriticalCSSGeneration } from '$lib/utils/console';
import { isSameOrigin } from '$lib/utils/is-same-origin';
import { prepareAdminAjaxRequest } from '$lib/utils/make-admin-ajax-request';
import { standardizeError } from '$lib/utils/standardize-error';
-import { SuccessTargetError } from 'jetpack-boost-critical-css-gen';
type Viewport = {
width: number;
@@ -47,6 +46,12 @@ interface GeneratorCallbacks extends ProviderCallbacks {
onFinished: () => void; // Called when the generator is finished, regardless of success or failure.
}
+async function criticalCssGenerator() {
+ return await import(
+ /* webpackChunkName: "jetpack-critical-css-gen" */ '@automattic/jetpack-critical-css-gen'
+ );
+}
+
/**
* Run the local Critical CSS Generator for a set of providers, if it is not already running.
* The result of generation will not be returned to the caller; it will be sent to the given
@@ -137,10 +142,11 @@ async function generateCriticalCss(
* @param {Object} requestGetParameters - GET parameters to include with each request.
* @param {string} proxyNonce - Nonce to use when proxying CSS requests.
*/
-function createBrowserInterface(
+async function createBrowserInterface(
requestGetParameters: Record< string, string >,
proxyNonce: string
) {
+ const CriticalCSSGenerator = await criticalCssGenerator();
return new ( class extends CriticalCSSGenerator.BrowserInterfaceIframe {
constructor() {
super( {
@@ -164,10 +170,6 @@ function createBrowserInterface(
} )();
}
-function isSuccessTargetError( err: unknown ): err is SuccessTargetError {
- return err instanceof Error && 'isSuccessTargetError' in err;
-}
-
/**
* Generate Critical CSS for the specified Provider Keys, sending each block
* to the server. Throws on error or cancellation.
@@ -187,6 +189,7 @@ async function generateForKeys(
callbacks: ProviderCallbacks,
signal: AbortSignal
): Promise< void > {
+ const CriticalCSSGenerator = await criticalCssGenerator();
try {
CriticalCSSGeneratorSchema.parse( CriticalCSSGenerator );
} catch ( err ) {
@@ -194,6 +197,12 @@ async function generateForKeys(
throw new Error( 'css-gen-library-failure' );
}
+ function isSuccessTargetError(
+ err: unknown
+ ): err is InstanceType< typeof CriticalCSSGenerator.SuccessTargetError > {
+ return err instanceof CriticalCSSGenerator.SuccessTargetError;
+ }
+
// eslint-disable-next-line @wordpress/no-unused-vars-before-return
const startTime = Date.now();
let totalSize = 0;
@@ -209,7 +218,7 @@ async function generateForKeys(
try {
const [ css ] = await CriticalCSSGenerator.generateCriticalCSS( {
- browserInterface: createBrowserInterface( requestGetParameters, proxyNonce ),
+ browserInterface: await createBrowserInterface( requestGetParameters, proxyNonce ),
urls,
viewports,
progressCallback: ( step: number, total: number ) => {
@@ -336,7 +345,7 @@ function keepAtRule( name: string ): boolean {
/**
* Helper method to filter out properties that we don't want.
* Note this function is used as a filter in the generateCriticalCSS function
- * in the jetpack-boost-critical-css-gen library (https://github.com/Automattic/jetpack-boost-critical-css-gen).
+ * in the @automattic/jetpack-critical-css-gen library (https://github.com/Automattic/jetpack-critical-css-gen).
*
* This function has a value parameter which is not being used here but other implementations of this
* helper function for the library may require the value parameter for filtering.
@@ -355,7 +364,7 @@ function keepProperty( name: string, _value: string ): boolean {
* Function to verify that a specific page is valid to run the Critical CSS process on it.
*
* Note that this function is used as a callback in the generateCriticalCSS function
- * in the jetpack-boost-critical-css-gen library (https://github.com/Automattic/jetpack-boost-critical-css-gen).
+ * in the @automattic/jetpack-critical-css-gen library (https://github.com/Automattic/jetpack-critical-css-gen).
*
* This function has a url and innerWindow parameters which are not being used here but this method
* is called with URL and InnerWindow in that library to offer flexibility of the verification for other implementation.
diff --git a/projects/plugins/boost/app/assets/src/js/global.d.ts b/projects/plugins/boost/app/assets/src/js/global.d.ts
index 2aecfb42192d7..b2f267885c8da 100644
--- a/projects/plugins/boost/app/assets/src/js/global.d.ts
+++ b/projects/plugins/boost/app/assets/src/js/global.d.ts
@@ -2,8 +2,6 @@
* Type definitions for the global namespace. i.e.: things we expect to find in window.
*/
-import type { BrowserInterfaceIframe, generateCriticalCSS } from 'jetpack-boost-critical-css-gen';
-
//
declare global {
@@ -38,12 +36,6 @@ declare global {
};
};
- // Critical CSS Generator library.
- const CriticalCSSGenerator: {
- generateCriticalCSS: typeof generateCriticalCSS;
- BrowserInterfaceIframe: typeof BrowserInterfaceIframe;
- };
-
const jpTracksAJAX: {
record_ajax_event: (
eventName: string,
diff --git a/projects/plugins/boost/app/modules/image-guide/Image_Guide_Proxy.php b/projects/plugins/boost/app/modules/image-guide/Image_Guide_Proxy.php
index 4d6ef31835cb7..8b08c45ca41b3 100644
--- a/projects/plugins/boost/app/modules/image-guide/Image_Guide_Proxy.php
+++ b/projects/plugins/boost/app/modules/image-guide/Image_Guide_Proxy.php
@@ -36,10 +36,8 @@ public static function handle_proxy() {
wp_send_json_error( 'Invalid URL', 400 );
}
- $photon_url = Image_CDN_Core::cdn_url( $proxy_url );
- $photon_url_domain = wp_parse_url( $photon_url, PHP_URL_HOST );
- $photon_domain = wp_parse_url( apply_filters( 'jetpack_photon_domain', 'https://i0.wp.com' ), PHP_URL_HOST );
- if ( $photon_url_domain !== $photon_domain ) {
+ $photon_url = Image_CDN_Core::cdn_url( $proxy_url );
+ if ( ! Image_CDN_Core::is_cdn_url( $proxy_url ) ) {
wp_send_json_error( 'Failed to proxy the image.', 400 );
}
diff --git a/projects/plugins/boost/app/modules/optimizations/render-blocking-js/class-render-blocking-js.php b/projects/plugins/boost/app/modules/optimizations/render-blocking-js/class-render-blocking-js.php
index 75ef96c69a6f1..169ce01ee91c9 100644
--- a/projects/plugins/boost/app/modules/optimizations/render-blocking-js/class-render-blocking-js.php
+++ b/projects/plugins/boost/app/modules/optimizations/render-blocking-js/class-render-blocking-js.php
@@ -68,6 +68,11 @@ public function setup() {
$this->ignore_attribute = apply_filters( 'jetpack_boost_render_blocking_js_ignore_attribute', 'data-jetpack-boost' );
add_action( 'template_redirect', array( $this, 'start_output_filtering' ), -999999 );
+
+ /**
+ * Shortcodes can sometimes output script to embed widget. It's safer to ignore them.
+ */
+ add_filter( 'do_shortcode_tag', array( $this, 'add_ignore_attribute' ) );
}
/**
@@ -256,7 +261,7 @@ protected function ignore_exclusion_scripts( $buffer ) {
return preg_replace_callback(
$exclusions,
function ( $script_match ) {
- return str_replace( '