Skip to content

Commit 2b1fbb5

Browse files
ellatrixyouknowriadmcsf
authored
Template activation: move to experiment (WordPress#73252)
Co-authored-by: ellatrix <ellatrix@git.wordpress.org> Co-authored-by: youknowriad <youknowriad@git.wordpress.org> Co-authored-by: mcsf <mcsf@git.wordpress.org>
1 parent 257acf1 commit 2b1fbb5

File tree

31 files changed

+2432
-126
lines changed

31 files changed

+2432
-126
lines changed

lib/compat/wordpress-6.9/template-activate.php

Lines changed: 47 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,52 @@
11
<?php
22

3+
// Migrate existing "edited" templates. By existing, it means that the template
4+
// is active.
5+
function gutenberg_get_migrated_active_templates() {
6+
// Query all templates in the database. See `get_block_templates`.
7+
$wp_query_args = array(
8+
'post_status' => 'publish',
9+
'post_type' => 'wp_template',
10+
'posts_per_page' => -1,
11+
'no_found_rows' => true,
12+
'lazy_load_term_meta' => false,
13+
'tax_query' => array(
14+
array(
15+
'taxonomy' => 'wp_theme',
16+
'field' => 'name',
17+
'terms' => get_stylesheet(),
18+
),
19+
),
20+
// Only get templates that are not inactive by default.
21+
'meta_query' => array(
22+
'relation' => 'OR',
23+
array(
24+
'key' => 'is_inactive_by_default',
25+
'compare' => 'NOT EXISTS',
26+
),
27+
array(
28+
'key' => 'is_inactive_by_default',
29+
'value' => false,
30+
'compare' => '=',
31+
),
32+
),
33+
);
34+
35+
$template_query = new WP_Query( $wp_query_args );
36+
$active_templates = array();
37+
38+
foreach ( $template_query->posts as $post ) {
39+
$active_templates[ $post->post_name ] = $post->ID;
40+
}
41+
42+
return $active_templates;
43+
}
44+
45+
// Only load template activation system if the experiment is enabled.
46+
if ( ! gutenberg_is_experiment_enabled( 'active_templates' ) ) {
47+
return;
48+
}
49+
350
require_once __DIR__ . '/class-gutenberg-rest-old-templates-controller.php';
451

552
// How does this work?
@@ -500,55 +547,6 @@ function gutenberg_set_active_template_theme( $changes, $request ) {
500547
return $changes;
501548
}
502549

503-
// Migrate existing "edited" templates. By existing, it means that the template
504-
// is active.
505-
add_action( 'init', 'gutenberg_migrate_existing_templates' );
506-
function gutenberg_migrate_existing_templates() {
507-
$active_templates = get_option( 'active_templates', false );
508-
509-
if ( false !== $active_templates ) {
510-
return;
511-
}
512-
513-
// Query all templates in the database. See `get_block_templates`.
514-
$wp_query_args = array(
515-
'post_status' => 'publish',
516-
'post_type' => 'wp_template',
517-
'posts_per_page' => -1,
518-
'no_found_rows' => true,
519-
'lazy_load_term_meta' => false,
520-
'tax_query' => array(
521-
array(
522-
'taxonomy' => 'wp_theme',
523-
'field' => 'name',
524-
'terms' => get_stylesheet(),
525-
),
526-
),
527-
// Only get templates that are not inactive by default.
528-
'meta_query' => array(
529-
'relation' => 'OR',
530-
array(
531-
'key' => 'is_inactive_by_default',
532-
'compare' => 'NOT EXISTS',
533-
),
534-
array(
535-
'key' => 'is_inactive_by_default',
536-
'value' => false,
537-
'compare' => '=',
538-
),
539-
),
540-
);
541-
542-
$template_query = new WP_Query( $wp_query_args );
543-
$active_templates = array();
544-
545-
foreach ( $template_query->posts as $post ) {
546-
$active_templates[ $post->post_name ] = $post->ID;
547-
}
548-
549-
update_option( 'active_templates', $active_templates );
550-
}
551-
552550
add_action( 'save_post_wp_template', 'gutenberg_maybe_update_active_templates' );
553551
function gutenberg_maybe_update_active_templates( $post_id ) {
554552
$post = get_post( $post_id );

lib/experimental/editor-settings.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ function gutenberg_enable_experiments() {
3737
if ( $gutenberg_experiments && array_key_exists( 'gutenberg-customizable-navigation-overlays', $gutenberg_experiments ) ) {
3838
wp_add_inline_script( 'wp-block-editor', 'window.__experimentalNavigationOverlays = true', 'before' );
3939
}
40+
if ( gutenberg_is_experiment_enabled( 'active_templates' ) ) {
41+
wp_add_inline_script( 'wp-block-editor', 'window.__experimentalTemplateActivate = true', 'before' );
42+
}
4043
}
4144

4245
add_action( 'admin_init', 'gutenberg_enable_experiments' );

lib/experiments-page.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,28 @@ class="wrap"
2222
<form method="post" action="options.php">
2323
<?php settings_fields( 'gutenberg-experiments' ); ?>
2424
<?php do_settings_sections( 'gutenberg-experiments' ); ?>
25+
<!-- We use a separate table for the template activation experiment because the option is managed separately. -->
26+
<table class="form-table">
27+
<tr>
28+
<th scope="row">
29+
<label for="active_templates"><?php echo __( 'Template Activation', 'gutenberg' ); ?></label>
30+
<br><a href="https://github.com/WordPress/gutenberg/issues/66950" target="_blank"><?php echo __( 'Learn more', 'gutenberg' ); ?></a>
31+
</th>
32+
<td>
33+
<label for="active_templates">
34+
<input
35+
type="checkbox"
36+
name="active_templates"
37+
id="active_templates"
38+
value="1"
39+
<?php checked( 1, gutenberg_is_experiment_enabled( 'active_templates' ) ); ?>
40+
/>
41+
<?php echo __( 'Allows multiple templates of the same type to be created, of which one can be active at a time.', 'gutenberg' ); ?>
42+
<p class="description"><?php echo __( 'Warning: when you deactivate this experiment, it is best to delete all created templates except for the active ones.', 'gutenberg' ); ?></p>
43+
</label>
44+
</td>
45+
</tr>
46+
</table>
2547
<?php submit_button(); ?>
2648
</form>
2749
</div>
@@ -248,3 +270,24 @@ function gutenberg_display_experiment_section() {
248270

249271
<?php
250272
}
273+
274+
add_action( 'admin_init', 'gutenberg_handle_template_activate_setting_submission' );
275+
function gutenberg_handle_template_activate_setting_submission() {
276+
if ( ! isset( $_POST['option_page'] ) || 'gutenberg-experiments' !== $_POST['option_page'] ) {
277+
return;
278+
}
279+
280+
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'gutenberg-experiments-options' ) ) {
281+
return;
282+
}
283+
284+
if ( ! current_user_can( 'manage_options' ) ) {
285+
return;
286+
}
287+
288+
if ( isset( $_POST['active_templates'] ) && '1' === $_POST['active_templates'] ) {
289+
update_option( 'active_templates', gutenberg_get_migrated_active_templates() );
290+
} else {
291+
delete_option( 'active_templates' );
292+
}
293+
}

lib/load.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@
3030
* @return bool True when the experiment is enabled.
3131
*/
3232
function gutenberg_is_experiment_enabled( $name ) {
33+
// Special handling for active_templates - check if the active_templates option exists.
34+
// This is not stored in the experiments array but as a separate option.
35+
if ( 'active_templates' === $name ) {
36+
return is_array( get_option( 'active_templates' ) );
37+
}
38+
3339
$experiments = get_option( 'gutenberg-experiments' );
3440
return ! empty( $experiments[ $name ] );
3541
}

packages/core-data/src/actions.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,10 @@ export const deleteEntityRecord =
317317
if (
318318
kind === 'postType' &&
319319
name === 'wp_template' &&
320-
recordId &&
321-
typeof recordId === 'string' &&
322-
! /^\d+$/.test( recordId )
320+
( ( recordId &&
321+
typeof recordId === 'string' &&
322+
! /^\d+$/.test( recordId ) ) ||
323+
! window?.__experimentalTemplateActivate )
323324
) {
324325
baseURL =
325326
baseURL.slice( 0, baseURL.lastIndexOf( '/' ) ) +
@@ -571,9 +572,10 @@ export const saveEntityRecord =
571572
if (
572573
kind === 'postType' &&
573574
name === 'wp_template' &&
574-
recordId &&
575-
typeof recordId === 'string' &&
576-
! /^\d+$/.test( recordId )
575+
( ( recordId &&
576+
typeof recordId === 'string' &&
577+
! /^\d+$/.test( recordId ) ) ||
578+
! window?.__experimentalTemplateActivate )
577579
) {
578580
baseURL =
579581
baseURL.slice( 0, baseURL.lastIndexOf( '/' ) ) +

packages/core-data/src/entities.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,10 @@ async function loadPostTypeEntities() {
329329
}/${ parentId }/revisions${
330330
revisionId ? '/' + revisionId : ''
331331
}`,
332-
revisionKey: DEFAULT_ENTITY_KEY,
332+
revisionKey:
333+
isTemplate && ! window?.__experimentalTemplateActivate
334+
? 'wp_id'
335+
: DEFAULT_ENTITY_KEY,
333336
};
334337

335338
if ( window.__experimentalEnableSync ) {

packages/core-data/src/resolvers.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,8 @@ export const getEntityRecord =
118118
if (
119119
kind === 'postType' &&
120120
name === 'wp_template' &&
121-
key &&
122-
typeof key === 'string' &&
123-
! /^\d+$/.test( key )
121+
( ( key && typeof key === 'string' && ! /^\d+$/.test( key ) ) ||
122+
! window?.__experimentalTemplateActivate )
124123
) {
125124
baseURL =
126125
baseURL.slice( 0, baseURL.lastIndexOf( '/' ) ) +

packages/e2e-test-utils-playwright/src/request-utils/gutenberg-experiments.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { RequestUtils } from './index';
88
*
99
* @param this
1010
* @param experiments Array of experimental flags to enable. Pass in an empty array to disable all experiments.
11+
* Use 'active_templates' for the template activation feature.
1112
*/
1213
async function setGutenbergExperiments(
1314
this: RequestUtils,
@@ -19,20 +20,40 @@ async function setGutenbergExperiments(
1920
const html = await response.text();
2021
const nonce = html.match( /name="_wpnonce" value="([^"]+)"/ )![ 1 ];
2122

22-
await this.request.post( '/wp-admin/options.php', {
23-
form: {
24-
option_page: 'gutenberg-experiments',
25-
action: 'update',
26-
_wpnonce: nonce,
27-
_wp_http_referer: '/wp-admin/admin.php?page=gutenberg-experiments',
28-
...Object.fromEntries(
29-
experiments.map( ( experiment ) => [
23+
const formData: Record< string, string | number > = {
24+
option_page: 'gutenberg-experiments',
25+
action: 'update',
26+
_wpnonce: nonce,
27+
_wp_http_referer: '/wp-admin/admin.php?page=gutenberg-experiments',
28+
submit: 'Save Changes',
29+
};
30+
31+
// Separate regular experiments from active_templates.
32+
const regularExperiments = experiments.filter(
33+
( exp ) => exp !== 'active_templates'
34+
);
35+
const hasActiveTemplates = experiments.includes( 'active_templates' );
36+
37+
// Add regular experiments to the gutenberg-experiments array.
38+
if ( regularExperiments.length > 0 ) {
39+
Object.assign(
40+
formData,
41+
Object.fromEntries(
42+
regularExperiments.map( ( experiment ) => [
3043
`gutenberg-experiments[${ experiment }]`,
3144
1,
3245
] )
33-
),
34-
submit: 'Save Changes',
35-
},
46+
)
47+
);
48+
}
49+
50+
// Template activation uses the active_templates checkbox field.
51+
if ( hasActiveTemplates ) {
52+
formData.active_templates = 1;
53+
}
54+
55+
await this.request.post( '/wp-admin/options.php', {
56+
form: formData,
3657
failOnStatusCode: true,
3758
} );
3859
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import { paramCase as kebabCase } from 'change-case';
5+
6+
/**
7+
* WordPress dependencies
8+
*/
9+
import { useState, useEffect, useRef } from '@wordpress/element';
10+
import { __ } from '@wordpress/i18n';
11+
import {
12+
Button,
13+
TextControl,
14+
__experimentalHStack as HStack,
15+
__experimentalVStack as VStack,
16+
} from '@wordpress/components';
17+
18+
function AddCustomGenericTemplateModalContent( { createTemplate, onBack } ) {
19+
const [ title, setTitle ] = useState( '' );
20+
const defaultTitle = __( 'Custom Template' );
21+
const [ isBusy, setIsBusy ] = useState( false );
22+
const inputRef = useRef();
23+
24+
// Set focus to the name input when the component mounts
25+
useEffect( () => {
26+
if ( inputRef.current ) {
27+
inputRef.current.focus();
28+
}
29+
}, [] );
30+
31+
async function onCreateTemplate( event ) {
32+
event.preventDefault();
33+
if ( isBusy ) {
34+
return;
35+
}
36+
setIsBusy( true );
37+
try {
38+
await createTemplate(
39+
{
40+
slug:
41+
kebabCase( title || defaultTitle ) ||
42+
'wp-custom-template',
43+
title: title || defaultTitle,
44+
},
45+
false
46+
);
47+
} finally {
48+
setIsBusy( false );
49+
}
50+
}
51+
return (
52+
<form onSubmit={ onCreateTemplate }>
53+
<VStack spacing={ 6 }>
54+
<TextControl
55+
__next40pxDefaultSize
56+
__nextHasNoMarginBottom
57+
label={ __( 'Name' ) }
58+
value={ title }
59+
onChange={ setTitle }
60+
placeholder={ defaultTitle }
61+
disabled={ isBusy }
62+
ref={ inputRef }
63+
help={ __(
64+
// eslint-disable-next-line no-restricted-syntax -- 'sidebar' is a common web design term for layouts
65+
'Describe the template, e.g. "Post with sidebar". A custom template can be manually applied to any post or page.'
66+
) }
67+
/>
68+
<HStack
69+
className="edit-site-custom-generic-template__modal-actions"
70+
justify="right"
71+
>
72+
<Button
73+
__next40pxDefaultSize
74+
variant="tertiary"
75+
onClick={ onBack }
76+
>
77+
{ __( 'Back' ) }
78+
</Button>
79+
<Button
80+
__next40pxDefaultSize
81+
variant="primary"
82+
type="submit"
83+
isBusy={ isBusy }
84+
aria-disabled={ isBusy }
85+
>
86+
{ __( 'Create' ) }
87+
</Button>
88+
</HStack>
89+
</VStack>
90+
</form>
91+
);
92+
}
93+
94+
export default AddCustomGenericTemplateModalContent;

0 commit comments

Comments
 (0)