diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3f0f71bad630e..1b21622778fda 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2112,6 +2112,9 @@ importers:
projects/packages/jetpack-mu-wpcom:
dependencies:
+ '@automattic/color-studio':
+ specifier: 2.6.0
+ version: 2.6.0
'@automattic/i18n-utils':
specifier: 1.2.3
version: 1.2.3
@@ -2121,6 +2124,9 @@ importers:
'@automattic/jetpack-shared-extension-utils':
specifier: workspace:*
version: link:../../js-packages/shared-extension-utils
+ '@automattic/page-pattern-modal':
+ specifier: 1.1.5
+ version: 1.1.5(@types/react@18.3.1)(@wordpress/data@10.2.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1)
'@automattic/typography':
specifier: 1.0.0
version: 1.0.0
@@ -2175,6 +2181,9 @@ importers:
preact:
specifier: ^10.13.1
version: 10.22.1
+ redux:
+ specifier: ^4.2.1
+ version: 4.2.1
wpcom-proxy-request:
specifier: ^7.0.3
version: 7.0.5
@@ -4874,6 +4883,13 @@ packages:
'@automattic/languages@1.0.0':
resolution: {integrity: sha512-froTyDbTmLitHkvY9WLCpFdjUo6moOLkDKw63J2fLiB2gBApy2thkBV+LRx4Z0kIF5iXVkQF4yYOPYkT9Sr13Q==}
+ '@automattic/page-pattern-modal@1.1.5':
+ resolution: {integrity: sha512-cFA82qWUDSSFhOHfOkOqh7X8I9As5fNGp7w3LVw7ZDRl6wSiQZveLvWp4msNDnLmeiJTpxWVOZWvCirxYUE3Sw==}
+ peerDependencies:
+ '@wordpress/data': ^10.2.0
+ react: ^18.2.0
+ redux: ^4.2.1
+
'@automattic/popup-monitor@1.0.2':
resolution: {integrity: sha512-Y4LMfdkV8iDmezu/7Ov/18JaFJ0QAy5vCntiP0S5AhLt4R/kjLtBt4ifNXNbdKTthGxlL17+LJ1bNtHBVCzPwg==}
@@ -14631,6 +14647,29 @@ snapshots:
dependencies:
tslib: 2.5.0
+ '@automattic/page-pattern-modal@1.1.5(@types/react@18.3.1)(@wordpress/data@10.2.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1)':
+ dependencies:
+ '@automattic/color-studio': 2.6.0
+ '@automattic/typography': 1.0.0
+ '@wordpress/base-styles': 5.2.0
+ '@wordpress/block-editor': 13.2.0(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/blocks': 13.2.0(react@18.3.1)
+ '@wordpress/components': 28.2.0(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@wordpress/compose': 7.2.0(react@18.3.1)
+ '@wordpress/data': 10.2.0(react@18.3.1)
+ '@wordpress/element': 6.2.0
+ '@wordpress/i18n': 5.2.0
+ clsx: 2.1.1
+ debug: 4.3.4
+ lodash: 4.17.21
+ react: 18.3.1
+ redux: 4.2.1
+ transitivePeerDependencies:
+ - '@emotion/is-prop-valid'
+ - '@types/react'
+ - react-dom
+ - supports-color
+
'@automattic/popup-monitor@1.0.2':
dependencies:
events: 3.3.0
@@ -18470,7 +18509,7 @@ snapshots:
'@types/hoist-non-react-statics': 3.3.5
'@types/react': 18.3.1
hoist-non-react-statics: 3.3.2
- redux: 4.1.1
+ redux: 4.2.1
'@types/react-router-dom@5.3.3':
dependencies:
diff --git a/projects/packages/jetpack-mu-wpcom/changelog/port-starter-page-templates b/projects/packages/jetpack-mu-wpcom/changelog/port-starter-page-templates
new file mode 100644
index 0000000000000..276b2bc5a4f60
--- /dev/null
+++ b/projects/packages/jetpack-mu-wpcom/changelog/port-starter-page-templates
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+MU WPCOM: Port the starter-page-templates feature from ETK
diff --git a/projects/packages/jetpack-mu-wpcom/composer.json b/projects/packages/jetpack-mu-wpcom/composer.json
index 14e966c6f40de..3bd1049903806 100644
--- a/projects/packages/jetpack-mu-wpcom/composer.json
+++ b/projects/packages/jetpack-mu-wpcom/composer.json
@@ -62,7 +62,7 @@
},
"autotagger": true,
"branch-alias": {
- "dev-trunk": "5.52.x-dev"
+ "dev-trunk": "5.53.x-dev"
},
"textdomain": "jetpack-mu-wpcom",
"version-constants": {
diff --git a/projects/packages/jetpack-mu-wpcom/package.json b/projects/packages/jetpack-mu-wpcom/package.json
index 82fb56fe87d3b..539a12260bb60 100644
--- a/projects/packages/jetpack-mu-wpcom/package.json
+++ b/projects/packages/jetpack-mu-wpcom/package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@automattic/jetpack-mu-wpcom",
- "version": "5.52.1-alpha",
+ "version": "5.53.0-alpha",
"description": "Enhances your site with features powered by WordPress.com",
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/packages/jetpack-mu-wpcom/#readme",
"bugs": {
@@ -45,10 +45,12 @@
"webpack-cli": "4.9.1"
},
"dependencies": {
+ "@automattic/color-studio": "2.6.0",
"@automattic/i18n-utils": "1.2.3",
"@automattic/jetpack-base-styles": "workspace:*",
"@automattic/jetpack-shared-extension-utils": "workspace:*",
"@automattic/typography": "1.0.0",
+ "@automattic/page-pattern-modal": "1.1.5",
"@preact/signals": "^1.2.2",
"@sentry/browser": "7.80.1",
"@tanstack/react-query": "^5.15.5",
@@ -68,6 +70,7 @@
"preact": "^10.13.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "redux": "^4.2.1",
"wpcom-proxy-request": "^7.0.3"
}
}
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 4867ca06ca8fc..c1cbcf570f1d7 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
@@ -13,7 +13,7 @@
* Jetpack_Mu_Wpcom main class.
*/
class Jetpack_Mu_Wpcom {
- const PACKAGE_VERSION = '5.52.1-alpha';
+ const PACKAGE_VERSION = '5.53.0-alpha';
const PKG_DIR = __DIR__ . '/../';
const BASE_DIR = __DIR__ . '/';
const BASE_FILE = __FILE__;
@@ -146,7 +146,7 @@ public static function load_wpcom_user_features() {
}
/**
- * Laod ETK features that need higher priority than the ETK plugin.
+ * Load ETK features that need higher priority than the ETK plugin.
* Can be moved back to load_features() once the feature no longer exists in the ETK plugin.
*/
public static function load_etk_features() {
@@ -165,6 +165,7 @@ public static function load_etk_features() {
require_once __DIR__ . '/features/wpcom-documentation-links/wpcom-documentation-links.php';
require_once __DIR__ . '/features/wpcom-global-styles/index.php';
require_once __DIR__ . '/features/wpcom-whats-new/wpcom-whats-new.php';
+ require_once __DIR__ . '/features/starter-page-templates/class-starter-page-templates.php';
}
/**
diff --git a/projects/packages/jetpack-mu-wpcom/src/features/block-patterns/class-wpcom-block-patterns-from-api.php b/projects/packages/jetpack-mu-wpcom/src/features/block-patterns/class-wpcom-block-patterns-from-api.php
index a7d0de0e6299f..78a31b75cfeb9 100644
--- a/projects/packages/jetpack-mu-wpcom/src/features/block-patterns/class-wpcom-block-patterns-from-api.php
+++ b/projects/packages/jetpack-mu-wpcom/src/features/block-patterns/class-wpcom-block-patterns-from-api.php
@@ -103,7 +103,7 @@ public function register_patterns() {
// We prefer to show the starter page patterns modal of wpcom instead of core
// if it's available. Hence, we have to update the block types of patterns
// to disable the core's.
- if ( class_exists( '\A8C\FSE\Starter_Page_Templates' ) ) {
+ if ( class_exists( '\A8C\FSE\Starter_Page_Templates' ) || class_exists( '\Automattic\Jetpack\Jetpack_Mu_Wpcom\Starter_Page_Templates' ) ) {
$this->update_pattern_block_types();
}
diff --git a/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/class-starter-page-templates.php b/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/class-starter-page-templates.php
new file mode 100644
index 0000000000000..395c21e967e24
--- /dev/null
+++ b/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/class-starter-page-templates.php
@@ -0,0 +1,448 @@
+get_templates_cache_key() getter.
+ *
+ * @var string
+ */
+ public $templates_cache_key = 'starter_page_templates';
+
+ /**
+ * Starter_Page_Templates constructor.
+ */
+ private function __construct() {
+ // We don't want the user to choose a template when copying a post.
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( isset( $_GET['jetpack-copy'] ) ) {
+ return;
+ }
+
+ /**
+ * Can be used to disable the Starter Page Templates.
+ *
+ * @param bool true if Starter Page Templates should be disabled, false otherwise.
+ */
+ if ( apply_filters( 'a8c_disable_starter_page_templates', false ) ) {
+ return;
+ }
+
+ // Register post metas for Launchpad newsletter task and template tracking
+ add_action( 'init', array( $this, 'register_meta_field' ) );
+ // Enqueue scripts and pass templates in a global JS variable
+ add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_assets' ) );
+ // Sideload images to add them to the Media Library for Gallery block images to work
+ add_action( 'rest_api_init', array( $this, 'register_rest_api' ) );
+ // Clean caches
+ add_action( 'delete_attachment', array( $this, 'clear_sideloaded_image_cache' ) );
+ add_action( 'switch_theme', array( $this, 'clear_templates_cache' ) );
+ // Handle styles for classic themes
+ add_action( 'block_editor_settings_all', array( $this, 'add_default_editor_styles_for_classic_themes' ), 10, 2 );
+ }
+
+ /**
+ * Gets the cache key for templates array.
+ *
+ * @param string $locale The templates locale.
+ *
+ * @return string
+ */
+ public function get_templates_cache_key( string $locale ) {
+ return $this->templates_cache_key . '_' . $locale;
+ }
+
+ /**
+ * Creates instance.
+ *
+ * @return \Automattic\Jetpack\Jetpack_Mu_Wpcom\Starter_Page_Templates
+ */
+ public static function get_instance() {
+ if ( null === self::$instance ) {
+ self::$instance = new self();
+ }
+
+ return self::$instance;
+ }
+
+ /**
+ * Register meta field for storing the template identifier.
+ */
+ public function register_meta_field() {
+ $args = array(
+ 'type' => 'string',
+ 'description' => 'Selected template',
+ 'single' => true,
+ 'show_in_rest' => true,
+ 'object_subtype' => 'page',
+ 'auth_callback' => function () {
+ return current_user_can( 'edit_posts' );
+ },
+ );
+ register_meta( 'post', '_starter_page_template', $args );
+
+ $args = array(
+ 'type' => 'array',
+ 'description' => 'Selected category',
+ 'show_in_rest' => array(
+ 'schema' => array(
+ 'type' => 'array',
+ 'items' => array(
+ 'type' => 'string',
+ ),
+ ),
+ ),
+ 'single' => true,
+ 'object_subtype' => 'page',
+ 'auth_callback' => function () {
+ return current_user_can( 'edit_pages' );
+ },
+ 'sanitize_callback' => function ( $meta_value ) {
+ if ( ! is_array( $meta_value ) ) {
+ return array();
+ }
+
+ if ( ! class_exists( '\Automattic\Jetpack\Jetpack_Mu_Wpcom\Starter_Page_Templates' ) ) {
+ return array();
+ }
+
+ $starter_page_templates = \Automattic\Jetpack\Jetpack_Mu_Wpcom\Starter_Page_Templates::get_instance();
+ // We need to pass a locale in here, but we don't actually depend on it, so we use the default site locale to optimise hitting the pattern cache for the site.
+ $all_page_templates = $starter_page_templates->get_page_templates( $starter_page_templates->get_verticals_locale() );
+ $all_categories = array_merge( ...array_map( 'array_keys', wp_list_pluck( $all_page_templates, 'categories' ) ) );
+
+ $unique_categories = array_unique( $all_categories );
+
+ // Only permit values that are valid categories.
+ return array_intersect( $meta_value, $unique_categories );
+ },
+ );
+ register_meta( 'post', '_wpcom_template_layout_category', $args );
+ }
+
+ /**
+ * Register rest api endpoint for side-loading images.
+ */
+ public function register_rest_api() {
+ require_once __DIR__ . '/class-wp-rest-sideload-image-controller.php';
+
+ ( new WP_REST_Sideload_Image_Controller() )->register_routes();
+ }
+
+ /**
+ * Pass error message to frontend JavaScript console.
+ *
+ * @param string $message Error message.
+ */
+ public function pass_error_to_frontend( $message ) {
+ wp_register_script(
+ 'starter-page-templates-error',
+ false,
+ array(),
+ '1.O',
+ true
+ );
+ wp_add_inline_script(
+ 'starter-page-templates-error',
+ sprintf(
+ 'console.warn(%s);',
+ wp_json_encode( $message )
+ )
+ );
+ wp_enqueue_script( 'starter-page-templates-error' );
+ }
+
+ /**
+ * Enqueue block editor assets.
+ */
+ public function enqueue_assets() {
+ $screen = get_current_screen();
+ $user_locale = Common\get_iso_639_locale( get_user_locale() );
+
+ // Return early if we don't meet conditions to show templates.
+ if ( 'page' !== $screen->id ) {
+ return;
+ }
+
+ // Load templates for this site.
+ $page_templates = $this->get_page_templates( $this->get_verticals_locale() );
+ if ( $user_locale !== $this->get_verticals_locale() ) {
+ // If the user locale is not the blog locale, we should show labels in the user locale.
+ $user_page_templates_indexed = array();
+ $user_page_templates = $this->get_page_templates( $user_locale );
+ foreach ( $user_page_templates as $page_template ) {
+ if ( ! empty( $page_template['ID'] ) ) {
+ $user_page_templates_indexed[ $page_template['ID'] ] = $page_template;
+ }
+ }
+ foreach ( $page_templates as $key => $page_template ) {
+ if ( isset( $user_page_templates_indexed[ $page_template['ID'] ]['categories'] ) ) {
+ $page_templates[ $key ]['categories'] = $user_page_templates_indexed[ $page_template['ID'] ]['categories'];
+ }
+ if ( isset( $user_page_templates_indexed[ $page_template['ID'] ]['description'] ) ) {
+ $page_templates[ $key ]['description'] = $user_page_templates_indexed[ $page_template['ID'] ]['description'];
+ }
+ }
+ }
+
+ // Hide non-user-facing categories (Pages, Virtual Theme, and wordpress.com/patterns homepage) in modal
+ $hidden_categories = array( 'page', 'virtual-theme', '_public_library_homepage' );
+ foreach ( $page_templates as &$page_template ) {
+ if ( ! isset( $page_template['categories'] ) ) {
+ continue;
+ }
+ foreach ( $page_template['categories'] as $category ) {
+ if ( in_array( $category['slug'], $hidden_categories, true ) ) {
+ unset( $page_template['categories'][ $category['slug'] ] );
+ }
+ }
+ }
+
+ if ( empty( $page_templates ) ) {
+ $this->pass_error_to_frontend( __( 'No templates available. Skipped showing modal window with template selection.', 'jetpack-mu-wpcom' ) );
+ return;
+ }
+
+ $handle = jetpack_mu_wpcom_enqueue_assets( 'starter-page-templates', array( 'js', 'css' ) );
+ wp_set_script_translations( $handle, 'jetpack-mu-wpcom' );
+
+ $default_templates = array(
+ array(
+ 'ID' => null,
+ 'title' => __( 'Blank', 'jetpack-mu-wpcom' ),
+ 'name' => 'blank',
+ ),
+ array(
+ 'ID' => null,
+ 'title' => __( 'Current', 'jetpack-mu-wpcom' ),
+ 'name' => 'current',
+ ),
+ );
+
+ $registered_page_templates = $this->get_registered_page_templates();
+
+ /**
+ * Filters the config before it's passed to the frontend.
+ *
+ * @param array $config The config.
+ */
+ $config = apply_filters(
+ 'fse_starter_page_templates_config',
+ array(
+ 'templates' => array_merge( $default_templates, $registered_page_templates, $page_templates ),
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ 'screenAction' => isset( $_GET['new-homepage'] ) ? 'add' : $screen->action,
+ )
+ );
+
+ $config = wp_json_encode( $config );
+
+ wp_add_inline_script(
+ $handle,
+ "var starterPageTemplatesConfig = $config;",
+ 'before'
+ );
+ }
+
+ /**
+ * Get page templates from the patterns API or return cached version if available.
+ *
+ * @param string $locale The templates locale.
+ *
+ * @return array Containing page templates or nothing if an error occurred.
+ */
+ public function get_page_templates( string $locale ) {
+ $page_template_data = get_transient( $this->get_templates_cache_key( $locale ) );
+ $override_source_site = apply_filters( 'a8c_override_patterns_source_site', false );
+ $disable_cache = function_exists( 'is_automattician' ) && is_automattician() || false !== $override_source_site || ( defined( 'WP_DISABLE_PATTERN_CACHE' ) && WP_DISABLE_PATTERN_CACHE );
+
+ // Load fresh data if is automattician or we don't have any data.
+ if ( $disable_cache || false === $page_template_data ) {
+ $request_url = esc_url_raw(
+ add_query_arg(
+ array(
+ 'site' => $override_source_site ?? 'dotcompatterns.wordpress.com',
+ 'categories' => 'page',
+ 'post_type' => 'wp_block',
+ ),
+ 'https://public-api.wordpress.com/rest/v1/ptk/patterns/' . $locale
+ )
+ );
+
+ $args = array( 'timeout' => 20 );
+
+ if ( function_exists( 'wpcom_json_api_get' ) ) {
+ $response = wpcom_json_api_get( $request_url, $args );
+ } else {
+ $response = wp_remote_get( $request_url, $args );
+ }
+
+ if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
+ return array();
+ }
+
+ $page_template_data = json_decode( wp_remote_retrieve_body( $response ), true );
+
+ // Only save to cache when is not disabled.
+ if ( ! $disable_cache ) {
+ set_transient( $this->get_templates_cache_key( $locale ), $page_template_data, 5 * MINUTE_IN_SECONDS );
+ }
+
+ return $page_template_data;
+ }
+
+ return $page_template_data;
+ }
+
+ /**
+ * Deletes cached attachment data when attachment gets deleted.
+ *
+ * @param int $id Attachment ID of the attachment to be deleted.
+ */
+ public function clear_sideloaded_image_cache( $id ) {
+ $url = get_post_meta( $id, '_sideloaded_url', true );
+ if ( ! empty( $url ) ) {
+ delete_transient( 'fse_sideloaded_image_' . hash( 'crc32b', $url ) );
+ }
+ }
+
+ /**
+ * Deletes cached templates data when theme switches.
+ */
+ public function clear_templates_cache() {
+ delete_transient( $this->get_templates_cache_key( $this->get_verticals_locale() ) );
+ }
+
+ /**
+ * Gets the locale to be used for fetching the site vertical
+ */
+ public function get_verticals_locale() {
+ // Make sure to get blog locale, not user locale.
+ $language = function_exists( 'get_blog_lang_code' ) ? get_blog_lang_code() : get_locale();
+ return Common\get_iso_639_locale( $language );
+ }
+
+ /**
+ * Gets the registered page templates
+ */
+ public function get_registered_page_templates() {
+ $registered_page_templates = array();
+
+ if ( class_exists( 'WP_Block_Patterns_Registry' ) ) {
+ $registered_categories = $this->get_registered_categories();
+ foreach ( \WP_Block_Patterns_Registry::get_instance()->get_all_registered() as $pattern ) {
+ if ( ! array_key_exists( 'blockTypes', $pattern ) ) {
+ continue;
+ }
+
+ $post_content_offset = array_search( 'core/post-content', $pattern['blockTypes'], true );
+ if ( $post_content_offset !== false ) {
+ $categories = array();
+ foreach ( $pattern['categories'] as $category ) {
+ $registered_category = $registered_categories[ $category ];
+ if ( $registered_category ) {
+ $categories[ $category ] = array(
+ 'slug' => $registered_category['name'],
+ 'title' => $registered_category['label'],
+ 'description' => $registered_category['description'],
+ );
+ }
+ }
+
+ $registered_page_templates[] = array(
+ 'ID' => null,
+ 'title' => $pattern['title'],
+ 'description' => $pattern['description'],
+ 'name' => $pattern['name'],
+ 'html' => $pattern['content'],
+ 'categories' => $categories,
+ );
+ }
+ }
+ }
+
+ return $registered_page_templates;
+ }
+
+ /**
+ * Gets the registered categories.
+ */
+ public function get_registered_categories() {
+ $registered_categories = array();
+
+ if ( class_exists( 'WP_Block_Pattern_Categories_Registry' ) ) {
+ foreach ( \WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered() as $category ) {
+ $registered_categories[ $category['name'] ] = $category;
+ }
+ }
+
+ return $registered_categories;
+ }
+
+ /**
+ * Fix for text overlapping on the page patterns preview on classic themes.
+ *
+ * @param array $editor_settings Editor settings.
+ * @param object $editor_context Editor context.
+ *
+ * Only for classic themes because the default styles for block themes include a line-height for the body.
+ * This issue would not exist if the WordPress wp-admin common.css for the body element (line-height: 1.4em)
+ * does not overwrite the Gutenberg block-library reset.css for .editor-styles-wrapper (line-height: normal).
+ *
+ * This fix adds the default editor styles as custom styles in the editor settings. These are used in the
+ * editor canvas (.editor-styles-wrapper) and pattern previews (BlockPreview).
+ * Custom styles are safe because they are overwritten by local block styles, global styles, or theme stylesheets.
+ **/
+ public function add_default_editor_styles_for_classic_themes( $editor_settings, $editor_context ) {
+ $theme = wp_get_theme( get_stylesheet() );
+ if ( $theme->is_block_theme() ) {
+ // Only for classic themes
+ return $editor_settings;
+ }
+ if ( 'core/edit-post' !== $editor_context->name || 'page' !== $editor_context->post->post_type ) {
+ // Only for page editor
+ return $editor_settings;
+ }
+ if ( ! function_exists( 'gutenberg_dir_path' ) ) {
+ return $editor_settings;
+ }
+
+ $default_editor_styles_file = gutenberg_dir_path() . 'build/block-editor/default-editor-styles.css'; // @phan-suppress-current-line PhanUndeclaredFunction
+ if ( ! file_exists( $default_editor_styles_file ) ) {
+ return $editor_settings;
+ }
+ $default_editor_styles = file_get_contents( $default_editor_styles_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
+
+ $editor_settings['styles'][] = array(
+ 'css' => $default_editor_styles,
+ );
+
+ return $editor_settings;
+ }
+}
+
+// Initialization
+\Automattic\Jetpack\Jetpack_Mu_Wpcom\Starter_Page_Templates::get_instance();
diff --git a/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/class-wp-rest-sideload-image-controller.php b/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/class-wp-rest-sideload-image-controller.php
new file mode 100644
index 0000000000000..ea547348d731b
--- /dev/null
+++ b/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/class-wp-rest-sideload-image-controller.php
@@ -0,0 +1,295 @@
+namespace = 'fse/v1';
+ $this->rest_base = 'sideload/image';
+ }
+
+ /**
+ * Register available routes.
+ */
+ public function register_routes() {
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base,
+ array(
+ array(
+ 'methods' => \WP_REST_Server::CREATABLE, // @phan-suppress-current-line PhanPluginMixedKeyNoKey
+ 'callback' => array( $this, 'create_item' ),
+ 'permission_callback' => array( $this, 'create_item_permissions_check' ),
+ 'show_in_index' => false,
+ 'args' => $this->get_collection_params(),
+ ),
+ 'schema' => array( $this, 'get_item_schema' ),
+ )
+ );
+
+ register_rest_route(
+ $this->namespace,
+ '/' . $this->rest_base . '/batch',
+ array(
+ array(
+ 'methods' => \WP_REST_Server::CREATABLE,
+ 'callback' => array( $this, 'create_items' ),
+ 'permission_callback' => array( $this, 'create_item_permissions_check' ),
+ 'show_in_index' => false,
+ 'args' => array(
+ 'resources' => array(
+ 'description' => 'URL to the image to be side-loaded.',
+ 'type' => 'array',
+ 'required' => true,
+ 'items' => array(
+ 'type' => 'object',
+ 'properties' => $this->get_collection_params(),
+ ),
+ ),
+ ),
+ ),
+ )
+ );
+ }
+
+ /**
+ * Creates a single attachment.
+ *
+ * @param \WP_REST_Request $request Full details about the request.
+ * @return \WP_Error|\WP_REST_Response Response object on success, WP_Error object on failure.
+ */
+ public function create_item( $request ) {
+ if ( ! empty( $request['post_id'] ) && in_array( get_post_type( $request['post_id'] ), array( 'revision', 'attachment' ), true ) ) {
+ return new \WP_Error( 'rest_invalid_param', __( 'Invalid parent type.', 'jetpack-mu-wpcom' ), array( 'status' => 400 ) );
+ }
+
+ $inserted = false;
+ $attachment = $this->get_attachment( $request->get_param( 'url' ) );
+ if ( ! $attachment ) {
+ // Include image functions to get access to wp_read_image_metadata().
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ require_once ABSPATH . 'wp-admin/includes/image.php';
+ require_once ABSPATH . 'wp-admin/includes/media.php';
+
+ // The post ID on success, WP_Error on failure.
+ $id = media_sideload_image(
+ $request->get_param( 'url' ),
+ $request->get_param( 'post_id' ),
+ '',
+ 'id'
+ );
+
+ if ( is_wp_error( $id ) ) {
+ if ( 'db_update_error' === $id->get_error_code() ) {
+ $id->add_data( array( 'status' => 500 ) );
+ } else {
+ $id->add_data( array( 'status' => 400 ) );
+ }
+
+ return rest_ensure_response( $id ); // Return error.
+ }
+
+ $attachment = get_post( $id );
+
+ /**
+ * Fires after a single attachment is created or updated via the REST API.
+ *
+ * @param WP_Post $attachment Inserted or updated attachment object.
+ * @param WP_REST_Request $request The request sent to the API.
+ * @param bool $creating True when creating an attachment, false when updating.
+ */
+ do_action( 'rest_insert_attachment', $attachment, $request, true );
+
+ if ( isset( $request['alt_text'] ) ) {
+ update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) );
+ }
+
+ update_post_meta( $id, '_sideloaded_url', $request->get_param( 'url' ) );
+
+ $fields_update = $this->update_additional_fields_for_object( $attachment, $request );
+
+ if ( is_wp_error( $fields_update ) ) {
+ return $fields_update;
+ }
+
+ $inserted = true;
+ $request->set_param( 'context', 'edit' );
+
+ /**
+ * Fires after a single attachment is completely created or updated via the REST API.
+ *
+ * @param WP_Post $attachment Inserted or updated attachment object.
+ * @param WP_REST_Request $request Request object.
+ * @param bool $creating True when creating an attachment, false when updating.
+ */
+ do_action( 'rest_after_insert_attachment', $attachment, $request, true );
+ }
+
+ $response = $this->prepare_item_for_response( $attachment, $request ); // @phan-suppress-current-line PhanTypeMismatchArgumentNullable
+ $response = rest_ensure_response( $response );
+ $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', 'wp/v2', 'media', $attachment->ID ) ) );
+
+ if ( $inserted ) {
+ $response->set_status( 201 );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Creates a batch of attachments.
+ *
+ * @param \WP_REST_Request $request Full details about the request.
+ * @return \WP_Error|\WP_REST_Response Response object on success, WP_Error object on failure.
+ */
+ public function create_items( $request ) {
+ $data = array();
+
+ // Foreach request specified in the requests param, run the endpoint.
+ foreach ( $request['resources'] as $resource ) {
+ $request = new \WP_REST_Request( 'POST', $this->get_item_route() );
+
+ // Add specified request parameters into the request.
+ foreach ( $resource as $param_name => $param_value ) {
+ $request->set_param( $param_name, $param_value );
+ }
+
+ $response = rest_do_request( $request );
+ $data[] = $this->prepare_for_collection( $response );
+ }
+
+ return rest_ensure_response( $data );
+ }
+
+ /**
+ * Prepare a response for inserting into a collection of responses.
+ *
+ * @param \WP_REST_Response $response Response object.
+ * @return array|\WP_REST_Response Response data, ready for insertion into collection data.
+ */
+ public function prepare_for_collection( $response ) {
+ if ( ! ( $response instanceof \WP_REST_Response ) ) {
+ return $response;
+ }
+
+ $data = (array) $response->get_data();
+ $server = rest_get_server();
+
+ if ( method_exists( $server, 'get_compact_response_links' ) ) {
+ $links = call_user_func( array( $server, 'get_compact_response_links' ), $response );
+ } else {
+ $links = call_user_func( array( $server, 'get_response_links' ), $response );
+ }
+
+ if ( ! empty( $links ) ) {
+ $data['_links'] = $links;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Prepares a single attachment output for response.
+ *
+ * @param \WP_Post $post Attachment object.
+ * @param \WP_REST_Request $request Request object.
+ * @return \WP_REST_Response Response object.
+ */
+ public function prepare_item_for_response( $post, $request ) {
+ $response = parent::prepare_item_for_response( $post, $request );
+ $base = 'wp/v2/media';
+
+ foreach ( array( 'self', 'collection', 'about' ) as $link ) {
+ $response->remove_link( $link );
+
+ }
+
+ $response->add_link( 'self', rest_url( trailingslashit( $base ) . $post->ID ) ); // @phan-suppress-current-line PhanAccessMethodInternal
+ $response->add_link( 'collection', rest_url( $base ) ); // @phan-suppress-current-line PhanAccessMethodInternal
+ $response->add_link( 'about', rest_url( 'wp/v2/types/' . $post->post_type ) ); // @phan-suppress-current-line PhanAccessMethodInternal
+
+ return $response;
+ }
+
+ /**
+ * Gets the attachment if an image has been sideloaded previously.
+ *
+ * @param string $url URL of the image to sideload.
+ * @return object|bool Attachment object on success, false on failure.
+ */
+ public function get_attachment( $url ) {
+ $cache_key = 'fse_sideloaded_image_' . hash( 'crc32b', $url );
+ $attachment = get_transient( $cache_key );
+
+ if ( false === $attachment ) {
+ $attachments = new \WP_Query(
+ array(
+ 'no_found_rows' => true,
+ 'posts_per_page' => 1,
+ 'post_status' => 'inherit',
+ 'post_type' => 'attachment',
+ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+ 'meta_query' => array(
+ array(
+ 'key' => '_sideloaded_url',
+ 'value' => $url,
+ ),
+ ),
+ )
+ );
+
+ if ( $attachments->have_posts() ) {
+ set_transient( $cache_key, $attachments->post );
+ }
+ }
+
+ return $attachment;
+ }
+
+ /**
+ * Returns the endpoints request parameters.
+ *
+ * @return array Request parameters.
+ */
+ public function get_collection_params() {
+ return array(
+ 'url' => array(
+ 'description' => 'URL to the image to be side-loaded.',
+ 'type' => 'string',
+ 'required' => true,
+ 'format' => 'uri',
+ 'sanitize_callback' => function ( $url ) {
+ return esc_url_raw( strtok( $url, '?' ) );
+ },
+ ),
+ 'post_id' => array(
+ 'description' => 'ID of the post to associate the image with',
+ 'type' => 'integer',
+ 'default' => 0,
+ ),
+ );
+ }
+
+ /**
+ * Returns the route to sideload a single image.
+ *
+ * @return string
+ */
+ public function get_item_route() {
+ return "/{$this->namespace}/{$this->rest_base}";
+ }
+}
diff --git a/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/index.scss b/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/index.scss
new file mode 100644
index 0000000000000..45002b1f0750e
--- /dev/null
+++ b/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/index.scss
@@ -0,0 +1,34 @@
+@import "@automattic/page-pattern-modal/src/styles/page-pattern-modal";
+
+.sidebar-modal-opener {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ .pattern-selector-item__label {
+ max-width: 300px;
+ }
+}
+
+.sidebar-modal-opener__warning-modal {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+.sidebar-modal-opener__warning-text {
+ max-width: 300px;
+ font-size: 1rem;
+ /* stylelint-disable-next-line declaration-property-unit-allowed-list */
+ line-height: 1.5rem;
+}
+
+.sidebar-modal-opener__warning-options {
+ float: right;
+ margin-top: 20px;
+
+ .components-button {
+ margin-left: 12px;
+ }
+}
diff --git a/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/index.tsx b/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/index.tsx
new file mode 100644
index 0000000000000..1f6422e776671
--- /dev/null
+++ b/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/index.tsx
@@ -0,0 +1,44 @@
+import { initializeTracksWithIdentity, PatternDefinition } from '@automattic/page-pattern-modal';
+import { dispatch } from '@wordpress/data';
+import { registerPlugin } from '@wordpress/plugins';
+import { PagePatternsPlugin } from './page-patterns-plugin';
+import { pageLayoutStore } from './store';
+import './index.scss';
+
+declare global {
+ interface Window {
+ starterPageTemplatesConfig?: {
+ templates?: PatternDefinition[];
+ screenAction?: string;
+ tracksUserData?: Parameters< typeof initializeTracksWithIdentity >[ 0 ];
+ };
+ }
+}
+
+// Load config passed from backend.
+const {
+ templates: patterns = [],
+ tracksUserData,
+ screenAction,
+} = window.starterPageTemplatesConfig ?? {};
+
+if ( tracksUserData ) {
+ initializeTracksWithIdentity( tracksUserData );
+}
+
+// Open plugin only if we are creating new page.
+if ( screenAction === 'add' ) {
+ dispatch( pageLayoutStore ).setOpenState( 'OPEN_FROM_ADD_PAGE' );
+}
+
+// Always register ability to open from document sidebar.
+registerPlugin( 'page-patterns', {
+ render: () => {
+ return ;
+ },
+
+ // `registerPlugin()` types assume `icon` is mandatory however it isn't
+ // actually required.
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ icon: undefined as any,
+} );
diff --git a/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/page-patterns-plugin.tsx b/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/page-patterns-plugin.tsx
new file mode 100644
index 0000000000000..0eb6d3f32e52d
--- /dev/null
+++ b/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/page-patterns-plugin.tsx
@@ -0,0 +1,134 @@
+import { PagePatternModal, PatternDefinition } from '@automattic/page-pattern-modal';
+import { useSelect, useDispatch } from '@wordpress/data';
+import { useCallback } from '@wordpress/element';
+import { addFilter, removeFilter } from '@wordpress/hooks';
+import { __ } from '@wordpress/i18n';
+import { pageLayoutStore } from './store';
+import '@wordpress/nux';
+
+const INSERTING_HOOK_NAME = 'isInsertingPagePattern';
+const INSERTING_HOOK_NAMESPACE = 'automattic/full-site-editing/inserting-pattern';
+
+interface PagePatternsPluginProps {
+ patterns: PatternDefinition[];
+}
+type CoreEditorPlaceholder = {
+ getBlocks: ( ...args: unknown[] ) => Array< { name: string; clientId: string } >;
+ getEditedPostAttribute: ( ...args: unknown[] ) => unknown;
+};
+type CoreEditPostPlaceholder = {
+ isFeatureActive: ( ...args: unknown[] ) => boolean;
+};
+type CoreNuxPlaceholder = {
+ areTipsEnabled: ( ...args: unknown[] ) => boolean;
+};
+
+/**
+ * Starter page templates feature plugin
+ *
+ * @param props - An object that receives the page patterns
+ */
+export function PagePatternsPlugin( props: PagePatternsPluginProps ) {
+ const { setOpenState } = useDispatch( pageLayoutStore );
+ const { setUsedPageOrPatternsModal } = useDispatch( 'automattic/wpcom-welcome-guide' );
+ const { replaceInnerBlocks } = useDispatch( 'core/block-editor' );
+ const { editPost } = useDispatch( 'core/editor' );
+ const { toggleFeature } = useDispatch( 'core/edit-post' );
+ const { disableTips } = useDispatch( 'core/nux' );
+
+ const selectProps = useSelect( select => {
+ const { isOpen, isPatternPicker } = select( pageLayoutStore );
+ return {
+ isOpen: isOpen(),
+ isWelcomeGuideActive: (
+ select( 'core/edit-post' ) as CoreEditPostPlaceholder
+ ).isFeatureActive( 'welcomeGuide' ) as boolean, // Gutenberg 7.2.0 or higher
+ areTipsEnabled: select( 'core/nux' )
+ ? ( ( select( 'core/nux' ) as CoreNuxPlaceholder ).areTipsEnabled() as boolean )
+ : false, // Gutenberg 7.1.0 or lower
+ ...( isPatternPicker() && {
+ title: __( 'Choose a Pattern', 'jetpack-mu-wpcom' ),
+ description: __(
+ 'Pick a pre-defined layout or continue with a blank page',
+ 'jetpack-mu-wpcom'
+ ),
+ } ),
+ };
+ }, [] );
+
+ const { getMeta, postContentBlock } = useSelect( select => {
+ const getMetaNew = () =>
+ ( select( 'core/editor' ) as CoreEditorPlaceholder ).getEditedPostAttribute( 'meta' );
+ const currentBlocks = ( select( 'core/editor' ) as CoreEditorPlaceholder ).getBlocks();
+ return {
+ getMeta: getMetaNew,
+ postContentBlock: currentBlocks.find( block => block.name === 'a8c/post-content' ),
+ };
+ }, [] );
+
+ const savePatternChoice = useCallback(
+ ( name: string, selectedCategory: string | null ) => {
+ // Save selected pattern slug in meta.
+ const currentMeta = getMeta() as Record< string, unknown >;
+ const currentCategory =
+ ( Array.isArray( currentMeta._wpcom_template_layout_category ) &&
+ currentMeta._wpcom_template_layout_category ) ||
+ [];
+ editPost( {
+ meta: {
+ ...currentMeta,
+ _starter_page_template: name,
+ _wpcom_template_layout_category: [ ...currentCategory, selectedCategory ],
+ },
+ } );
+ },
+ [ editPost, getMeta ]
+ );
+
+ const insertPattern = useCallback(
+ ( title: string | null, blocks: unknown[] ) => {
+ // Add filter to let the tracking library know we are inserting a template.
+ addFilter( INSERTING_HOOK_NAME, INSERTING_HOOK_NAMESPACE, () => true );
+
+ // Set post title.
+ if ( title ) {
+ editPost( { title } );
+ }
+
+ // Replace blocks.
+ replaceInnerBlocks( postContentBlock ? postContentBlock.clientId : '', blocks, false );
+
+ // Remove filter.
+ removeFilter( INSERTING_HOOK_NAME, INSERTING_HOOK_NAMESPACE );
+ },
+ [ editPost, postContentBlock, replaceInnerBlocks ]
+ );
+
+ const { isWelcomeGuideActive, areTipsEnabled } = selectProps;
+
+ const hideWelcomeGuide = useCallback( () => {
+ if ( isWelcomeGuideActive ) {
+ // Gutenberg 7.2.0 or higher.
+ toggleFeature( 'welcomeGuide' );
+ } else if ( areTipsEnabled ) {
+ // Gutenberg 7.1.0 or lower.
+ disableTips();
+ }
+ }, [ areTipsEnabled, disableTips, isWelcomeGuideActive, toggleFeature ] );
+
+ const handleClose = useCallback( () => {
+ setOpenState( 'CLOSED' );
+ setUsedPageOrPatternsModal?.();
+ }, [ setOpenState, setUsedPageOrPatternsModal ] );
+
+ return (
+
+ );
+}
diff --git a/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/store.ts b/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/store.ts
new file mode 100644
index 0000000000000..93bcaf2542eac
--- /dev/null
+++ b/projects/packages/jetpack-mu-wpcom/src/features/starter-page-templates/store.ts
@@ -0,0 +1,25 @@
+import { register, createReduxStore } from '@wordpress/data';
+
+type OpenState = 'CLOSED' | 'OPEN_FROM_ADD_PAGE' | 'OPEN_FOR_BLANK_CANVAS';
+
+const reducer = ( state = 'CLOSED', { type, ...action } ) =>
+ 'SET_IS_OPEN' === type ? action.openState : state;
+
+const actions = {
+ setOpenState: ( openState: OpenState | false ) => ( {
+ type: 'SET_IS_OPEN' as const,
+ openState: openState || 'CLOSED',
+ } ),
+};
+
+export const selectors = {
+ isOpen: ( state: OpenState ): boolean => 'CLOSED' !== state,
+ isPatternPicker: ( state: OpenState ): boolean => 'OPEN_FOR_BLANK_CANVAS' === state,
+};
+
+export const pageLayoutStore = createReduxStore( 'automattic/starter-page-layouts', {
+ reducer,
+ actions,
+ selectors,
+} );
+register( pageLayoutStore );
diff --git a/projects/packages/jetpack-mu-wpcom/src/utils.php b/projects/packages/jetpack-mu-wpcom/src/utils.php
index 7d2b01300d639..93bce198060c1 100644
--- a/projects/packages/jetpack-mu-wpcom/src/utils.php
+++ b/projects/packages/jetpack-mu-wpcom/src/utils.php
@@ -89,12 +89,13 @@ function wpcom_get_calypso_origin() {
* @param array $asset_types The types of the asset.
*/
function jetpack_mu_wpcom_enqueue_assets( $asset_name, $asset_types = array() ) {
- $asset_file = include Jetpack_Mu_Wpcom::BASE_DIR . "build/$asset_name/$asset_name.asset.php";
+ $asset_handle = "jetpack-mu-wpcom-$asset_name";
+ $asset_file = include Jetpack_Mu_Wpcom::BASE_DIR . "build/$asset_name/$asset_name.asset.php";
if ( in_array( 'js', $asset_types, true ) ) {
$js_file = "build/$asset_name/$asset_name.js";
wp_enqueue_script(
- "jetpack-mu-wpcom-$asset_name-script",
+ $asset_handle,
plugins_url( $js_file, Jetpack_Mu_Wpcom::BASE_FILE ),
$asset_file['dependencies'] ?? array(),
$asset_file['version'] ?? filemtime( Jetpack_Mu_Wpcom::BASE_DIR . $js_file ),
@@ -106,10 +107,12 @@ function jetpack_mu_wpcom_enqueue_assets( $asset_name, $asset_types = array() )
$css_ext = is_rtl() ? 'rtl.css' : 'css';
$css_file = "build/$asset_name/$asset_name.$css_ext";
wp_enqueue_style(
- "jetpack-mu-wpcom-$asset_name-style",
+ $asset_handle,
plugins_url( $css_file, Jetpack_Mu_Wpcom::BASE_FILE ),
array(),
filemtime( Jetpack_Mu_Wpcom::BASE_DIR . $css_file )
);
}
+
+ return $asset_handle;
}
diff --git a/projects/packages/jetpack-mu-wpcom/webpack.config.js b/projects/packages/jetpack-mu-wpcom/webpack.config.js
index dca79c2a1fd0a..2e89e3d249467 100644
--- a/projects/packages/jetpack-mu-wpcom/webpack.config.js
+++ b/projects/packages/jetpack-mu-wpcom/webpack.config.js
@@ -40,6 +40,7 @@ module.exports = [
'wpcom-plugins-banner': './src/features/wpcom-plugins/js/banner.js',
'wpcom-plugins-banner-style': './src/features/wpcom-plugins/css/banner.css',
'wpcom-sidebar-notice': './src/features/wpcom-sidebar-notice/wpcom-sidebar-notice.js',
+ 'starter-page-templates': './src/features/starter-page-templates/index.tsx',
},
mode: jetpackWebpackConfig.mode,
devtool: jetpackWebpackConfig.devtool,
@@ -62,6 +63,11 @@ module.exports = [
plugins: [
...jetpackWebpackConfig.StandardPlugins( {
MiniCssExtractPlugin: { filename: '[name]/[name].css' },
+ DefinePlugin: {
+ // __i18n_text_domain__ is used in page-pattern-modal npm package, which is used only by starter-page-templates feature.
+ // Consider moving page-pattern-modal package to starter-page-templates and remove this.
+ __i18n_text_domain__: JSON.stringify( 'jetpack-mu-wpcom' ),
+ },
} ),
],
module: {
diff --git a/projects/plugins/mu-wpcom-plugin/changelog/HEAD b/projects/plugins/mu-wpcom-plugin/changelog/HEAD
new file mode 100644
index 0000000000000..9aa70e3ec1f75
--- /dev/null
+++ b/projects/plugins/mu-wpcom-plugin/changelog/HEAD
@@ -0,0 +1,5 @@
+Significance: patch
+Type: changed
+Comment: Updated composer.lock.
+
+
diff --git a/projects/plugins/mu-wpcom-plugin/changelog/port-starter-page-templates b/projects/plugins/mu-wpcom-plugin/changelog/port-starter-page-templates
new file mode 100644
index 0000000000000..9aa70e3ec1f75
--- /dev/null
+++ b/projects/plugins/mu-wpcom-plugin/changelog/port-starter-page-templates
@@ -0,0 +1,5 @@
+Significance: patch
+Type: changed
+Comment: Updated composer.lock.
+
+
diff --git a/projects/plugins/mu-wpcom-plugin/changelog/port-starter-page-templates#2 b/projects/plugins/mu-wpcom-plugin/changelog/port-starter-page-templates#2
new file mode 100644
index 0000000000000..9aa70e3ec1f75
--- /dev/null
+++ b/projects/plugins/mu-wpcom-plugin/changelog/port-starter-page-templates#2
@@ -0,0 +1,5 @@
+Significance: patch
+Type: changed
+Comment: Updated composer.lock.
+
+
diff --git a/projects/plugins/mu-wpcom-plugin/changelog/port-starter-page-templates#3 b/projects/plugins/mu-wpcom-plugin/changelog/port-starter-page-templates#3
new file mode 100644
index 0000000000000..9aa70e3ec1f75
--- /dev/null
+++ b/projects/plugins/mu-wpcom-plugin/changelog/port-starter-page-templates#3
@@ -0,0 +1,5 @@
+Significance: patch
+Type: changed
+Comment: Updated composer.lock.
+
+
diff --git a/projects/plugins/mu-wpcom-plugin/composer.lock b/projects/plugins/mu-wpcom-plugin/composer.lock
index 54cdd560ac5fb..77769b61f9d56 100644
--- a/projects/plugins/mu-wpcom-plugin/composer.lock
+++ b/projects/plugins/mu-wpcom-plugin/composer.lock
@@ -1005,7 +1005,7 @@
"dist": {
"type": "path",
"url": "../../packages/jetpack-mu-wpcom",
- "reference": "a9ffd2d847bb68899b38f0b6916a33e917fe2c90"
+ "reference": "87d053c8aeb9e5c4da954bff7081b36977ce3ad2"
},
"require": {
"automattic/jetpack-assets": "@dev",
@@ -1039,7 +1039,7 @@
},
"autotagger": true,
"branch-alias": {
- "dev-trunk": "5.52.x-dev"
+ "dev-trunk": "5.53.x-dev"
},
"textdomain": "jetpack-mu-wpcom",
"version-constants": {
diff --git a/projects/plugins/wpcomsh/changelog/HEAD b/projects/plugins/wpcomsh/changelog/HEAD
new file mode 100644
index 0000000000000..9aa70e3ec1f75
--- /dev/null
+++ b/projects/plugins/wpcomsh/changelog/HEAD
@@ -0,0 +1,5 @@
+Significance: patch
+Type: changed
+Comment: Updated composer.lock.
+
+
diff --git a/projects/plugins/wpcomsh/changelog/port-starter-page-templates b/projects/plugins/wpcomsh/changelog/port-starter-page-templates
new file mode 100644
index 0000000000000..9aa70e3ec1f75
--- /dev/null
+++ b/projects/plugins/wpcomsh/changelog/port-starter-page-templates
@@ -0,0 +1,5 @@
+Significance: patch
+Type: changed
+Comment: Updated composer.lock.
+
+
diff --git a/projects/plugins/wpcomsh/changelog/port-starter-page-templates#2 b/projects/plugins/wpcomsh/changelog/port-starter-page-templates#2
new file mode 100644
index 0000000000000..9aa70e3ec1f75
--- /dev/null
+++ b/projects/plugins/wpcomsh/changelog/port-starter-page-templates#2
@@ -0,0 +1,5 @@
+Significance: patch
+Type: changed
+Comment: Updated composer.lock.
+
+
diff --git a/projects/plugins/wpcomsh/changelog/port-starter-page-templates#3 b/projects/plugins/wpcomsh/changelog/port-starter-page-templates#3
new file mode 100644
index 0000000000000..9aa70e3ec1f75
--- /dev/null
+++ b/projects/plugins/wpcomsh/changelog/port-starter-page-templates#3
@@ -0,0 +1,5 @@
+Significance: patch
+Type: changed
+Comment: Updated composer.lock.
+
+
diff --git a/projects/plugins/wpcomsh/composer.lock b/projects/plugins/wpcomsh/composer.lock
index 7544fbbb8f3c2..2ccd641c15511 100644
--- a/projects/plugins/wpcomsh/composer.lock
+++ b/projects/plugins/wpcomsh/composer.lock
@@ -1142,7 +1142,7 @@
"dist": {
"type": "path",
"url": "../../packages/jetpack-mu-wpcom",
- "reference": "a9ffd2d847bb68899b38f0b6916a33e917fe2c90"
+ "reference": "87d053c8aeb9e5c4da954bff7081b36977ce3ad2"
},
"require": {
"automattic/jetpack-assets": "@dev",
@@ -1176,7 +1176,7 @@
},
"autotagger": true,
"branch-alias": {
- "dev-trunk": "5.52.x-dev"
+ "dev-trunk": "5.53.x-dev"
},
"textdomain": "jetpack-mu-wpcom",
"version-constants": {