Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions inc/ui/class-template-switching-element.php
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,34 @@ public function output($atts, $content = null) {

$sites = array_filter($sites);

/*
* Hide templates the network admin has marked as unavailable.
*
* `wu_get_site_templates()` (used upstream to seed the candidate
* list) only filters by `wu_type = site_template` — it does not
* exclude templates the network admin has deactivated, archived,
* marked as deleted, or flagged as spam. Without this filter the
* customer-panel switching grid will list templates that the
* admin explicitly took out of circulation, which is what the
* customer reported as "templates that should not be available
* are still listed".
*
* Mirrors the inactive-only filter in
* inc/checkout/signup-fields/class-signup-field-template-selection.php
* (line 349) and extends it to cover archived/deleted/spam.
* Applied here (in the switching element) rather than in
* `wu_get_site_templates()` so we don't change behaviour for
* network-admin pages or any other callers that legitimately
* want every template.
*/
$sites = array_filter(
$sites,
static fn($site_template) => $site_template->is_active()
&& ! $site_template->is_archived()
&& ! $site_template->is_deleted()
&& ! $site_template->is_spam()
);

/*
* Hide the current template from the "Available Templates" grid.
* The current template is already shown in the summary card above,
Expand Down
113 changes: 113 additions & 0 deletions tests/WP_Ultimo/UI/Template_Switching_Element_Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
parent::set_up();

// Reset request globals between tests.
foreach ( [ 'template_id' ] as $key ) {

Check warning on line 33 in tests/WP_Ultimo/UI/Template_Switching_Element_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Expected no space before the array closer in a single line array. Found: 1 space

Check warning on line 33 in tests/WP_Ultimo/UI/Template_Switching_Element_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Expected no space after the array opener in a single line array. Found: 1 space
unset( $_REQUEST[ $key ], $_GET[ $key ], $_POST[ $key ] );
}
}
Expand All @@ -40,7 +40,7 @@
*/
public function tear_down(): void {

foreach ( [ 'template_id' ] as $key ) {

Check warning on line 43 in tests/WP_Ultimo/UI/Template_Switching_Element_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Expected no space before the array closer in a single line array. Found: 1 space

Check warning on line 43 in tests/WP_Ultimo/UI/Template_Switching_Element_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Expected no space after the array opener in a single line array. Found: 1 space
unset( $_REQUEST[ $key ], $_GET[ $key ], $_POST[ $key ] );
}

Expand Down Expand Up @@ -68,8 +68,8 @@
add_filter( 'wp_doing_ajax', '__return_true' );

$handler = function () {
return function ( $message ) {

Check warning on line 71 in tests/WP_Ultimo/UI/Template_Switching_Element_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Expected 0 spaces before closing parenthesis; 1 found

Check warning on line 71 in tests/WP_Ultimo/UI/Template_Switching_Element_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Expected 0 spaces after opening parenthesis; 1 found
throw new \WPAjaxDieContinueException( (string) $message );

Check warning on line 72 in tests/WP_Ultimo/UI/Template_Switching_Element_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

All output should be run through an escaping function (see the Security sections in the WordPress Developer Handbooks), found '(string)'.
};
};

Expand All @@ -83,7 +83,7 @@
*
* @param callable $handler The handler returned by install_ajax_die_handler().
*/
private function remove_ajax_die_handler( callable $handler ): void {

Check warning on line 86 in tests/WP_Ultimo/UI/Template_Switching_Element_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Expected 0 spaces before closing parenthesis; 1 found

Check warning on line 86 in tests/WP_Ultimo/UI/Template_Switching_Element_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Expected 0 spaces after opening parenthesis; 1 found

remove_filter( 'wp_doing_ajax', '__return_true' );
remove_filter( 'wp_die_ajax_handler', $handler, 1 );
Expand Down Expand Up @@ -123,7 +123,7 @@
* @param string $output Raw output from the AJAX handler.
* @return array
*/
private function decode_json( string $output ): array {

Check warning on line 126 in tests/WP_Ultimo/UI/Template_Switching_Element_Test.php

View workflow job for this annotation

GitHub Actions / Code Quality Checks

Expected 0 spaces after opening parenthesis; 1 found

$this->assertNotEmpty(
$output,
Expand Down Expand Up @@ -371,4 +371,117 @@
);
}

/**
* Templates the network admin has marked unavailable (inactive,
* archived, deleted, or spam) must not appear in the customer-panel
* "Available Templates" grid.
*
* Regression guard for the customer report that "templates that should
* not be available are still listed". `wu_get_site_templates()` only
* filters by `wu_type = site_template`, so without the additional
* filter in Template_Switching_Element::output() any template whose
* availability flags the admin has cleared still ended up rendered.
*
* The assertion looks for `id="wu-site-template-{ID}"` because the
* grid card wrapper uses that exact attribute (see
* views/checkout/templates/template-selection/clean.php). A simple
* presence check on the title would false-positive on the
* current-template card or hidden DOM markers.
*/
public function test_render_excludes_unavailable_templates_from_grid(): void {

// Active + available → must render.
$active_id = $this->factory()->blog->create();
$active = wu_get_site( $active_id );
$active->set_type( 'site_template' );
$active->set_active( true );
$active->set_archived( false );
$active->set_deleted( false );
$active->set_spam( false );
$active->save();

// Inactive (network admin disabled) → must NOT render.
$inactive_id = $this->factory()->blog->create();
$inactive = wu_get_site( $inactive_id );
$inactive->set_type( 'site_template' );
$inactive->set_active( false );
$inactive->save();

// Archived → must NOT render.
$archived_id = $this->factory()->blog->create();
$archived = wu_get_site( $archived_id );
$archived->set_type( 'site_template' );
$archived->set_active( true );
$archived->set_archived( true );
$archived->save();

// Deleted → must NOT render.
$deleted_id = $this->factory()->blog->create();
$deleted = wu_get_site( $deleted_id );
$deleted->set_type( 'site_template' );
$deleted->set_active( true );
$deleted->set_deleted( true );
$deleted->save();

// Spam → must NOT render.
$spam_id = $this->factory()->blog->create();
$spam = wu_get_site( $spam_id );
$spam->set_type( 'site_template' );
$spam->set_active( true );
$spam->set_spam( true );
$spam->save();

$html = $this->render_element_with_context();

/*
* The grid is rendered client-side by Vue from the JSON payload
* embedded in the `<dynamic :template="...">` tag. Server-side
* HTML therefore does not contain `id="wu-site-template-X"` —
* we have to inspect the encoded `sites` array in the dynamic
* tag's attribute. `wp_json_encode()` then `esc_attr()` produce
* `&quot;sites&quot;:[&quot;2&quot;,...]` in the output.
*/
if ( ! preg_match( '/get_template\\(\'template-selection\\/clean\',\\s*({.*?})\\)/', $html, $matches ) ) {
$this->fail( 'Could not find the dynamic-template JSON payload in the rendered output.' );
}

$decoded_attrs = html_entity_decode( $matches[1], ENT_QUOTES );
$attrs = json_decode( $decoded_attrs, true );

$this->assertIsArray( $attrs, 'Dynamic-template payload must decode to an array. Got: ' . $decoded_attrs );
$this->assertArrayHasKey( 'sites', $attrs );

$sites = array_map( 'intval', $attrs['sites'] );

$this->assertContains(
(int) $active_id,
$sites,
'Active, non-archived, non-deleted, non-spam template must appear in the grid sites array.'
);

$this->assertNotContains(
(int) $inactive_id,
$sites,
'Inactive template must not appear in the grid sites array.'
);

$this->assertNotContains(
(int) $archived_id,
$sites,
'Archived template must not appear in the grid sites array.'
);

$this->assertNotContains(
(int) $deleted_id,
$sites,
'Deleted template must not appear in the grid sites array.'
);

$this->assertNotContains(
(int) $spam_id,
$sites,
'Spam template must not appear in the grid sites array.'
);
}

}
Loading