wordpress.org/environments/plugin-directory/bin/import-plugins.php
Dion Hulse bc3041fa9f Plugin Directory: Introduce a local development option for the plugin directory.
This also provides an environment in which we can run integration & unit tests in.

Merges https://github.com/WordPress/wordpress.org/pull/555


git-svn-id: https://meta.svn.wordpress.org/sites/trunk@14720 74240141-8908-4e6f-9713-ba540dce6ec7
2026-03-16 05:00:45 +00:00

289 lines
8.2 KiB
PHP

<?php
/**
* Import plugins from WordPress.org for local development.
*
* Fetches plugin slugs from featured, popular, and beta browse views,
* imports full data for each using the /wp/v2/plugin REST API endpoint,
* and tags them with the appropriate section terms.
*
* Plugin data is fetched in batches to reduce HTTP requests.
*/
namespace WordPressdotorg\Plugin_Directory\Env;
use WordPressdotorg\Plugin_Directory\Plugin_Directory;
use WordPressdotorg\Plugin_Directory\Tools;
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Skip if already imported.
if ( get_option( 'wporg_env_imported' ) ) {
echo "Already imported, skipping.\n";
return;
}
update_option( 'wporg_env_imported', time() );
$per_section = 15;
$batch_size = $per_section;
$base_url = 'https://wordpress.org/plugins/wp-json';
$browse_sections = array( 'featured', 'popular', 'beta', 'blocks', 'new', 'updated' );
update_option( 'blogname', 'Plugin Directory' );
/**
* Fetch slugs for a given browse section.
*/
function fetch_slugs( $base_url, $section, $count ) {
$response = wp_remote_get( "{$base_url}/plugins/v1/query-plugins/?browse={$section}&posts_per_page={$count}" );
if ( is_wp_error( $response ) ) {
return array();
}
$data = json_decode( wp_remote_retrieve_body( $response ) );
if ( empty( $data->plugins ) ) {
return array();
}
return array_unique( $data->plugins );
}
/**
* Fetch plugin data for multiple slugs in a single REST API call.
*
* @param string $base_url REST API base URL.
* @param array $slugs Plugin slugs to fetch.
* @return array Keyed by slug, values are REST API response arrays.
*/
function fetch_plugins_batch( $base_url, $slugs ) {
$query = http_build_query( array(
'slug' => $slugs,
'_embed' => 1,
'per_page' => count( $slugs ),
) );
$response = wp_remote_get( "{$base_url}/wp/v2/plugin?{$query}", array( 'timeout' => 60 ) );
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
return array();
}
$results = json_decode( wp_remote_retrieve_body( $response ), true );
if ( ! is_array( $results ) ) {
return array();
}
$plugins = array();
foreach ( $results as $data ) {
if ( ! empty( $data['slug'] ) ) {
$plugins[ $data['slug'] ] = $data;
}
}
return $plugins;
}
/**
* Ensure a WordPress user exists for a contributor.
*
* @param string $nicename User nicename/slug.
* @param string $display_name Display name.
*/
function ensure_user( $nicename, $display_name = '' ) {
if ( get_user_by( 'slug', $nicename ) ) {
return;
}
wp_insert_user( array(
'user_login' => $nicename,
'user_nicename' => $nicename,
'user_email' => $nicename . '@example.invalid',
'display_name' => $display_name ?: $nicename,
'user_pass' => wp_generate_password(),
'role' => 'subscriber',
) );
}
/**
* Extract taxonomy terms grouped by taxonomy from an embedded REST response.
*/
function extract_embedded_terms( $data ) {
$terms = array();
foreach ( $data['_embedded']['wp:term'] ?? [] as $group ) {
foreach ( $group as $term ) {
$terms[ $term['taxonomy'] ][] = $term;
}
}
return $terms;
}
/**
* Import a single plugin from pre-fetched REST API data.
*/
function save_plugin( $data, $existing_post = null ) {
$meta = $data['meta'] ?? [];
$taxonomies = extract_embedded_terms( $data );
// Ensure the plugin author user exists.
$author_id = 0;
$author = $data['_embedded']['author'][0] ?? [];
if ( ! empty( $author['slug'] ) ) {
ensure_user( $author['slug'], $author['name'] ?? '' );
$author_user = get_user_by( 'slug', $author['slug'] );
if ( $author_user ) {
$author_id = $author_user->ID;
}
}
// Build the post args from the REST API response.
// The plugin post type doesn't support 'title', so use header_name meta instead.
// Use last_updated for post_modified to match production behavior.
$last_updated = $meta['last_updated'] ?? '';
$post_args = array(
'post_title' => $meta['header_name'] ?? $data['slug'],
'post_name' => $data['slug'],
'post_status' => 'publish',
'post_content' => $data['raw_content'] ?? '',
'post_excerpt' => $data['raw_excerpt'] ?? '',
'post_date' => $data['date'] ?? '',
'post_date_gmt' => $data['date_gmt'] ?? '',
'post_modified' => $last_updated ?: ( $data['date'] ?? '' ),
'post_modified_gmt' => $last_updated ? get_gmt_from_date( $last_updated ) : ( $data['date_gmt'] ?? '' ),
);
if ( $author_id ) {
$post_args['post_author'] = $author_id;
}
// Preserve our custom dates through wp_insert_post/wp_update_post.
$preserve_dates = function( $data ) use ( $post_args ) {
foreach ( array( 'post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt' ) as $key ) {
if ( ! empty( $post_args[ $key ] ) ) {
$data[ $key ] = $post_args[ $key ];
}
}
return $data;
};
add_filter( 'wp_insert_post_data', $preserve_dates );
if ( $existing_post ) {
$post_args['ID'] = $existing_post->ID;
wp_update_post( $post_args );
$post = get_post( $existing_post->ID );
} else {
$post = Plugin_Directory::create_plugin_post( $post_args );
}
remove_filter( 'wp_insert_post_data', $preserve_dates );
if ( is_wp_error( $post ) || ! $post ) {
return null;
}
// Store meta values from the standard REST meta object.
foreach ( $meta as $key => $value ) {
if ( '' !== $value && null !== $value ) {
update_post_meta( $post->ID, $key, wp_slash( $value ) );
}
}
// Store underscore-prefixed meta used internally for sorting/queries.
if ( ! empty( $meta['active_installs'] ) ) {
update_post_meta( $post->ID, '_active_installs', (int) $meta['active_installs'] );
}
// Taxonomies from embedded terms.
foreach ( $taxonomies as $taxonomy => $terms ) {
if ( 'plugin_contributors' === $taxonomy ) {
// Create users and grant committer access.
$contributor_slugs = array();
foreach ( $terms as $term ) {
$contributor_slugs[] = $term['slug'];
ensure_user( $term['slug'], $term['display_name'] ?? $term['name'] ?? '' );
}
wp_set_object_terms( $post->ID, $contributor_slugs, $taxonomy );
foreach ( $contributor_slugs as $contributor_slug ) {
Tools::grant_plugin_committer( $post, $contributor_slug );
}
} else {
$term_slugs = wp_list_pluck( $terms, 'slug' );
wp_set_object_terms( $post->ID, $term_slugs, $taxonomy );
}
}
return $post;
}
// Main loop.
$imported_slugs = array();
foreach ( $browse_sections as $section ) {
echo "Fetching plugins in '{$section}' section...\n";
$slugs = fetch_slugs( $base_url, $section, $per_section );
echo " Found " . count( $slugs ) . " slugs.\n";
// Separate new slugs from already-imported ones.
$new_slugs = array_diff( $slugs, $imported_slugs );
$existing_slugs = array_intersect( $slugs, $imported_slugs );
// Fetch plugin data only for new slugs.
$all_plugin_data = array();
foreach ( array_chunk( $new_slugs, $batch_size ) as $batch ) {
$batch_data = fetch_plugins_batch( $base_url, $batch );
$all_plugin_data = array_merge( $all_plugin_data, $batch_data );
$missing = array_diff( $batch, array_keys( $batch_data ) );
if ( $missing ) {
echo " Skipped (not found): " . implode( ', ', $missing ) . "\n";
}
}
echo " Fetched " . count( $all_plugin_data ) . " plugins, importing...\n";
// Tag already-imported plugins with this section.
foreach ( $existing_slugs as $slug ) {
$existing = get_posts( array(
'post_type' => 'plugin',
'name' => $slug,
'post_status' => 'any',
'numberposts' => 1,
) );
if ( $existing ) {
wp_set_object_terms( $existing[0]->ID, $section, 'plugin_section', true );
echo " {$slug}... {$existing[0]->post_title} (tagged)\n";
}
}
// Import new plugins.
$imported = 0;
foreach ( $new_slugs as $slug ) {
if ( ! isset( $all_plugin_data[ $slug ] ) ) {
continue;
}
echo " {$slug}...";
$post = save_plugin( $all_plugin_data[ $slug ] );
if ( ! $post ) {
echo " failed.\n";
continue;
}
wp_set_object_terms( $post->ID, $section, 'plugin_section', true );
$imported_slugs[] = $slug;
echo " {$post->post_title} (done)\n";
$imported++;
}
echo " {$section}: {$imported} new, " . count( $existing_slugs ) . " tagged.\n\n";
}
// Flush rewrite rules to generate .htaccess.
flush_rewrite_rules();
echo "Done!\n";