diff --git a/api.wordpress.org/public_html/themes/info/1.0/index.php b/api.wordpress.org/public_html/themes/info/1.0/index.php
index 3a80c5c5ad..8e03f8c678 100644
--- a/api.wordpress.org/public_html/themes/info/1.0/index.php
+++ b/api.wordpress.org/public_html/themes/info/1.0/index.php
@@ -37,7 +37,7 @@ function send_error( $error, $code = 404 ) {
// Back-compat behaviour for the 1.0/1.1 API's
if (
defined( 'THEMES_API_VERSION' ) && THEMES_API_VERSION < 1.2 &&
- 'Theme not found' == $response->error
+ in_array( $response->error, [ 'Theme not found', 'closed' ], true )
) {
$response = false;
}
@@ -150,6 +150,7 @@ function send_error( $error, $code = 404 ) {
'theme_information' == $action &&
isset( $slug ) &&
404 == http_response_code() &&
+ ( ! isset( $api->response->error ) || 'closed' !== $api->response->error ) &&
// Validate that the theme doesn't exist for update-checks, as a sanity check.
! wp_cache_get( $slug, 'theme-update-check' ) &&
// And that there were no DB errors.
diff --git a/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin.php b/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin.php
index b69214b658..dd7331201a 100644
--- a/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin.php
+++ b/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin.php
@@ -56,14 +56,18 @@ function plugin_info( $request ) {
}
return [
- 'error' => 'closed',
- 'name' => get_the_title(),
- 'slug' => $post->post_name,
- 'description' => $close_text,
- 'closed' => true,
- 'closed_date' => $close_data['date'] ? gmdate( 'Y-m-d', strtotime( $close_data['date'] ) ) : false,
- 'reason' => $close_data['public'] ? $close_data['reason'] : false,
- 'reason_text' => $close_data['public'] ? $close_data['label'] : false,
+ 'error' => 'closed',
+ 'name' => get_the_title(),
+ 'slug' => $post->post_name,
+ 'description' => $close_text,
+ 'status' => $post->post_status,
+ 'closed' => true,
+ 'closed_date' => $close_data['date'] ? gmdate( 'Y-m-d', strtotime( $close_data['date'] ) ) : false,
+ 'reason' => $close_data['public'] ? $close_data['reason'] : false,
+ 'reason_text' => $close_data['public'] ? $close_data['label'] : false,
+ 'is_outdated' => Template::is_plugin_outdated( $post ),
+ 'outdated_notice' => Template::is_plugin_outdated( $post ) ? __( 'This plugin hasnt been tested with the latest 3 major releases of WordPress. It may no longer be maintained or supported and may have compatibility issues when used with more recent versions of WordPress.', 'wporg-plugins' ) : '',
+ 'is_security' => ( $close_data['public'] && 'security-issue' === $close_data['reason'] ),
];
} elseif ( ! $post || 'publish' != $post->post_status ) {
@@ -179,6 +183,15 @@ public function plugin_info_data( $request, $post ) {
$result['downloaded'] = intval( get_post_meta( $post_id, 'downloads', true ) );
$result['last_updated'] = gmdate( 'Y-m-d g:ia \G\M\T', strtotime( $last_updated ) );
$result['added'] = gmdate( 'Y-m-d', strtotime( $post->post_date_gmt ) );
+ $is_outdated = Template::is_plugin_outdated( $post );
+ $result['status'] = $post->post_status;
+ $result['closed'] = false;
+ $result['closed_date'] = false;
+ $result['reason'] = false;
+ $result['reason_text'] = false;
+ $result['is_outdated'] = (bool) $is_outdated;
+ $result['outdated_notice'] = $is_outdated ? __( 'This plugin hasnt been tested with the latest 3 major releases of WordPress. It may no longer be maintained or supported and may have compatibility issues when used with more recent versions of WordPress.', 'wporg-plugins' ) : '';
+ $result['is_security'] = false;
$result['homepage'] = get_post_meta( $post_id, 'header_plugin_uri', true );
$result['sections'] = array();
diff --git a/wordpress.org/public_html/wp-content/plugins/plugin-directory/standalone/class-plugins-info-api-request.php b/wordpress.org/public_html/wp-content/plugins/plugin-directory/standalone/class-plugins-info-api-request.php
index a6fd2a44b3..2e740eb232 100644
--- a/wordpress.org/public_html/wp-content/plugins/plugin-directory/standalone/class-plugins-info-api-request.php
+++ b/wordpress.org/public_html/wp-content/plugins/plugin-directory/standalone/class-plugins-info-api-request.php
@@ -46,6 +46,14 @@ class Plugins_Info_API_Request {
'repository_url' => false,
'support_url' => false,
'commercial_support_url' => false,
+ 'closed' => false,
+ 'closed_date' => false,
+ 'reason' => false,
+ 'reason_text' => false,
+ 'status' => false,
+ 'is_outdated' => false,
+ 'outdated_notice' => false,
+ 'is_security' => false,
);
static $plugins_info_fields_defaults = array(
@@ -67,6 +75,8 @@ class Plugins_Info_API_Request {
'tested' => true,
'versions' => true,
'screenshots' => true,
+ 'closed' => true,
+ 'is_outdated' => true,
);
// Alterations made to default fields in the info/1.2 API.
@@ -85,6 +95,14 @@ class Plugins_Info_API_Request {
'repository_url' => true,
'support_url' => true,
'commercial_support_url' => true,
+ 'closed' => true,
+ 'closed_date' => true,
+ 'reason' => true,
+ 'reason_text' => true,
+ 'status' => true,
+ 'is_outdated' => true,
+ 'outdated_notice' => true,
+ 'is_security' => true,
);
static $query_plugins_fields_defaults = array(
diff --git a/wordpress.org/public_html/wp-content/plugins/plugin-directory/standalone/plugin-update-helpers.php b/wordpress.org/public_html/wp-content/plugins/plugin-directory/standalone/plugin-update-helpers.php
index bfc1e10532..68fa86de2b 100644
--- a/wordpress.org/public_html/wp-content/plugins/plugin-directory/standalone/plugin-update-helpers.php
+++ b/wordpress.org/public_html/wp-content/plugins/plugin-directory/standalone/plugin-update-helpers.php
@@ -27,6 +27,15 @@ function alter_update( $plugin_info, $plugin_details, $installed_version, $wp_ve
// Apply the Phased Rollout / Staged Rollout / Gradual Rollout strategy to the plugin update.
$plugin_info = phased_rollout( $plugin_info, $plugin_details, $installed_version );
+ // Attach closure metadata if the plugin has been closed or disabled.
+ if ( ! empty( $plugin_details->meta->closed_at ) ) {
+ $plugin_info->closed = true;
+ $plugin_info->closed_date = $plugin_details->meta->closed_at;
+ if ( ! empty( $plugin_details->meta->closed_reason ) ) {
+ $plugin_info->closed_reason = $plugin_details->meta->closed_reason;
+ }
+ }
+
return $plugin_info;
}
diff --git a/wordpress.org/public_html/wp-content/plugins/theme-directory/class-themes-api.php b/wordpress.org/public_html/wp-content/plugins/theme-directory/class-themes-api.php
index e0d9f2fb90..8245bd225f 100644
--- a/wordpress.org/public_html/wp-content/plugins/theme-directory/class-themes-api.php
+++ b/wordpress.org/public_html/wp-content/plugins/theme-directory/class-themes-api.php
@@ -70,6 +70,15 @@ class Themes_API {
'external_repository_url' => false,
'external_support_url' => false,
'upload_date' => false,
+ 'is_closed' => false,
+ 'closed' => false,
+ 'closed_date' => false,
+ 'reason' => false,
+ 'reason_text' => false,
+ 'status' => false,
+ 'is_suspended' => false,
+ 'is_outdated' => false,
+ 'outdated_notice' => false,
);
/**
@@ -207,7 +216,7 @@ public function get_result( $format = 'raw' ) {
// Back-compat behaviour for the 1.0/1.1 API's
if ( defined( 'THEMES_API_VERSION' ) && THEMES_API_VERSION < 1.2 ) {
- if ( isset( $this->response->error ) && 'Theme not found' == $this->response->error ) {
+ if ( isset( $this->response->error ) && in_array( $this->response->error, array( 'Theme not found', 'closed' ), true ) ) {
$response = false;
}
}
@@ -231,7 +240,7 @@ public function set_status_header() {
status_header( 400 );
} elseif (
isset( $this->response->error ) &&
- 'Theme not found' == $this->response->error
+ in_array( $this->response->error, array( 'Theme not found', 'closed' ), true )
) {
status_header( 404 );
} else {
@@ -491,7 +500,16 @@ public function theme_information() {
$defaults['is_commercial'] = true;
$defaults['is_community'] = true;
$defaults['external_repository_url'] = true;
- $defaults['external_support_url'] = true;
+ $defaults['external_support_url'] = true;
+ $defaults['is_closed'] = true;
+ $defaults['closed'] = true;
+ $defaults['closed_date'] = true;
+ $defaults['reason'] = true;
+ $defaults['reason_text'] = true;
+ $defaults['status'] = true;
+ $defaults['is_suspended'] = true;
+ $defaults['is_outdated'] = true;
+ $defaults['outdated_notice'] = true;
}
$this->fields = array_merge( $this->fields, $defaults, (array) $this->request->fields );
@@ -509,7 +527,42 @@ public function theme_information() {
if ( $themes ) {
$this->response = $this->fill_theme( $themes[0] );
} else {
- $this->response = (object) array( 'error' => 'Theme not found' ); // Check get_result() if changing this string.
+ // Check for suspended theme.
+ $suspended_query = array(
+ 'name' => $this->request->slug,
+ 'post_type' => 'repopackage',
+ 'post_status' => 'suspend',
+ );
+ $suspended = get_posts( $suspended_query );
+
+ if ( $suspended ) {
+ $theme = $suspended[0];
+ $reason = (string) get_post_meta( $theme->ID, '_close_reason', true ) ?: 'security-issue';
+ $close_date = get_post_meta( $theme->ID, 'theme_closed_date', true );
+ if ( ! $close_date ) {
+ $close_date = get_post_modified_time( 'Y-m-d', true, $theme->ID, true );
+ } else {
+ $close_date = gmdate( 'Y-m-d', strtotime( $close_date ) );
+ }
+
+ $this->response = (object) array(
+ 'error' => 'closed',
+ 'name' => $theme->post_title,
+ 'slug' => $theme->post_name,
+ 'description' => __( 'This theme has been suspended and is not available for download.', 'wporg-themes' ),
+ 'status' => 'suspend',
+ 'closed' => true,
+ 'is_closed' => true,
+ 'is_suspended' => true,
+ 'closed_date' => $close_date,
+ 'reason' => $reason,
+ 'reason_text' => ( 'security-issue' === $reason ) ? __( 'Security Issue', 'wporg-themes' ) : __( 'Suspended', 'wporg-themes' ),
+ 'is_outdated' => false,
+ 'outdated_notice' => '',
+ );
+ } else {
+ $this->response = (object) array( 'error' => 'Theme not found' ); // Check get_result() if changing this string.
+ }
}
}
}
@@ -923,6 +976,43 @@ public function fill_theme( $theme ) {
}
}
+ $is_delisted = ( 'delist' === $theme->post_status );
+ $last_modified_time = get_post_modified_time( 'U', true, $theme->ID, true );
+ $is_outdated = ( $last_modified_time && ( time() - $last_modified_time ) > ( 2 * YEAR_IN_SECONDS ) );
+
+ if ( $this->fields['status'] ) {
+ $phil->status = $theme->post_status;
+ }
+
+ if ( $this->fields['closed'] || $this->fields['is_closed'] ) {
+ $phil->closed = $is_delisted;
+ $phil->is_closed = $is_delisted;
+ }
+
+ if ( $this->fields['is_suspended'] ) {
+ $phil->is_suspended = false;
+ }
+
+ if ( $this->fields['is_outdated'] ) {
+ $phil->is_outdated = (bool) $is_outdated;
+ }
+
+ if ( $this->fields['outdated_notice'] ) {
+ $phil->outdated_notice = $is_outdated ? __( 'This theme has not been updated in over 2 years. It may no longer be maintained or supported and may have compatibility issues when used with more recent versions of WordPress.', 'wporg-themes' ) : '';
+ }
+
+ if ( $this->fields['closed_date'] ) {
+ $phil->closed_date = $is_delisted ? get_post_modified_time( 'Y-m-d', true, $theme->ID, true ) : false;
+ }
+
+ if ( $this->fields['reason'] ) {
+ $phil->reason = $is_delisted ? ( (string) get_post_meta( $theme->ID, '_close_reason', true ) ?: 'delisted' ) : false;
+ }
+
+ if ( $this->fields['reason_text'] ) {
+ $phil->reason_text = $is_delisted ? __( 'Delisted', 'wporg-themes' ) : false;
+ }
+
if ( class_exists( 'GlotPress_Translate_Bridge' ) ) {
$glotpress_project = "wp-themes/{$phil->slug}";
diff --git a/wordpress.org/public_html/wp-content/plugins/theme-directory/tests/Themes_API_Test.php b/wordpress.org/public_html/wp-content/plugins/theme-directory/tests/Themes_API_Test.php
index 9287c6580d..81878838c2 100644
--- a/wordpress.org/public_html/wp-content/plugins/theme-directory/tests/Themes_API_Test.php
+++ b/wordpress.org/public_html/wp-content/plugins/theme-directory/tests/Themes_API_Test.php
@@ -287,4 +287,60 @@ public function test_keyed_fields_values_are_cast_to_booleans() {
$api->request->fields
);
}
+
+ /**
+ * Closure and outdated fields are preserved and properly cast in the fields array.
+ */
+ public function test_closure_and_outdated_fields_cast_to_booleans() {
+ $request = array(
+ 'fields' => array(
+ 'is_closed' => 1,
+ 'closed' => 'true',
+ 'status' => 1,
+ 'is_suspended' => 'true',
+ 'is_outdated' => 1,
+ 'outdated_notice' => 'true',
+ 'closed_date' => 1,
+ 'reason' => 'true',
+ 'reason_text' => 1,
+ ),
+ );
+ $api = $this->make_api( $request );
+
+ $this->assertSame(
+ array(
+ 'is_closed' => true,
+ 'closed' => true,
+ 'status' => true,
+ 'is_suspended' => true,
+ 'is_outdated' => true,
+ 'outdated_notice' => true,
+ 'closed_date' => true,
+ 'reason' => true,
+ 'reason_text' => true,
+ ),
+ $api->request->fields
+ );
+ }
+
+ /**
+ * When fields are passed as a list of strings, closure and outdated fields are mapped to true.
+ */
+ public function test_closure_and_outdated_fields_in_list_mapped_to_true() {
+ $request = array(
+ 'fields' => array( 'closed', 'is_closed', 'is_outdated', 'status', 'reason' ),
+ );
+ $api = $this->make_api( $request );
+
+ $this->assertSame(
+ array(
+ 'closed' => true,
+ 'is_closed' => true,
+ 'is_outdated' => true,
+ 'status' => true,
+ 'reason' => true,
+ ),
+ $api->request->fields
+ );
+ }
}
diff --git a/wordpress.org/public_html/wp-content/plugins/theme-directory/themes-api.php b/wordpress.org/public_html/wp-content/plugins/theme-directory/themes-api.php
index 664d1dc585..72592bfd22 100644
--- a/wordpress.org/public_html/wp-content/plugins/theme-directory/themes-api.php
+++ b/wordpress.org/public_html/wp-content/plugins/theme-directory/themes-api.php
@@ -27,3 +27,21 @@ function wporg_themes_update_check( $post_id, $current_version ) {
wp_cache_delete( $slug, 'theme_information_error' );
}
add_action( 'wporg_themes_update_version_live', 'wporg_themes_update_check', 10, 2 );
+
+/**
+ * Clears the update-check and information caches when a theme is suspended or reinstated.
+ *
+ * @param int $post_id Post ID.
+ */
+function wporg_themes_clear_theme_caches( $post_id ) {
+ $post = get_post( $post_id );
+ if ( ! $post || 'repopackage' !== $post->post_type ) {
+ return;
+ }
+
+ wp_cache_delete( $post->post_name, 'theme-update-check' );
+ wp_cache_delete( $post->post_name, 'theme_information_error' );
+ wp_cache_delete( 'theme-info:' . $post->post_name, 'theme-info' );
+}
+add_action( 'suspend_repopackage', 'wporg_themes_clear_theme_caches' );
+add_action( 'publish_repopackage', 'wporg_themes_clear_theme_caches' );