Skip to content

Commit

Permalink
fix: memory leaks from legacy term utilities (#81)
Browse files Browse the repository at this point in the history
* feat: gate custom tax migration behind env flag to avoid expensive ops

* feat: move legacy taxonomy migrator script to a CLI command

* chore: rename CLI command and use ::log instead of ::line

* refactor: move missing/orphan shadow term handling to WP CLI
  • Loading branch information
dkoo authored Jun 30, 2021
1 parent b8bccc3 commit 4576805
Show file tree
Hide file tree
Showing 5 changed files with 422 additions and 195 deletions.
104 changes: 0 additions & 104 deletions includes/class-newspack-listings-core.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ public static function instance() {
public function __construct() {
add_action( 'admin_menu', [ __CLASS__, 'add_plugin_page' ] );
add_action( 'init', [ __CLASS__, 'register_post_types' ] );
add_action( 'admin_init', [ __CLASS__, 'convert_legacy_taxonomies' ] );
add_filter( 'body_class', [ __CLASS__, 'set_template_class' ] );
add_action( 'save_post', [ __CLASS__, 'sync_post_meta' ], 10, 2 );
add_filter( 'newspack_listings_hide_author', [ __CLASS__, 'hide_author' ] );
Expand Down Expand Up @@ -842,109 +841,6 @@ public static function support_newspack_sponsors( $post_types ) {
array_values( self::NEWSPACK_LISTINGS_POST_TYPES )
);
}

/**
* Convert legacy custom taxonomies to regular post categories and tags.
* Helpful for sites that have been using v1 of the Listings plugin.
*/
public static function convert_legacy_taxonomies() {
$custom_category_slug = 'newspack_lst_cat';
$custom_tag_slug = 'newspack_lst_tag';

$category_args = [
'hierarchical' => true,
'public' => false,
'rewrite' => false,
'show_in_menu' => false,
'show_in_rest' => false,
'show_tagcloud' => false,
'show_ui' => false,
];
$tag_args = [
'hierarchical' => false,
'public' => false,
'rewrite' => false,
'show_in_menu' => false,
'show_in_rest' => false,
'show_tagcloud' => false,
'show_ui' => false,
];

// Temporarily register the taxonomies for all Listing CPTs.
$post_types = array_values( self::NEWSPACK_LISTINGS_POST_TYPES );
register_taxonomy( $custom_category_slug, $post_types, $category_args );
register_taxonomy( $custom_tag_slug, $post_types, $tag_args );

// Get a list of the custom terms.
$custom_terms = get_terms(
[
'taxonomy' => [ $custom_category_slug, $custom_tag_slug ],
'hide_empty' => false,
]
);

// If we don't have any terms from the legacy taxonomies, no need to proceed.
if ( is_wp_error( $custom_terms ) || 0 === count( $custom_terms ) ) {
unregister_taxonomy( $custom_category_slug );
unregister_taxonomy( $custom_tag_slug );
return;
}

foreach ( $custom_terms as $term ) {
// See if we have any corresponding terms already.
$corresponding_taxonomy = $custom_category_slug === $term->taxonomy ? 'category' : 'post_tag';
$corresponding_term = get_term_by( 'name', $term->name, $corresponding_taxonomy, ARRAY_A );

// If not, create the term.
if ( ! $corresponding_term ) {
$corresponding_term = wp_insert_term(
$term->name,
$corresponding_taxonomy,
[
'description' => $term->description,
'slug' => $term->slug,
]
);
}

// Get any posts with the legacy term.
$posts_with_custom_term = new \WP_Query(
[
'post_type' => $post_types,
'per_page' => 1000,
'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
[
'taxonomy' => $term->taxonomy,
'field' => 'term_id',
'terms' => $term->term_id,
],
],
]
);

// Apply the new term to the post.
if ( $posts_with_custom_term->have_posts() ) {
while ( $posts_with_custom_term->have_posts() ) {
$posts_with_custom_term->the_post();
wp_set_post_terms(
get_the_ID(), // Post ID to apply the new term.
[ $corresponding_term['term_id'] ], // Term ID of the new term.
$corresponding_taxonomy, // Category or tag.
true // Append the term without deleting existing terms.
);
}
}

// Finally, delete the legacy term.
if ( defined( 'NEWSPACK_LISTINGS_ENV' ) && 'production' === NEWSPACK_LISTINGS_ENV ) {
wp_delete_term( $term->term_id, $term->taxonomy );
}
}

// Unregister the legacy taxonomies.
unregister_taxonomy( $custom_category_slug );
unregister_taxonomy( $custom_tag_slug );
}
}

Newspack_Listings_Core::instance();
87 changes: 0 additions & 87 deletions includes/class-newspack-listings-taxonomies.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ public static function instance() {
*/
public function __construct() {
add_action( 'init', [ __CLASS__, 'init' ] );
add_action( 'admin_init', [ __CLASS__, 'handle_missing_terms' ] );
add_action( 'admin_init', [ __CLASS__, 'handle_orphaned_terms' ] );
add_filter( 'rest_prepare_taxonomy', [ __CLASS__, 'hide_taxonomy_sidebar' ], 10, 2 );
register_activation_hook( NEWSPACK_LISTINGS_FILE, [ __CLASS__, 'activation_hook' ] );
}
Expand Down Expand Up @@ -395,91 +393,6 @@ public static function create_shadow_term( $post, $taxonomy = null ) {
return $new_term;
}

/**
* Handle any published posts of the relevant types that are missing a corresponding shadow term.
*/
public static function handle_missing_terms() {
$tax_query = [ 'relation' => 'OR' ];

foreach ( self::NEWSPACK_LISTINGS_TAXONOMIES as $post_type_to_shadow => $shadow_taxonomy ) {
$tax_query[] = [
'taxonomy' => $shadow_taxonomy,
'operator' => 'NOT EXISTS',
];
}

$query = new \WP_Query(
[
'post_type' => self::get_post_types_to_shadow(),
'post_status' => 'publish',
'posts_per_page' => 100,
'tax_query' => $tax_query, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
]
);

if ( $query->have_posts() ) {
while ( $query->have_posts() ) {
$query->the_post();
$post_id = get_the_ID();
$post = get_post( $post_id );
$post_type = $post->post_type;
$term_slug = array_keys( Core::NEWSPACK_LISTINGS_POST_TYPES, $post_type );
$term_slug = reset( $term_slug );

// Bail if not a post type to be shadowed.
if ( empty( $term_slug ) || ! self::should_update_shadow_term( $post ) ) {
continue;
}

// Check for a shadow term associated with this post.
$shadow_term = self::get_shadow_term( $post, self::NEWSPACK_LISTINGS_TAXONOMIES[ $term_slug ] );

// If there isn't already a shadow term, create it. Otherwise, apply the term to the post.
if ( empty( $shadow_term ) ) {
self::create_shadow_term( $post, self::NEWSPACK_LISTINGS_TAXONOMIES[ $term_slug ] );
} else {
wp_set_post_terms( $post_id, $shadow_term->term_id, self::NEWSPACK_LISTINGS_TAXONOMIES[ $term_slug ], true );
}
}
}
}

/**
* Delete any shadow terms that no longer have a post to shadow.
*/
public static function handle_orphaned_terms() {
$all_terms = get_terms(
[
'taxonomy' => array_values( self::NEWSPACK_LISTINGS_TAXONOMIES ),
'hide_empty' => false,
]
);
$term_slugs = array_column( $all_terms, 'slug' );
$query = new \WP_Query(
[
'post_type' => self::get_post_types_to_shadow(),
'post_status' => 'publish',
'posts_per_page' => 100,
'post_name__in' => $term_slugs,
]
);

if ( $query->have_posts() ) {
$post_slugs = array_column( $query->posts, 'post_name' );
$orphaned_slugs = array_diff( $term_slugs, $post_slugs );
$orphaned_terms = array_filter(
$all_terms,
function( $term ) use ( $orphaned_slugs ) {
return in_array( $term->slug, $orphaned_slugs );
}
);

foreach ( $orphaned_terms as $orphaned_term ) {
wp_delete_term( $orphaned_term->term_id, $orphaned_term->taxonomy );
}
}
}

/**
* Workaround to hide the default taxonomy sidebars, since we're registering our own UI for managing shadow terms.
* The taxonomies must be available via REST from our custom UI, but hidden to Gutenberg's default taxonomy UI.
Expand Down
8 changes: 4 additions & 4 deletions includes/importer/class-newspack-listings-importer.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,13 @@ public static function run_cli_command( $args, $assoc_args ) {
}

if ( self::$is_dry_run ) {
WP_CLI::line( "\n===================\n= Dry Run =\n===================\n" );
WP_CLI::log( "\n===================\n= Dry Run =\n===================\n" );
}

if ( 0 < $start_row ) {
WP_CLI::line( 'Starting CSV import at row ' . $start_row . '...' );
WP_CLI::log( 'Starting CSV import at row ' . $start_row . '...' );
} else {
WP_CLI::line( 'Starting CSV import...' );
WP_CLI::log( 'Starting CSV import...' );
}
self::import_data( $file_path, $start_row, $max_rows );
WP_CLI::success( 'Completed! Processed ' . ( self::$row_number - $start_row ) . ' records.' );
Expand Down Expand Up @@ -301,7 +301,7 @@ public static function import_listing( $data ) {
],
];

WP_CLI::line( 'Importing data for ' . $post['post_title'] . '...' );
WP_CLI::log( 'Importing data for ' . $post['post_title'] . '...' );

// If a post already exists, update it.
if ( $existing_post ) {
Expand Down
Loading

0 comments on commit 4576805

Please sign in to comment.