diff --git a/projects/packages/external-media/changelog/feat-external-media-import-button b/projects/packages/external-media/changelog/feat-external-media-import-button new file mode 100644 index 0000000000000..628b477a5f482 --- /dev/null +++ b/projects/packages/external-media/changelog/feat-external-media-import-button @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +External Media: Add Import button in Media Library diff --git a/projects/packages/external-media/changelog/feat-external-media-track-events b/projects/packages/external-media/changelog/feat-external-media-track-events new file mode 100644 index 0000000000000..9c86bd2e0a665 --- /dev/null +++ b/projects/packages/external-media/changelog/feat-external-media-track-events @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +External Media: Add track events to the Import page and modal diff --git a/projects/packages/external-media/changelog/media-library-tracks-2 b/projects/packages/external-media/changelog/media-library-tracks-2 new file mode 100644 index 0000000000000..5c6cbae94d559 --- /dev/null +++ b/projects/packages/external-media/changelog/media-library-tracks-2 @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Media Library: add track event to the Import Media button diff --git a/projects/packages/external-media/changelog/media-tracks b/projects/packages/external-media/changelog/media-tracks new file mode 100644 index 0000000000000..48205a668b670 --- /dev/null +++ b/projects/packages/external-media/changelog/media-tracks @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Media Library: add track events for upload from URL feature diff --git a/projects/packages/external-media/src/features/admin/external-media-import-button.js b/projects/packages/external-media/src/features/admin/external-media-import-button.js new file mode 100644 index 0000000000000..22145691a40f7 --- /dev/null +++ b/projects/packages/external-media/src/features/admin/external-media-import-button.js @@ -0,0 +1,29 @@ +import jetpackAnalytics from '@automattic/jetpack-analytics'; +import { __ } from '@wordpress/i18n'; + +document.addEventListener( 'DOMContentLoaded', function () { + const addNewButton = document.querySelector( 'a.page-title-action' ); + if ( addNewButton ) { + const buttonContainer = document.createElement( 'div' ); + buttonContainer.className = 'wpcom-media-library-action-buttons'; + + const importButton = document.createElement( 'a' ); + importButton.className = 'button'; + importButton.role = 'button'; + importButton.innerHTML = __( 'Import Media', 'jetpack-external-media' ); + importButton.href = window.JETPACK_EXTERNAL_MEDIA_IMPORT_BUTTON?.href; + importButton.onclick = function () { + jetpackAnalytics.tracks.recordEvent( 'jetpack_external_media_import_media_button_click', { + page: 'media-library', + } ); + }; + + const parentNode = addNewButton.parentNode; + const nextSibling = addNewButton.nextSibling; + + buttonContainer.appendChild( addNewButton ); + buttonContainer.appendChild( importButton ); + + parentNode.insertBefore( buttonContainer, nextSibling ); + } +} ); diff --git a/projects/packages/external-media/src/features/admin/external-media-import-button.scss b/projects/packages/external-media/src/features/admin/external-media-import-button.scss new file mode 100644 index 0000000000000..df9db337f0ab9 --- /dev/null +++ b/projects/packages/external-media/src/features/admin/external-media-import-button.scss @@ -0,0 +1,12 @@ +.wpcom-media-library-action-buttons { + display: inline-flex; + flex-wrap: wrap; + gap: 4px; + + > a { + position: relative; + top: -3px; + margin-left: 0; + vertical-align: baseline; + } +} diff --git a/projects/packages/external-media/src/features/admin/external-media-import.js b/projects/packages/external-media/src/features/admin/external-media-import.js index d6feba02f7716..f1abfe45f3722 100644 --- a/projects/packages/external-media/src/features/admin/external-media-import.js +++ b/projects/packages/external-media/src/features/admin/external-media-import.js @@ -1,3 +1,4 @@ +import { useAnalytics } from '@automattic/jetpack-shared-extension-utils'; import { sprintf, __ } from '@wordpress/i18n'; import { useState, useEffect } from 'react'; import { createPortal } from 'react-dom'; @@ -22,6 +23,7 @@ const Notice = ( { message, onDismiss } ) => ( const JetpackExternalMediaImport = () => { const [ selectedSource, setSelectedSource ] = useState( null ); const [ noticeMessage, setNoticeMessage ] = useState( '' ); + const { tracks } = useAnalytics(); const ExternalLibrary = getExternalLibrary( selectedSource ); const selectButtonText = ( selectedImages, isCopying ) => { @@ -77,6 +79,9 @@ const JetpackExternalMediaImport = () => { const slug = event.target.dataset.slug; if ( slug ) { setSelectedSource( slug ); + tracks.recordEvent( 'jetpack_external_media_import_media_page_import_click', { + media_source: slug, + } ); } }; @@ -89,7 +94,7 @@ const JetpackExternalMediaImport = () => { element.removeEventListener( 'click', handleClick ); } }; - }, [] ); + }, [ tracks ] ); return ( <> diff --git a/projects/packages/external-media/src/features/admin/external-media-import.php b/projects/packages/external-media/src/features/admin/external-media-import.php index c5a43769b7f09..972f8b7b9c764 100644 --- a/projects/packages/external-media/src/features/admin/external-media-import.php +++ b/projects/packages/external-media/src/features/admin/external-media-import.php @@ -47,10 +47,39 @@ function add_jetpack_external_media_import_page() { __NAMESPACE__ . '\render_jetpack_external_media_import_page' ); + add_action( 'load-upload.php', __NAMESPACE__ . '\enqueue_jetpack_external_media_import_button' ); add_action( "load-$external_media_import_page_hook", __NAMESPACE__ . '\enqueue_jetpack_external_media_import_page' ); } add_action( 'admin_menu', __NAMESPACE__ . '\add_jetpack_external_media_import_page' ); +/** + * Enqueue the assets of the Jetpack external media import button. + */ +function enqueue_jetpack_external_media_import_button() { + $assets_base_path = 'build/'; + $asset_name = 'jetpack-external-media-import-button'; + + Assets::register_script( + $asset_name, + $assets_base_path . "$asset_name/$asset_name.js", + External_Media::BASE_FILE, + array( + 'in_footer' => true, + 'textdomain' => 'jetpack-external-media', + 'css_path' => $assets_base_path . "$asset_name/$asset_name.css", + ) + ); + + Assets::enqueue_script( $asset_name ); + wp_localize_script( + $asset_name, + 'JETPACK_EXTERNAL_MEDIA_IMPORT_BUTTON', + array( + 'href' => admin_url( 'upload.php?page=jetpack_external_media_import_page&untangling-media=true' ), + ) + ); +} + /** * Enqueue the assets of the Jetpack external media page. */ diff --git a/projects/packages/external-media/src/shared/media-browser/index.js b/projects/packages/external-media/src/shared/media-browser/index.js index e9bd6bd4551aa..7ba3619044401 100644 --- a/projects/packages/external-media/src/shared/media-browser/index.js +++ b/projects/packages/external-media/src/shared/media-browser/index.js @@ -1,9 +1,12 @@ -import { Button, Spinner, Composite } from '@wordpress/components'; +import { useAnalytics } from '@automattic/jetpack-shared-extension-utils'; +import { Spinner, Composite } from '@wordpress/components'; import { useCallback, useState, useRef, useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import clsx from 'clsx'; import React from 'react'; +import MediaBrowserSelectButton from './media-browser-select-button'; import MediaItem from './media-item'; +import usePageSource from './use-page-source'; import './style.scss'; const MAX_SELECTED = 10; @@ -13,6 +16,7 @@ const MAX_SELECTED = 10; * * @param {object} props - The component props * @param {object[]} props.media - The list of media + * @param {string} props.mediaSource - The source of media * @param {boolean} props.isCopying - Whether the media browser is copying the media * @param {boolean} props.isLoading - Whether the media browser is loading * @param {boolean} props.imageOnly - Whether to skip non-media items @@ -22,12 +26,13 @@ const MAX_SELECTED = 10; * @param {Function} props.setPath - To set the path for the folder item * @param {Function} props.nextPage - To get the next path * @param {Function} props.onCopy - To handle the copy - * @param {string} props.selectButtonText - The text of the selection button + * @param {Function} props.selectButtonText - To get the select button text * @param {boolean} props.shouldProxyImg - Whether to use the proxy for the media URL * @return {React.ReactElement} - JSX element */ function MediaBrowser( { media, + mediaSource, isCopying, isLoading, imageOnly, @@ -42,6 +47,8 @@ function MediaBrowser( { } ) { const [ selected, setSelected ] = useState( [] ); const gridEl = useRef( null ); + const { tracks } = useAnalytics(); + const pageSource = usePageSource(); const select = useCallback( newlySelected => { @@ -65,45 +72,29 @@ function MediaBrowser( { ); const onCopyAndInsert = useCallback( () => { + tracks.recordEvent( 'jetpack_external_media_modal_submit', { + page: pageSource, + media_source: mediaSource, + media_count: selected.length, + multiple: !! multiple, + } ); + onCopy( selected ); - }, [ selected, onCopy ] ); + }, [ tracks, pageSource, mediaSource, selected, multiple, onCopy ] ); const hasMediaItems = media.filter( item => item.type !== 'folder' ).length > 0; - const classes = clsx( { - 'jetpack-external-media-browser__media': true, - 'jetpack-external-media-browser__media__loading': isLoading, - } ); - const wrapper = clsx( { - 'jetpack-external-media-browser': true, - [ className ]: true, - } ); - - // Using _event to avoid eslint errors. Can change to event if it's in use again. - const handleMediaItemClick = ( _event, { item } ) => { - select( item ); - }; - const SelectButton = selectProps => { - const disabled = selected.length === 0 || isCopying; + const getSelectButtonLabel = () => { const defaultLabel = isCopying ? __( 'Inserting…', 'jetpack-external-media' ) : __( 'Select', 'jetpack-external-media', /* dummy arg to avoid bad minification */ 0 ); - const label = selectProps?.labelText - ? selectProps?.labelText( selected.length, isCopying ) - : defaultLabel; - - return ( -
- -
- ); + + return selectButtonText ? selectButtonText( selected.length, isCopying ) : defaultLabel; + }; + + // Using _event to avoid eslint errors. Can change to event if it's in use again. + const handleMediaItemClick = ( _event, { item } ) => { + select( item ); }; // Infinite scroll @@ -126,11 +117,19 @@ function MediaBrowser( { }, [ pageHandle, isLoading, gridEl ] ); // eslint-disable-line react-hooks/exhaustive-deps return ( -
+
} > @@ -161,7 +160,14 @@ function MediaBrowser( {
) } - { hasMediaItems && } + { hasMediaItems && ( + + ) }
); } diff --git a/projects/packages/external-media/src/shared/media-browser/media-browser-select-button.js b/projects/packages/external-media/src/shared/media-browser/media-browser-select-button.js new file mode 100644 index 0000000000000..a612a7daef41a --- /dev/null +++ b/projects/packages/external-media/src/shared/media-browser/media-browser-select-button.js @@ -0,0 +1,24 @@ +import { Button } from '@wordpress/components'; +import React from 'react'; + +/** + * MediaBrowserSelectButton component + * + * @param {object} props - The component props + * @param {string} props.label - The label of the button + * @param {boolean} props.isLoading - Whether the button is loading + * @param {boolean} props.disabled - Whether the button is disabled + * @param {Function} props.onClick - To handle the click + * @return {React.ReactElement} - JSX element + */ +const MediaBrowserSelectButton = ( { label, isLoading, disabled, onClick } ) => { + return ( +
+ +
+ ); +}; + +export default MediaBrowserSelectButton; diff --git a/projects/packages/external-media/src/shared/media-browser/use-page-source.js b/projects/packages/external-media/src/shared/media-browser/use-page-source.js new file mode 100644 index 0000000000000..70cfa13b6e7e6 --- /dev/null +++ b/projects/packages/external-media/src/shared/media-browser/use-page-source.js @@ -0,0 +1,12 @@ +import { useSelect } from '@wordpress/data'; + +const usePageSource = () => { + const isEditor = useSelect( select => !! select( 'core/editor' ), [] ); + + if ( isEditor ) { + return 'editor'; + } + return 'media-library'; +}; + +export default usePageSource; diff --git a/projects/packages/external-media/src/shared/sources/google-photos/google-photos-media.js b/projects/packages/external-media/src/shared/sources/google-photos/google-photos-media.js index e334c03a156e7..5fcb598812966 100644 --- a/projects/packages/external-media/src/shared/sources/google-photos/google-photos-media.js +++ b/projects/packages/external-media/src/shared/sources/google-photos/google-photos-media.js @@ -11,6 +11,7 @@ import { DATE_RANGE_ANY, } from '../../constants'; import MediaBrowser from '../../media-browser'; +import { MediaSource } from '../../media-service/types'; import { getExternalMediaApiUrl } from '../api'; import Breadcrumbs from './breadcrumbs'; import GoogleFilterOption from './filter-option'; @@ -183,6 +184,7 @@ function GooglePhotosMedia( props ) { className="jetpack-external-media-browser__google" key={ listUrl } media={ media } + mediaSource={ MediaSource.GooglePhotos } imageOnly={ imageOnly } isCopying={ isCopying } isLoading={ isLoading } diff --git a/projects/packages/external-media/src/shared/sources/jetpack-app-media/index.js b/projects/packages/external-media/src/shared/sources/jetpack-app-media/index.js index a4a2cd0643639..640b38de3781b 100644 --- a/projects/packages/external-media/src/shared/sources/jetpack-app-media/index.js +++ b/projects/packages/external-media/src/shared/sources/jetpack-app-media/index.js @@ -136,6 +136,7 @@ function JetpackAppMedia( props ) { key={ 'jetpack-app-media' } className="jetpack-external-media-browser__jetpack_app_media_browser" media={ media } + mediaSource={ MediaSource.JetpackAppMedia } isCopying={ isCopying } isLoading={ false } nextPage={ getNextPage } diff --git a/projects/packages/external-media/src/shared/sources/openverse/index.js b/projects/packages/external-media/src/shared/sources/openverse/index.js index 331c648b817e3..86675f7cc59b0 100644 --- a/projects/packages/external-media/src/shared/sources/openverse/index.js +++ b/projects/packages/external-media/src/shared/sources/openverse/index.js @@ -67,6 +67,7 @@ function OpenverseMedia( props ) { getNextPage( searchQuery ) } diff --git a/projects/packages/external-media/src/shared/sources/pexels/index.js b/projects/packages/external-media/src/shared/sources/pexels/index.js index 83b142ed04fcf..23c162874afe5 100644 --- a/projects/packages/external-media/src/shared/sources/pexels/index.js +++ b/projects/packages/external-media/src/shared/sources/pexels/index.js @@ -68,6 +68,7 @@ function PexelsMedia( props ) { getNextPage( searchQuery ) } diff --git a/projects/packages/external-media/webpack.config.js b/projects/packages/external-media/webpack.config.js index abfb18bdd1654..992136af6494e 100644 --- a/projects/packages/external-media/webpack.config.js +++ b/projects/packages/external-media/webpack.config.js @@ -5,6 +5,10 @@ module.exports = [ { entry: { 'jetpack-external-media-editor': './src/features/editor/index.js', + 'jetpack-external-media-import-button': [ + './src/features/admin/external-media-import-button.js', + './src/features/admin/external-media-import-button.scss', + ], 'jetpack-external-media-import-page': './src/features/admin/external-media-import.js', }, mode: jetpackWebpackConfig.mode, diff --git a/projects/packages/forms/changelog/fix-25025-form-seperator b/projects/packages/forms/changelog/fix-25025-form-seperator new file mode 100644 index 0000000000000..6c872c241915f --- /dev/null +++ b/projects/packages/forms/changelog/fix-25025-form-seperator @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Improves the styling options of the separator block when placed inside the form block diff --git a/projects/packages/forms/changelog/fix-date-validation-bug b/projects/packages/forms/changelog/fix-date-validation-bug new file mode 100644 index 0000000000000..b812fbbf2ae5f --- /dev/null +++ b/projects/packages/forms/changelog/fix-date-validation-bug @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Forms: fixes the date format input if multiple date pickers are used with different date formats. diff --git a/projects/packages/forms/changelog/fix-form-submit-miulti-page b/projects/packages/forms/changelog/fix-form-submit-miulti-page new file mode 100644 index 0000000000000..bf6cbe84766ef --- /dev/null +++ b/projects/packages/forms/changelog/fix-form-submit-miulti-page @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Forms: Add support for having multiple forms accross paginated pages diff --git a/projects/packages/forms/changelog/fix-invalid-field-ids b/projects/packages/forms/changelog/fix-invalid-field-ids new file mode 100644 index 0000000000000..ced045f8f6a66 --- /dev/null +++ b/projects/packages/forms/changelog/fix-invalid-field-ids @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Forms: Fix invalid html IDs. diff --git a/projects/packages/forms/src/blocks/contact-form/editor.scss b/projects/packages/forms/src/blocks/contact-form/editor.scss index d506b5bc8dc98..15b4e982be665 100644 --- a/projects/packages/forms/src/blocks/contact-form/editor.scss +++ b/projects/packages/forms/src/blocks/contact-form/editor.scss @@ -93,6 +93,17 @@ &[data-type='jetpack/field-consent'] { align-self: center; } + + &:where( .wp-block-jetpack-contact-form .wp-block-separator ){ + max-width: var( --wp--preset--spacing--80, 100px ); + margin-left: auto; + margin-right: auto; + } + &:where( .wp-block-jetpack-contact-form .wp-block-separator.is-style-wide ), + &:where( .wp-block-jetpack-contact-form .wp-block-separator.is-style-dots ) { + max-width: inherit; + } + } } diff --git a/projects/packages/forms/src/class-wpcom-rest-api-v2-endpoint-forms.php b/projects/packages/forms/src/class-wpcom-rest-api-v2-endpoint-forms.php index ad29686fb12f2..0e9b8295363dd 100644 --- a/projects/packages/forms/src/class-wpcom-rest-api-v2-endpoint-forms.php +++ b/projects/packages/forms/src/class-wpcom-rest-api-v2-endpoint-forms.php @@ -159,13 +159,7 @@ public function get_responses( $request ) { array_diff_key( $filter_args, array( 'post_parent' => '' ) ) ); - $base_fields = array( - 'email_marketing_consent' => '', - 'entry_title' => '', - 'entry_permalink' => '', - 'feedback_id' => '', - ); - + $base_fields = Contact_Form_Plugin::NON_PRINTABLE_FIELDS; $data_defaults = array( '_feedback_author' => '', '_feedback_author_email' => '', diff --git a/projects/packages/forms/src/contact-form/class-admin.php b/projects/packages/forms/src/contact-form/class-admin.php index 0f2ec1379352c..77bb034a45c59 100644 --- a/projects/packages/forms/src/contact-form/class-admin.php +++ b/projects/packages/forms/src/contact-form/class-admin.php @@ -702,12 +702,6 @@ public function grunion_manage_post_column_from( $post ) { * @return void */ public function grunion_manage_post_column_response( $post ) { - $non_printable_keys = array( - 'email_marketing_consent', - 'entry_title', - 'entry_permalink', - 'feedback_id', - ); $post_content = get_post_field( 'post_content', $post->ID ); $content = explode( '', $post_content ); @@ -750,7 +744,12 @@ public function grunion_manage_post_column_response( $post ) { } } - $response_fields = array_diff_key( $response_fields, array_flip( $non_printable_keys ) ); + $url = get_permalink( $post->post_parent ); + if ( isset( $response_fields['entry_page'] ) ) { + $url = add_query_arg( 'page', $response_fields['entry_page'], $url ); + } + + $response_fields = array_diff_key( $response_fields, array_flip( array_keys( Contact_Form_Plugin::NON_PRINTABLE_FIELDS ) ) ); echo ''; echo ''; } diff --git a/projects/packages/forms/src/contact-form/class-contact-form-field.php b/projects/packages/forms/src/contact-form/class-contact-form-field.php index 52e5f6385e6e6..4cd4819420d45 100644 --- a/projects/packages/forms/src/contact-form/class-contact-form-field.php +++ b/projects/packages/forms/src/contact-form/class-contact-form-field.php @@ -655,7 +655,7 @@ public function render_textarea_field( $id, $label, $value, $class, $required, $ /** * Return the HTML for the radio field. * - * @param int $id - the ID. + * @param string $id - the ID (starts with 'g' - see constructor). * @param string $label - the label. * @param string $value - the value of the field. * @param string $class - the field class. @@ -670,11 +670,20 @@ public function render_radio_field( $id, $label, $value, $class, $required, $req $field_style = 'style="' . $this->option_styles . '"'; + $used_html_ids = array(); + foreach ( (array) $this->get_attribute( 'options' ) as $option_index => $option ) { $option = Contact_Form_Plugin::strip_tags( $option ); if ( is_string( $option ) && $option !== '' ) { $radio_value = $this->get_option_value( $this->get_attribute( 'values' ), $option_index, $option ); - $radio_id = "$id-$radio_value"; + $radio_id = $id . '-' . sanitize_html_class( $radio_value ); + + // If exact id was already used in this radio group, append option index. + // Multiple 'blue' options would give id-blue, id-blue-1, id-blue-2, etc. + if ( isset( $used_html_ids[ $radio_id ] ) ) { + $radio_id .= '-' . $option_index; + } + $used_html_ids[ $radio_id ] = true; $field .= "

"; $field .= "option_styles . '"'; + $used_html_ids = array(); + foreach ( (array) $this->get_attribute( 'options' ) as $option_index => $option ) { $option = Contact_Form_Plugin::strip_tags( $option ); if ( is_string( $option ) && $option !== '' ) { $checkbox_value = $this->get_option_value( $this->get_attribute( 'values' ), $option_index, $option ); - $checkbox_id = "$id-$checkbox_value"; + $checkbox_id = $id . '-' . sanitize_html_class( $checkbox_value ); + + // If exact id was already used in this checkbox group, append option index. + // Multiple 'blue' options would give id-blue, id-blue-1, id-blue-2, etc. + if ( isset( $used_html_ids[ $checkbox_id ] ) ) { + $checkbox_id .= '-' . $option_index; + } + $used_html_ids[ $checkbox_id ] = true; $field .= "

"; $field .= " '', + 'email_marketing_consent' => '', + 'entry_permalink' => '', + 'entry_page' => '', + 'feedback_id' => '', + ); + /** * Initializing function. */ @@ -712,8 +726,16 @@ public function process_form_submission() { // Process the content to populate Contact_Form::$last if ( $post ) { + if ( str_contains( $post->post_content, '' ) ) { + $postdata = generate_postdata( $post ); + $page = isset( $_POST['page'] ) ? absint( wp_unslash( $_POST['page'] ) ) : null; // phpcs:Ignore WordPress.Security.NonceVerification.Missing + $paged = isset( $page ) ? $page : 1; + $content = isset( $postdata['pages'][ $paged - 1 ] ) ? $postdata['pages'][ $paged - 1 ] : $post->post_content; + } else { + $content = $post->post_content; + } /** This filter is already documented in core. wp-includes/post-template.php */ - apply_filters( 'the_content', $post->post_content ); + apply_filters( 'the_content', $content ); } } @@ -1148,7 +1170,7 @@ public function get_post_meta_for_csv_export( $post_id, $has_json_data = false ) $content_fields = self::parse_fields_from_content( $post_id ); $all_fields = isset( $content_fields['_feedback_all_fields'] ) ? $content_fields['_feedback_all_fields'] : array(); $md = $has_json_data - ? array_diff_key( $all_fields, array_flip( array( 'entry_title', 'email_marketing_consent', 'entry_permalink', 'feedback_id' ) ) ) + ? array_diff_key( $all_fields, array_flip( array_keys( self::NON_PRINTABLE_FIELDS ) ) ) : (array) get_post_meta( $post_id, '_feedback_extra_fields', true ); $md['-3_response_date'] = get_the_date( 'Y-m-d H:i:s', $post_id ); diff --git a/projects/packages/forms/src/contact-form/class-contact-form.php b/projects/packages/forms/src/contact-form/class-contact-form.php index 96bea7549ca73..764c7d52240de 100644 --- a/projects/packages/forms/src/contact-form/class-contact-form.php +++ b/projects/packages/forms/src/contact-form/class-contact-form.php @@ -82,7 +82,7 @@ class Contact_Form extends Contact_Form_Shortcode { * @param string $content - the content. */ public function __construct( $attributes, $content = null ) { - global $post; + global $post, $page; // Set up the default subject and recipient for this form. $default_to = ''; @@ -123,7 +123,7 @@ public function __construct( $attributes, $content = null ) { if ( ! isset( $attributes['id'] ) ) { $attributes['id'] = ''; } - $attributes['id'] = $attributes['id'] . '-' . ( count( self::$forms ) + 1 ); + $attributes['id'] = $attributes['id'] . '-' . ( count( self::$forms ) + 1 ) . '-' . $page; } $this->hash = sha1( wp_json_encode( $attributes ) ); @@ -249,8 +249,7 @@ public static function style_on() { * @return string HTML for the concat form. */ public static function parse( $attributes, $content ) { - global $post; - + global $post, $page; // $page is used in the contact-form submission redirect if ( Settings::is_syncing() ) { return ''; } @@ -347,6 +346,9 @@ public static function parse( $attributes, $content ) { } else { // Submit form to the post permalink $url = get_permalink(); + if ( $page ) { + $url = add_query_arg( 'page', $page, $url ); + } } // For SSL/TLS page. See RFC 3986 Section 4.2 @@ -364,7 +366,7 @@ public static function parse( $attributes, $content ) { * @param $post $GLOBALS['post'] Post global variable. * @param int $id Contact Form ID. */ - $url = apply_filters( 'grunion_contact_form_form_action', "{$url}#contact-form-{$id}", $GLOBALS['post'], $id ); + $url = apply_filters( 'grunion_contact_form_form_action', "{$url}#contact-form-{$id}", $GLOBALS['post'], $id, $page ); $has_submit_button_block = str_contains( $content, 'wp-block-jetpack-button' ); $form_classes = 'contact-form commentsblock'; $post_title = $post->post_title ?? ''; @@ -434,6 +436,10 @@ public static function parse( $attributes, $content ) { $r .= "\t\t\n"; $r .= "\t\t\n"; + if ( $page && $page > 1 ) { + $r .= "\t\t\n"; + } + if ( ! $has_submit_button_block ) { $r .= "\t

\n"; } @@ -1323,10 +1329,14 @@ public function process_submission() { $entry_values = array( 'entry_title' => the_title_attribute( 'echo=0' ), - 'entry_permalink' => esc_url( get_permalink( get_the_ID() ) ), + 'entry_permalink' => esc_url( self::get_permalink( get_the_ID() ) ), 'feedback_id' => $feedback_id, ); + if ( isset( $_POST['page'] ) ) { // phpcs:Ignore WordPress.Security.NonceVerification.Missing + $entry_values['entry_page'] = absint( wp_unslash( $_POST['page'] ) ); // phpcs:Ignore WordPress.Security.NonceVerification.Missing + } + $all_values = array_merge( $all_values, $entry_values ); /** This filter is already documented in \Automattic\Jetpack\Forms\ContactForm\Admin */ @@ -1338,7 +1348,7 @@ public function process_submission() { if ( $block_template || $block_template_part || $widget ) { $url = home_url( '/' ); } else { - $url = get_permalink( $post->ID ); + $url = self::get_permalink( $post->ID ); } // translators: the time of the form submission. @@ -1645,6 +1655,21 @@ public function process_submission() { wp_redirect( $redirect ); exit( 0 ); } + /** + * Get the permalink for the post ID that include the page query parameter if it was set. + * + * @param int $post_id The post ID. + * + * return string The permalink for the post ID. + */ + public static function get_permalink( $post_id ) { + $url = get_permalink( $post_id ); + $page = isset( $_POST['page'] ) ? absint( wp_unslash( $_POST['page'] ) ) : null; // phpcs:Ignore WordPress.Security.NonceVerification.Missing + if ( $page ) { + return add_query_arg( 'page', $page, $url ); + } + return $url; + } /** * Wrapper for wp_mail() that enables HTML messages with text alternatives diff --git a/projects/packages/forms/src/contact-form/css/grunion.css b/projects/packages/forms/src/contact-form/css/grunion.css index d189111812244..30f83ecd6565c 100644 --- a/projects/packages/forms/src/contact-form/css/grunion.css +++ b/projects/packages/forms/src/contact-form/css/grunion.css @@ -235,6 +235,16 @@ box-sizing: border-box; } +:where( .wp-block-jetpack-contact-form .wp-block-separator ) { + max-width: var( --wp--preset--spacing--80, 100px ); + margin-top: 0; + margin-bottom: 0; +} +:where( .wp-block-jetpack-contact-form .wp-block-separator.is-style-wide ), +:where( .wp-block-jetpack-contact-form .wp-block-separator.is-style-dots ) { + max-width: inherit; +} + /* Added circa Nov 2022: container class assigned to topmost block div */ .wp-block-jetpack-contact-form-container.alignfull .wp-block-jetpack-contact-form { padding-right: 0; diff --git a/projects/packages/forms/src/contact-form/js/grunion-frontend.js b/projects/packages/forms/src/contact-form/js/grunion-frontend.js index c10e153fc19e6..b615836bb8a18 100644 --- a/projects/packages/forms/src/contact-form/js/grunion-frontend.js +++ b/projects/packages/forms/src/contact-form/js/grunion-frontend.js @@ -1,13 +1,15 @@ jQuery( function ( $ ) { const $input = $( '.contact-form input.jp-contact-form-date' ); - const dateFormat = $input.attr( 'data-format' ) || 'yy-mm-dd'; - - $input.datepicker( { - dateFormat, - constrainInput: false, - showOptions: { direction: 'down' }, - onSelect: function () { - $( this ).focus(); - }, + $input.each( function () { + const el = $( this ); + const dateFormat = el.attr( 'data-format' ) || 'yy-mm-dd'; + el.datepicker( { + dateFormat, + constrainInput: false, + showOptions: { direction: 'down' }, + onSelect: function () { + $( this ).focus(); + }, + } ); } ); } ); diff --git a/projects/packages/jetpack-mu-wpcom/changelog/media-tracks b/projects/packages/jetpack-mu-wpcom/changelog/media-tracks new file mode 100644 index 0000000000000..48205a668b670 --- /dev/null +++ b/projects/packages/jetpack-mu-wpcom/changelog/media-tracks @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Media Library: add track events for upload from URL feature diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-media/wpcom-media-url-upload-form/index.jsx b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-media/wpcom-media-url-upload-form/index.jsx index ab965659d96e3..bc6d37c441b79 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-media/wpcom-media-url-upload-form/index.jsx +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-media/wpcom-media-url-upload-form/index.jsx @@ -1,10 +1,11 @@ import { __ } from '@wordpress/i18n'; import clsx from 'clsx'; import { useState } from 'react'; +import { wpcomTrackEvent } from '../../../common/tracks'; import './style.scss'; -const WpcomMediaUrlUploadForm = ( { ajaxUrl, action, nonce, isEditor } ) => { +const WpcomMediaUrlUploadForm = ( { ajaxUrl, action, nonce, page } ) => { const [ url, setUrl ] = useState( '' ); const [ show, setShow ] = useState( false ); @@ -25,6 +26,10 @@ const WpcomMediaUrlUploadForm = ( { ajaxUrl, action, nonce, isEditor } ) => { } e.preventDefault(); + wpcomTrackEvent( 'wpcom_media_upload_from_url_submit', { + page, + } ); + const formData = new FormData(); formData.append( 'action', action ); formData.append( 'url', url ); @@ -48,7 +53,7 @@ const WpcomMediaUrlUploadForm = ( { ajaxUrl, action, nonce, isEditor } ) => { .collection.add( attachmentToAdd ); }; - if ( isEditor ) { + if ( page === 'editor' ) { const mediaLibraryTab = window.wp.media.frame.state( 'library' ); mediaLibraryTab.trigger( 'open' ); diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-media/wpcom-media-url-upload.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-media/wpcom-media-url-upload.php index 82afc12ae90a3..930151c080d2e 100644 --- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-media/wpcom-media-url-upload.php +++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-media/wpcom-media-url-upload.php @@ -23,10 +23,10 @@ function wpcom_media_url_upload() { $data = wp_json_encode( array( - 'ajaxUrl' => admin_url( 'admin-ajax.php' ), - 'action' => 'wpcom_media_url_upload', - 'nonce' => wp_create_nonce( 'wpcom_media_url_upload' ), - 'isEditor' => $pagenow !== 'upload.php', + 'ajaxUrl' => admin_url( 'admin-ajax.php' ), + 'action' => 'wpcom_media_url_upload', + 'nonce' => wp_create_nonce( 'wpcom_media_url_upload' ), + 'page' => $pagenow === 'upload.php' ? 'media-library' : 'editor', ) ); diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/auto-firewall-status.tsx b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/auto-firewall-status.tsx index 9b1d94839270e..9b12175cf32c1 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/auto-firewall-status.tsx +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/auto-firewall-status.tsx @@ -19,10 +19,11 @@ export const AutoFirewallStatus = () => { const { protect: { wafConfig: wafData }, } = getMyJetpackWindowInitialState(); - const { jetpack_waf_automatic_rules: isAutoFirewallEnabled } = wafData || {}; + const { jetpack_waf_automatic_rules: isAutoFirewallEnabled, waf_enabled: isWafEnabled } = + wafData || {}; if ( isPluginActive && isSiteConnected ) { - if ( isAutoFirewallEnabled ) { + if ( isAutoFirewallEnabled && isWafEnabled ) { return ; } diff --git a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts index 45f7f150190f9..662b37aff79ec 100644 --- a/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts +++ b/projects/packages/my-jetpack/_inc/components/product-cards-section/protect-card/use-protect-tooltip-copy.ts @@ -50,6 +50,7 @@ export function useProtectTooltipCopy(): TooltipContent { blocked_logins: blockedLoginsCount, brute_force_protection: hasBruteForceProtection, waf_supported: wafSupported, + waf_enabled: isWafEnabled, } = wafData || {}; const pluginsCount = fromScanPlugins.length || Object.keys( plugins ).length; @@ -247,7 +248,7 @@ export function useProtectTooltipCopy(): TooltipContent { ), }, autoFirewallTooltip: - ( hasProtectPaidPlan && ! isAutoFirewallEnabled ) || ! wafSupported + ( hasProtectPaidPlan && ( ! isAutoFirewallEnabled || ! isWafEnabled ) ) || ! wafSupported ? { title: __( 'Auto-Firewall: Inactive', 'jetpack-my-jetpack' ), text: wafSupported diff --git a/projects/packages/my-jetpack/changelog/fix-my-jetpack-protect-card-shield-inconsistency b/projects/packages/my-jetpack/changelog/fix-my-jetpack-protect-card-shield-inconsistency new file mode 100644 index 0000000000000..48b9490da9170 --- /dev/null +++ b/projects/packages/my-jetpack/changelog/fix-my-jetpack-protect-card-shield-inconsistency @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Fix bug where firewall was displayed as active if automatic rules were enabled but firewall was off diff --git a/projects/packages/my-jetpack/global.d.ts b/projects/packages/my-jetpack/global.d.ts index 117857b70ec97..eb8fc175024ce 100644 --- a/projects/packages/my-jetpack/global.d.ts +++ b/projects/packages/my-jetpack/global.d.ts @@ -288,6 +288,7 @@ interface Window { jetpack_waf_share_debug_data: boolean; standalone_mode: boolean; waf_supported: boolean; + waf_enabled: boolean; }; }; videopress: { diff --git a/projects/packages/my-jetpack/src/class-initializer.php b/projects/packages/my-jetpack/src/class-initializer.php index ec2a40c7af88b..b8ee063b144f4 100644 --- a/projects/packages/my-jetpack/src/class-initializer.php +++ b/projects/packages/my-jetpack/src/class-initializer.php @@ -237,8 +237,9 @@ public static function enqueue_scripts() { $scan_data = Products\Protect::get_protect_data(); self::update_historically_active_jetpack_modules(); - $waf_config = array(); - $waf_supported = false; + $waf_config = array(); + $waf_supported = false; + $is_waf_enabled = false; $sandboxed_domain = ''; $is_dev_version = false; @@ -248,8 +249,9 @@ public static function enqueue_scripts() { } if ( class_exists( 'Automattic\Jetpack\Waf\Waf_Runner' ) ) { - $waf_config = Waf_Runner::get_config(); - $waf_supported = Waf_Runner::is_supported_environment(); + $waf_config = Waf_Runner::get_config(); + $is_waf_enabled = Waf_Runner::is_enabled(); + $waf_supported = Waf_Runner::is_supported_environment(); } wp_localize_script( @@ -313,6 +315,7 @@ public static function enqueue_scripts() { $waf_config, array( 'waf_supported' => $waf_supported, + 'waf_enabled' => $is_waf_enabled, ), array( 'blocked_logins' => (int) get_site_option( 'jetpack_protect_blocked_attempts', 0 ) ) ), diff --git a/projects/packages/sync/changelog/update-dont-sync-set-object-terms-action-for-blacklisted-taxonomies b/projects/packages/sync/changelog/update-dont-sync-set-object-terms-action-for-blacklisted-taxonomies new file mode 100644 index 0000000000000..2b074cde382a9 --- /dev/null +++ b/projects/packages/sync/changelog/update-dont-sync-set-object-terms-action-for-blacklisted-taxonomies @@ -0,0 +1,4 @@ +Significance: minor +Type: deprecated + +Sync: Full sync for posts not sending term relationships diff --git a/projects/packages/sync/src/modules/class-posts.php b/projects/packages/sync/src/modules/class-posts.php index 54c07996855ba..6d20fafe732fa 100644 --- a/projects/packages/sync/src/modules/class-posts.php +++ b/projects/packages/sync/src/modules/class-posts.php @@ -228,7 +228,7 @@ public function init_before_send() { // Full sync. $sync_module = Modules::get_module( 'full-sync' ); if ( $sync_module instanceof Full_Sync_Immediately ) { - add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'add_term_relationships' ) ); + add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'build_full_sync_action_array' ) ); } else { add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'expand_posts_with_metadata_and_terms' ) ); } @@ -779,6 +779,25 @@ public function send_published( $post_ID, $post ) { } } + /** + * Build the full sync action object for Posts. + * + * @access public + * + * @param array $args An array with the posts and the previous end. + * + * @return array An array with the posts, postmeta and the previous end. + */ + public function build_full_sync_action_array( $args ) { + list( $filtered_posts, $previous_end ) = $args; + return array( + $filtered_posts['objects'], + $filtered_posts['meta'], + array(), // WPCOM does not process term relationships in full sync posts actions for a while now, let's skip them. + $previous_end, + ); + } + /** * Add term relationships to post objects within a hook before they are serialized and sent to the server. * This is used in Full Sync Immediately @@ -787,8 +806,10 @@ public function send_published( $post_ID, $post ) { * * @param array $args The hook parameters. * @return array $args The expanded hook parameters. + * @deprecated since $$next-version$$ */ public function add_term_relationships( $args ) { + _deprecated_function( __METHOD__, '$$next-version$$' ); list( $filtered_posts, $previous_interval_end ) = $args; return array( diff --git a/projects/packages/sync/src/modules/class-woocommerce-hpos-orders.php b/projects/packages/sync/src/modules/class-woocommerce-hpos-orders.php index 13a892dea24c2..2bc63511b98fa 100644 --- a/projects/packages/sync/src/modules/class-woocommerce-hpos-orders.php +++ b/projects/packages/sync/src/modules/class-woocommerce-hpos-orders.php @@ -231,7 +231,7 @@ public function get_objects_by_id( $object_type, $ids ) { * @deprecated since $$next-version$$ */ public function expand_order_objects( $args ) { - _deprecated_function( __METHOD__, 'next-version' ); + _deprecated_function( __METHOD__, '$$next-version$$' ); list( $order_ids, $previous_end ) = $args; return array( 'orders' => $this->get_objects_by_id( 'order', $order_ids ), diff --git a/projects/plugins/boost/app/lib/minify/functions-service.php b/projects/plugins/boost/app/lib/minify/functions-service.php index 798e986d6fad0..ccdf9e8513a06 100644 --- a/projects/plugins/boost/app/lib/minify/functions-service.php +++ b/projects/plugins/boost/app/lib/minify/functions-service.php @@ -68,17 +68,26 @@ function jetpack_boost_check_404_handler( $request_uri ) { * This function is used to test if is_404() is working in wp-content/ * It sends a request to a non-existent URL, that will execute the 404 handler * in jetpack_boost_check_404_handler(). + * Define the constant JETPACK_BOOST_DISABLE_404_TESTER to disable this. * - * This function is called when the Minify_CSS or Minify_JS module is activated. + * This function is called when the Minify_CSS or Minify_JS module is activated, and once per day. */ function jetpack_boost_404_tester() { + if ( defined( 'JETPACK_BOOST_DISABLE_404_TESTER' ) && JETPACK_BOOST_DISABLE_404_TESTER ) { + return; + } + + $minification_enabled = ''; wp_remote_get( home_url( '/wp-content/boost-cache/static/testing_404' ) ); if ( file_exists( Config::get_static_cache_dir_path() . '/404' ) ) { wp_delete_file( Config::get_static_cache_dir_path() . '/404' ); - update_site_option( 'jetpack_boost_static_minification', 1 ); + $minification_enabled = 1; } else { - update_site_option( 'jetpack_boost_static_minification', 0 ); + $minification_enabled = 0; } + update_site_option( 'jetpack_boost_static_minification', $minification_enabled ); + + return $minification_enabled; } add_action( 'jetpack_boost_404_tester_cron', 'jetpack_boost_404_tester' ); diff --git a/projects/plugins/boost/app/modules/optimizations/minify/class-minify-css.php b/projects/plugins/boost/app/modules/optimizations/minify/class-minify-css.php index 2144a5072c269..efeec1c0ac79f 100644 --- a/projects/plugins/boost/app/modules/optimizations/minify/class-minify-css.php +++ b/projects/plugins/boost/app/modules/optimizations/minify/class-minify-css.php @@ -3,12 +3,13 @@ namespace Automattic\Jetpack_Boost\Modules\Optimizations\Minify; use Automattic\Jetpack_Boost\Contracts\Changes_Page_Output; +use Automattic\Jetpack_Boost\Contracts\Has_Activate; use Automattic\Jetpack_Boost\Contracts\Has_Deactivate; use Automattic\Jetpack_Boost\Contracts\Optimization; use Automattic\Jetpack_Boost\Contracts\Pluggable; use Automattic\Jetpack_Boost\Lib\Minify\Concatenate_CSS; -class Minify_CSS implements Pluggable, Changes_Page_Output, Optimization, Has_Deactivate { +class Minify_CSS implements Pluggable, Changes_Page_Output, Optimization, Has_Activate, Has_Deactivate { public static $default_excludes = array( 'admin-bar', 'dashicons', 'elementor-app' ); @@ -49,6 +50,10 @@ public function init_minify() { $wp_styles->allow_gzip_compression = true; // @todo - used constant ALLOW_GZIP_COMPRESSION = true if not defined. } + public static function activate() { + jetpack_boost_404_tester(); + } + public static function deactivate() { jetpack_boost_page_optimize_cleanup_cache( 'css' ); } diff --git a/projects/plugins/boost/app/modules/optimizations/minify/class-minify-js.php b/projects/plugins/boost/app/modules/optimizations/minify/class-minify-js.php index 0a9bfb319a011..76998a6c4e824 100644 --- a/projects/plugins/boost/app/modules/optimizations/minify/class-minify-js.php +++ b/projects/plugins/boost/app/modules/optimizations/minify/class-minify-js.php @@ -3,12 +3,13 @@ namespace Automattic\Jetpack_Boost\Modules\Optimizations\Minify; use Automattic\Jetpack_Boost\Contracts\Changes_Page_Output; +use Automattic\Jetpack_Boost\Contracts\Has_Activate; use Automattic\Jetpack_Boost\Contracts\Has_Deactivate; use Automattic\Jetpack_Boost\Contracts\Optimization; use Automattic\Jetpack_Boost\Contracts\Pluggable; use Automattic\Jetpack_Boost\Lib\Minify\Concatenate_JS; -class Minify_JS implements Pluggable, Changes_Page_Output, Optimization, Has_Deactivate { +class Minify_JS implements Pluggable, Changes_Page_Output, Optimization, Has_Activate, Has_Deactivate { public static $default_excludes = array( 'jquery', 'jquery-core', 'underscore', 'backbone' ); @@ -49,6 +50,10 @@ public function init_minify() { $wp_scripts->allow_gzip_compression = true; // @todo - used constant ALLOW_GZIP_COMPRESSION = true if not defined. } + public static function activate() { + jetpack_boost_404_tester(); + } + public static function deactivate() { jetpack_boost_page_optimize_cleanup_cache( 'js' ); } diff --git a/projects/plugins/boost/changelog/fix-boost-compatibility-depay-woocommerce b/projects/plugins/boost/changelog/fix-boost-compatibility-depay-woocommerce new file mode 100644 index 0000000000000..6044234689c32 --- /dev/null +++ b/projects/plugins/boost/changelog/fix-boost-compatibility-depay-woocommerce @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Concatenate JS: Add compatibility with "Depay Payments for WooCommerce". diff --git a/projects/plugins/boost/changelog/update-boost-404-test-activate b/projects/plugins/boost/changelog/update-boost-404-test-activate new file mode 100644 index 0000000000000..535387eb3008f --- /dev/null +++ b/projects/plugins/boost/changelog/update-boost-404-test-activate @@ -0,0 +1,5 @@ +Significance: patch +Type: fixed +Comment: Minification: run the 404 tester when activating either of the minification/concatenation modules. + + diff --git a/projects/plugins/boost/compatibility/js-concatenate.php b/projects/plugins/boost/compatibility/js-concatenate.php index b4aca14772b45..0e3157c94860c 100644 --- a/projects/plugins/boost/compatibility/js-concatenate.php +++ b/projects/plugins/boost/compatibility/js-concatenate.php @@ -12,6 +12,8 @@ function maybe_do_not_concat( $do_concat, $handle ) { 'tribe-tickets-provider', // Plugin: `woocommerce-shipping` 'woocommerce-shipping-checkout-address-validation', + // Plugin: `depay-payments-for-woocommerce` + 'DEPAY_WC_WIDGETS', ); if ( in_array( $handle, $excluded_handles, true ) ) { diff --git a/projects/plugins/boost/tests/php/lib/minify/test-functions-service.php b/projects/plugins/boost/tests/php/lib/minify/test-functions-service.php new file mode 100644 index 0000000000000..2cf2d253b4f5b --- /dev/null +++ b/projects/plugins/boost/tests/php/lib/minify/test-functions-service.php @@ -0,0 +1,99 @@ +andReturn( true ); + + // Clean up any test files before each test + if ( file_exists( Config::get_static_cache_dir_path() . '/404' ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_exists + unlink( Config::get_static_cache_dir_path() . '/404' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink + } + require_once __DIR__ . '/../../../../app/lib/minify/loader.php'; + } + + public function test_404_tester_when_404_file_exists() { + // Create mock 404 file + $cache_dir = Config::get_static_cache_dir_path(); + if ( ! is_dir( $cache_dir ) ) { + mkdir( $cache_dir, 0775, true ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_mkdir + } + file_put_contents( $cache_dir . '/404', '1' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents + + Functions\expect( 'home_url' ) + ->once() + ->with( '/wp-content/boost-cache/static/testing_404' ) + ->andReturn( 'http://example.com/wp-content/boost-cache/static/testing_404' ); + + Functions\expect( 'wp_remote_get' ) + ->once() + ->with( 'http://example.com/wp-content/boost-cache/static/testing_404' ); + + Functions\expect( 'wp_delete_file' ) + ->once() + ->with( $cache_dir . '/404' ); + + Functions\expect( 'update_site_option' ) + ->once() + ->with( 'jetpack_boost_static_minification', 1 ); + + $this->assertEquals( 1, jetpack_boost_404_tester() ); + } + + public function test_404_tester_when_404_file_does_not_exist() { + Functions\expect( 'home_url' ) + ->once() + ->with( '/wp-content/boost-cache/static/testing_404' ) + ->andReturn( 'http://example.com/wp-content/boost-cache/static/testing_404' ); + + Functions\expect( 'wp_remote_get' ) + ->once() + ->with( 'http://example.com/wp-content/boost-cache/static/testing_404' ); + + Functions\expect( 'update_site_option' ) + ->once() + ->with( 'jetpack_boost_static_minification', 0 ); + + $this->assertEquals( 0, jetpack_boost_404_tester() ); + } + + public function test_404_tester_disabled_by_constant() { + if ( ! defined( 'JETPACK_BOOST_DISABLE_404_TESTER' ) ) { + define( 'JETPACK_BOOST_DISABLE_404_TESTER', true ); + } + + Functions\expect( 'wp_remote_get' )->never(); + Functions\expect( 'update_site_option' )->never(); + + $this->assertEquals( '', jetpack_boost_404_tester() ); + } + + protected function tear_down() { + parent::tear_down(); + + // Clean up any test files after each test + if ( file_exists( Config::get_static_cache_dir_path() . '/404' ) ) { + unlink( Config::get_static_cache_dir_path() . '/404' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink + } + + $path = '/tmp/wordpress/wp-content/boost-cache/static/'; + while ( $path !== '/tmp' ) { + if ( is_dir( $path ) ) { + rmdir( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir + } + $path = dirname( $path ); + } + } +} diff --git a/projects/plugins/debug-helper/.phan/baseline.php b/projects/plugins/debug-helper/.phan/baseline.php index b7c093aa21e3b..724eaf018ac4c 100644 --- a/projects/plugins/debug-helper/.phan/baseline.php +++ b/projects/plugins/debug-helper/.phan/baseline.php @@ -45,6 +45,7 @@ 'modules/class-scan-helper.php' => ['PhanNoopNew', 'PhanSuspiciousValueComparison', 'PhanTypeConversionFromArray', 'PhanTypeMismatchReturnProbablyReal'], 'modules/class-sync-data-settings-tester.php' => ['PhanNoopNew', 'PhanTypePossiblyInvalidDimOffset', 'PhanUndeclaredClass'], 'modules/class-waf-helper.php' => ['PhanNoopNew', 'PhanPluginSimplifyExpressionBool', 'PhanTypeMismatchReturnProbablyReal', 'PhanUndeclaredClassConstant', 'PhanUndeclaredClassMethod'], + 'modules/class-wpcom-api-request-faker-module.php' => ['PhanUndeclaredClassMethod'], 'modules/class-wpcom-api-request-tracker-module.php' => ['PhanNoopNew', 'PhanTypeMismatchArgument'], 'modules/class-xmlrpc-blocker.php' => ['PhanNoopNew'], 'modules/class-xmlrpc-logger.php' => ['PhanNoopNew', 'PhanUndeclaredFunction'], diff --git a/projects/plugins/debug-helper/changelog/debug-add-wpcom-api-request-faker b/projects/plugins/debug-helper/changelog/debug-add-wpcom-api-request-faker new file mode 100644 index 0000000000000..6b85711b72655 --- /dev/null +++ b/projects/plugins/debug-helper/changelog/debug-add-wpcom-api-request-faker @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Debug Helper: Added WPcom API request sending functionality to help testing specific requests manually. diff --git a/projects/plugins/debug-helper/modules/class-wpcom-api-request-faker-module.php b/projects/plugins/debug-helper/modules/class-wpcom-api-request-faker-module.php new file mode 100644 index 0000000000000..b50bdf0e6d16f --- /dev/null +++ b/projects/plugins/debug-helper/modules/class-wpcom-api-request-faker-module.php @@ -0,0 +1,185 @@ +Error: This helper requires a jetpack connection to work. Please ensure that you have set one up before using.

'; + return; + } + + // Handle the form submit + if ( ! empty( $_POST ) ) { + if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ?? '' ) ), 'wpcom-api-request-faker' ) ) { + echo '

Wrong nonce, aborting.

'; + return; + } + + $is_connected = ( new Connection_Manager() )->is_connected(); + if ( ! $is_connected ) { + echo '

Site is not connected, please establish a jetpack connection first

'; + return; + } + + $api_url = '/' . sanitize_text_field( wp_unslash( $_POST['url'] ?? '' ) ); + $version = sanitize_text_field( wp_unslash( $_POST['version'] ?? '2' ) ); + $method = sanitize_text_field( wp_unslash( $_POST['method'] ?? 'get' ) ); + + $body = null; + if ( 'post' === $_POST['method'] || 'put' === $_POST['method'] ) { + $body = json_decode( sanitize_text_field( wp_unslash( $_POST['body'] ?? '' ) ), true ); + } + + $response = Client::wpcom_json_api_request_as_blog( + $api_url, + $version, + array( 'method' => sanitize_text_field( wp_unslash( $_POST['method'] ?? '2' ) ) ), + $body, + 'wpcom' + ); + + $response_code = wp_remote_retrieve_response_code( $response ); + + // Display error or response + if ( is_wp_error( $response ) || 200 !== $response_code || empty( $response['body'] ) ) { + ?> +

Something went wrong, here is the error (http code )

+ +
+ +

Response for

+ ' . esc_html( var_export( $looks_like_json ? json_decode( $body, true ) : $body, true ) ) . ''; + } + } + + ?> +
+ +

WPcom API Request Faker

+ +
+
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ / + +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ + +
+ +
+
+
+
+ 'WPCOM API Request Tracker', 'description' => 'Displays the number of requests to WPCOM API endpoints for the current page request.', ), + 'wpcom-api-request-faker' => array( + 'file' => 'class-wpcom-api-request-faker-module.php', + 'name' => 'WPCOM API Request Faker', + 'description' => 'Send custom requests to the WPcom API, authorized via your Jetpack connection.', + ), 'xmlrpc-logger' => array( 'file' => 'class-xmlrpc-logger.php', 'name' => 'XMLRPC Logger', diff --git a/projects/plugins/jetpack/changelog/add-sharing-safeguard-post b/projects/plugins/jetpack/changelog/add-sharing-safeguard-post new file mode 100644 index 0000000000000..c8ba163b65325 --- /dev/null +++ b/projects/plugins/jetpack/changelog/add-sharing-safeguard-post @@ -0,0 +1,4 @@ +Significance: patch +Type: bugfix + +Sharing: Fix possible warnings related to plugin compatibility. diff --git a/projects/plugins/jetpack/changelog/enhance-rename-copy-post-duplicate b/projects/plugins/jetpack/changelog/enhance-rename-copy-post-duplicate new file mode 100644 index 0000000000000..bdf2dcf1ee879 --- /dev/null +++ b/projects/plugins/jetpack/changelog/enhance-rename-copy-post-duplicate @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + +Post actions: rename Copy action to Duplicate, which is clearer diff --git a/projects/plugins/jetpack/changelog/fix-25025-form-seperator b/projects/plugins/jetpack/changelog/fix-25025-form-seperator new file mode 100644 index 0000000000000..9814b5a45b694 --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-25025-form-seperator @@ -0,0 +1,4 @@ +Significance: patch +Type: bugfix + +Forms: Improve the styling of the separator block when placed inside the form block diff --git a/projects/plugins/jetpack/changelog/fix-form-submit-miulti-page b/projects/plugins/jetpack/changelog/fix-form-submit-miulti-page new file mode 100644 index 0000000000000..444f11caf4071 --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-form-submit-miulti-page @@ -0,0 +1,4 @@ +Significance: patch +Type: enhancement + + Forms: Add support for having multiple forms accross paginated pages. diff --git a/projects/plugins/jetpack/changelog/fix-invalid-field-ids b/projects/plugins/jetpack/changelog/fix-invalid-field-ids new file mode 100644 index 0000000000000..b7c244efa48bf --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-invalid-field-ids @@ -0,0 +1,4 @@ +Significance: patch +Type: bugfix + +Forms: Fix invalid html IDs. diff --git a/projects/plugins/jetpack/changelog/fix-jetpack-seo-assistant-chat-spacings b/projects/plugins/jetpack/changelog/fix-jetpack-seo-assistant-chat-spacings new file mode 100644 index 0000000000000..c0b75e32269f6 --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-jetpack-seo-assistant-chat-spacings @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Jetpack SEO: fix gap/spacing between chat bubbles and options diff --git a/projects/plugins/jetpack/changelog/update-dont-sync-set-object-terms-action-for-blacklisted-taxonomies b/projects/plugins/jetpack/changelog/update-dont-sync-set-object-terms-action-for-blacklisted-taxonomies new file mode 100644 index 0000000000000..76e66c9980175 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-dont-sync-set-object-terms-action-for-blacklisted-taxonomies @@ -0,0 +1,4 @@ +Significance: minor +Type: other + +Sync: Full sync for posts not sending term relationships diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/style.scss b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/style.scss index 0acaea3025d52..8b1b0d1c86dab 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/style.scss +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/style.scss @@ -63,6 +63,7 @@ &__content { flex: 1 1 auto; + gap: 8px; display: flex; flex-direction: column; height: 100%; @@ -78,11 +79,24 @@ flex: 1 1 auto; display: flex; flex-direction: column; - gap: 16px; - padding: 8px 8px 8px 0; + gap: 8px; + padding: 0 8px; overflow-y: auto; scroll-behavior: smooth; align-items: flex-start; + + // weirdly placed here, this makes the first option have a top margin + & > .assistant-wizard__message.is-option { + margin-top: 8px; + } + + & > .assistant-wizard__message.is-option ~ .assistant-wizard__message.is-option { + margin-top: 0px; + } + + & > .assistant-wizard__message.is-option:last-of-type { + margin-bottom: 8px; + } } &__message { @@ -94,6 +108,11 @@ align-items: center; max-width: 255px; + &:not( .is-option ) { + margin-top: 8px; + margin-bottom: 8px; + } + .assistant-wizard__message-icon { flex-shrink: 0; align-self: baseline; @@ -122,6 +141,10 @@ display: none; } } + + &.is-option { + align-self: flex-start; + } } &__input-container { diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/wizard-messages.tsx b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/wizard-messages.tsx index cbfbdd0f2e91d..a8f205478a7c4 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/wizard-messages.tsx +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/wizard-messages.tsx @@ -69,6 +69,7 @@ export const MessageBubble = ( { message, onSelect = ( m: Message ) => m } ) =>
diff --git a/projects/plugins/jetpack/modules/copy-post.php b/projects/plugins/jetpack/modules/copy-post.php index 38afc654a5a24..88f3e9c26f9a9 100644 --- a/projects/plugins/jetpack/modules/copy-post.php +++ b/projects/plugins/jetpack/modules/copy-post.php @@ -344,8 +344,8 @@ public function add_row_action( $actions, $post ) { 'jetpack-copy' => sprintf( '%3$s %4$s', esc_url( $edit_url ), - esc_attr__( 'Copy this post with Jetpack', 'jetpack' ), - esc_html__( 'Copy', 'jetpack' ), + esc_attr__( 'Duplicate this post with Jetpack.', 'jetpack' ), + esc_html__( 'Duplicate', 'jetpack' ), $jetpack_logo->get_jp_emblem() ), ); diff --git a/projects/plugins/jetpack/modules/sharedaddy/sharing-service.php b/projects/plugins/jetpack/modules/sharedaddy/sharing-service.php index db9a37dac1216..d726ec387adc3 100644 --- a/projects/plugins/jetpack/modules/sharedaddy/sharing-service.php +++ b/projects/plugins/jetpack/modules/sharedaddy/sharing-service.php @@ -960,7 +960,8 @@ function sharing_display( $text = '', $echo = false ) { return $text; } - if ( empty( $post ) ) { + // We require the post to not be empty and be an actual WordPress post object. If it's not - we just return. + if ( empty( $post ) || ! $post instanceof \WP_Post ) { return $text; } diff --git a/projects/plugins/jetpack/tests/php/sync/test_class.jetpack-sync-full-immediately.php b/projects/plugins/jetpack/tests/php/sync/test_class.jetpack-sync-full-immediately.php index 4a83a9127a939..93adbe246721a 100644 --- a/projects/plugins/jetpack/tests/php/sync/test_class.jetpack-sync-full-immediately.php +++ b/projects/plugins/jetpack/tests/php/sync/test_class.jetpack-sync-full-immediately.php @@ -679,24 +679,6 @@ public function test_full_sync_doesnt_sends_forbiden_private_or_public_post_meta $this->assertEquals( 'foo5', $this->server_replica_storage->get_metadata( 'post', $post_id, 'a_public_meta', true ) ); } - public function test_full_sync_sends_all_post_terms() { - $post_id = self::factory()->post->create(); - wp_set_object_terms( $post_id, 'tag', 'post_tag' ); - - $this->sender->do_sync(); - $terms = get_the_terms( $post_id, 'post_tag' ); - - $this->assertEqualsObject( $terms, $this->server_replica_storage->get_the_terms( $post_id, 'post_tag' ), 'Initial sync doesn\'t work' ); - // reset the storage, check value, and do full sync - storage should be set! - $this->server_replica_storage->reset(); - - $this->assertFalse( $this->server_replica_storage->get_the_terms( $post_id, 'post_tag' ), 'Not empty' ); - $this->full_sync->start(); - $this->sender->do_full_sync(); - - $this->assertEqualsObject( $terms, $this->server_replica_storage->get_the_terms( $post_id, 'post_tag' ), 'Full sync doesn\'t work' ); - } - public function test_full_sync_sends_all_comment_meta() { $post_id = self::factory()->post->create(); $comment_ids = self::factory()->comment->create_post_comments( $post_id ); diff --git a/projects/plugins/wpcomsh/changelog/fix-warnings-on-any-theme-using-customizer-colors b/projects/plugins/wpcomsh/changelog/fix-warnings-on-any-theme-using-customizer-colors new file mode 100644 index 0000000000000..da207bfd8be38 --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/fix-warnings-on-any-theme-using-customizer-colors @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Fix: Undefined array key warnings on any customizer theme with colors. diff --git a/projects/plugins/wpcomsh/changelog/wpcomsh-update-media-notice b/projects/plugins/wpcomsh/changelog/wpcomsh-update-media-notice new file mode 100644 index 0000000000000..69cc7c5cb8c32 --- /dev/null +++ b/projects/plugins/wpcomsh/changelog/wpcomsh-update-media-notice @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Media Library: don't show storage info on Atomic upload.php's uploader diff --git a/projects/plugins/wpcomsh/custom-colors/colors.php b/projects/plugins/wpcomsh/custom-colors/colors.php index b2a4405482e8d..15f56c8ee244c 100644 --- a/projects/plugins/wpcomsh/custom-colors/colors.php +++ b/projects/plugins/wpcomsh/custom-colors/colors.php @@ -1421,8 +1421,12 @@ public static function override_themecolors() { $colors = $opts['colors']; - $colors['border'] = $colors['fg1']; - $colors['url'] = $colors['link']; + if ( isset( $colors['fg1'] ) ) { + $colors['border'] = $colors['fg1']; + } + if ( isset( $colors['link'] ) ) { + $colors['url'] = $colors['link']; + } if ( isset( $colors['txt'] ) ) { $colors['text'] = $colors['txt']; } diff --git a/projects/plugins/wpcomsh/notices/storage-notices.php b/projects/plugins/wpcomsh/notices/storage-notices.php index 8ceeb3297c3b3..09ee42a365989 100644 --- a/projects/plugins/wpcomsh/notices/storage-notices.php +++ b/projects/plugins/wpcomsh/notices/storage-notices.php @@ -59,9 +59,15 @@ function wpcomsh_storage_notices() { add_action( 'admin_notices', 'wpcomsh_storage_notices' ); /** - * Display disk space usage on /wp-admin/upload.php + * Display disk space usage on the uploader */ function wpcomsh_display_disk_space_usage() { + global $pagenow; + + if ( $pagenow === 'upload.php' ) { + return; + } + $site_info = wpcomsh_get_at_site_info(); if ( empty( $site_info['space_used'] ) || empty( $site_info['space_quota'] ) ) { diff --git a/tools/cli/helpers/doc-parser/src/class-doc-parser.php b/tools/cli/helpers/doc-parser/src/class-doc-parser.php index f4014d0cdff8c..83d7002d6b79d 100644 --- a/tools/cli/helpers/doc-parser/src/class-doc-parser.php +++ b/tools/cli/helpers/doc-parser/src/class-doc-parser.php @@ -243,9 +243,7 @@ function ( Node $node ) { if ( ! empty( $entry->text ) ) { $block['doc']['description'] .= - '

' - . str_replace( array( "\r\n", "\n", "\r" ), '

', $entry->text ) - . '

'; + str_replace( array( "\r\n", "\n", "\r" ), ' ', $entry->text ); } }