✨feature: Assign feed season to a configurable taxonomy on import
Adds a 'Season taxonomy' setting (default empty) that points at an existing taxonomy registered against the destination post type. On every successful import the plugin reads itunes:season, looks up (or creates) a term with that numeric name in the configured taxonomy, and assigns it to the imported post via wp_set_object_terms. Behavior: - Empty option: no-op. Existing posts and installs without a season taxonomy are unaffected. - Tax that doesn't exist or isn't attached to the post type: no-op. - Feed item with no itunes:season: no-op. Manual taxonomy assignments are preserved on subsequent refreshes. - Otherwise: the feed value wins, on both create and update paths. The new assign_season_term() helper is public so it is unit-testable, and four smoke tests cover the lookup, create-on-miss, unconfigured, and no-season paths.
This commit is contained in:
@@ -175,4 +175,130 @@ final class SmokeTest extends TestCase {
|
||||
$this->assertSame( array(), $GLOBALS['rss2cpt_test_deleted_options'] );
|
||||
$this->assertSame( array(), $GLOBALS['rss2cpt_test_deleted_site_transients'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that assign_season_term resolves an existing numeric term and
|
||||
* assigns it to the post without creating a duplicate.
|
||||
*/
|
||||
public function test_assign_season_term_uses_existing_term(): void {
|
||||
rss2cpt_test_reset_term_stores();
|
||||
|
||||
$GLOBALS['rss2cpt_test_taxonomies']['series_season'] = true;
|
||||
$GLOBALS['rss2cpt_test_object_in_taxonomy']['podcast_episode']['series_season'] = true;
|
||||
$GLOBALS['rss2cpt_test_terms']['series_season']['8'] = 42;
|
||||
|
||||
$importer = new RSS2CPT\Import\Importer();
|
||||
$method = ( new \ReflectionClass( $importer ) )->getMethod( 'assign_season_term' );
|
||||
$method->setAccessible( true );
|
||||
|
||||
$post_type = ( new \ReflectionClass( $importer ) )->getProperty( 'post_type' );
|
||||
$post_type->setAccessible( true );
|
||||
$post_type->setValue( $importer, 'podcast_episode' );
|
||||
|
||||
$term_id = $method->invoke(
|
||||
$importer,
|
||||
123,
|
||||
array( 'season' => 8 ),
|
||||
array( 'season_taxonomy' => 'series_season' )
|
||||
);
|
||||
|
||||
$this->assertSame( 42, $term_id );
|
||||
$this->assertCount( 1, $GLOBALS['rss2cpt_test_object_terms'] );
|
||||
$this->assertSame(
|
||||
array(
|
||||
'object_id' => 123,
|
||||
'term_ids' => array( 42 ),
|
||||
'taxonomy' => 'series_season',
|
||||
),
|
||||
$GLOBALS['rss2cpt_test_object_terms'][0]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that assign_season_term creates the term when the feed exposes
|
||||
* a season number whose term does not yet exist (e.g. a brand-new season).
|
||||
*/
|
||||
public function test_assign_season_term_creates_missing_term(): void {
|
||||
rss2cpt_test_reset_term_stores();
|
||||
|
||||
$GLOBALS['rss2cpt_test_taxonomies']['series_season'] = true;
|
||||
$GLOBALS['rss2cpt_test_object_in_taxonomy']['podcast_episode']['series_season'] = true;
|
||||
|
||||
$importer = new RSS2CPT\Import\Importer();
|
||||
$method = ( new \ReflectionClass( $importer ) )->getMethod( 'assign_season_term' );
|
||||
$method->setAccessible( true );
|
||||
|
||||
$post_type = ( new \ReflectionClass( $importer ) )->getProperty( 'post_type' );
|
||||
$post_type->setAccessible( true );
|
||||
$post_type->setValue( $importer, 'podcast_episode' );
|
||||
|
||||
$term_id = $method->invoke(
|
||||
$importer,
|
||||
456,
|
||||
array( 'season' => 9 ),
|
||||
array( 'season_taxonomy' => 'series_season' )
|
||||
);
|
||||
|
||||
$this->assertGreaterThan( 0, $term_id );
|
||||
$this->assertArrayHasKey( '9', $GLOBALS['rss2cpt_test_terms']['series_season'] );
|
||||
$this->assertSame( $term_id, $GLOBALS['rss2cpt_test_terms']['series_season']['9'] );
|
||||
$this->assertCount( 1, $GLOBALS['rss2cpt_test_object_terms'] );
|
||||
$this->assertSame( array( 456, $term_id, 'series_season' ), array( $GLOBALS['rss2cpt_test_object_terms'][0]['object_id'], $GLOBALS['rss2cpt_test_object_terms'][0]['term_ids'][0], $GLOBALS['rss2cpt_test_object_terms'][0]['taxonomy'] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that assign_season_term is a no-op when no season taxonomy is
|
||||
* configured (the option is empty by default for installs that don't use one).
|
||||
*/
|
||||
public function test_assign_season_term_is_noop_when_taxonomy_unconfigured(): void {
|
||||
rss2cpt_test_reset_term_stores();
|
||||
|
||||
$importer = new RSS2CPT\Import\Importer();
|
||||
$method = ( new \ReflectionClass( $importer ) )->getMethod( 'assign_season_term' );
|
||||
$method->setAccessible( true );
|
||||
|
||||
$post_type = ( new \ReflectionClass( $importer ) )->getProperty( 'post_type' );
|
||||
$post_type->setAccessible( true );
|
||||
$post_type->setValue( $importer, 'podcast_episode' );
|
||||
|
||||
$result = $method->invoke(
|
||||
$importer,
|
||||
789,
|
||||
array( 'season' => 3 ),
|
||||
array( 'season_taxonomy' => '' )
|
||||
);
|
||||
|
||||
$this->assertFalse( $result );
|
||||
$this->assertSame( array(), $GLOBALS['rss2cpt_test_object_terms'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that assign_season_term is a no-op when the feed item has no
|
||||
* itunes:season value (so existing manual taxonomy assignments are not
|
||||
* stripped).
|
||||
*/
|
||||
public function test_assign_season_term_is_noop_when_feed_has_no_season(): void {
|
||||
rss2cpt_test_reset_term_stores();
|
||||
|
||||
$GLOBALS['rss2cpt_test_taxonomies']['series_season'] = true;
|
||||
$GLOBALS['rss2cpt_test_object_in_taxonomy']['podcast_episode']['series_season'] = true;
|
||||
|
||||
$importer = new RSS2CPT\Import\Importer();
|
||||
$method = ( new \ReflectionClass( $importer ) )->getMethod( 'assign_season_term' );
|
||||
$method->setAccessible( true );
|
||||
|
||||
$post_type = ( new \ReflectionClass( $importer ) )->getProperty( 'post_type' );
|
||||
$post_type->setAccessible( true );
|
||||
$post_type->setValue( $importer, 'podcast_episode' );
|
||||
|
||||
$result = $method->invoke(
|
||||
$importer,
|
||||
321,
|
||||
array( 'season' => 0 ),
|
||||
array( 'season_taxonomy' => 'series_season' )
|
||||
);
|
||||
|
||||
$this->assertFalse( $result );
|
||||
$this->assertSame( array(), $GLOBALS['rss2cpt_test_object_terms'] );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,21 @@ function rss2cpt_test_reset_delete_logs() {
|
||||
$GLOBALS['rss2cpt_test_deleted_options'] = array();
|
||||
$GLOBALS['rss2cpt_test_deleted_site_transients'] = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset in-memory term/taxonomy stores between tests.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
function rss2cpt_test_reset_term_stores() {
|
||||
$GLOBALS['rss2cpt_test_taxonomies'] = array();
|
||||
$GLOBALS['rss2cpt_test_object_in_taxonomy'] = array();
|
||||
$GLOBALS['rss2cpt_test_terms'] = array();
|
||||
$GLOBALS['rss2cpt_test_object_terms'] = array();
|
||||
$GLOBALS['rss2cpt_test_next_term_id'] = 1;
|
||||
}
|
||||
rss2cpt_test_reset_delete_logs();
|
||||
rss2cpt_test_reset_term_stores();
|
||||
|
||||
/**
|
||||
* Minimal do_action stub for isolated tests.
|
||||
@@ -98,5 +112,101 @@ function do_action( $hook, ...$args ) {
|
||||
unset( $hook, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* In-memory taxonomy registry for isolated tests.
|
||||
*
|
||||
* Populate via $GLOBALS['rss2cpt_test_taxonomies'][$tax] = true.
|
||||
*
|
||||
* @param string $taxonomy Taxonomy slug.
|
||||
* @return bool
|
||||
*/
|
||||
function taxonomy_exists( $taxonomy ) {
|
||||
return ! empty( $GLOBALS['rss2cpt_test_taxonomies'][ (string) $taxonomy ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* In-memory post-type/taxonomy attachment map for isolated tests.
|
||||
*
|
||||
* Populate via $GLOBALS['rss2cpt_test_object_in_taxonomy'][$post_type][$tax] = bool.
|
||||
*
|
||||
* @param string $post_type Post type slug.
|
||||
* @param string $taxonomy Taxonomy slug.
|
||||
* @return bool
|
||||
*/
|
||||
function is_object_in_taxonomy( $post_type, $taxonomy ) {
|
||||
return ! empty( $GLOBALS['rss2cpt_test_object_in_taxonomy'][ (string) $post_type ][ (string) $taxonomy ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* In-memory term store for isolated tests.
|
||||
*
|
||||
* Populate via $GLOBALS['rss2cpt_test_terms'][$tax][$name] = $term_id.
|
||||
*
|
||||
* @param string $field Field to look up (only 'name' is meaningful here).
|
||||
* @param string|int $value Value to look up.
|
||||
* @param string $taxonomy Taxonomy slug.
|
||||
* @return object|null
|
||||
*/
|
||||
function get_term_by( $field, $value, $taxonomy ) {
|
||||
$taxonomy = (string) $taxonomy;
|
||||
if ( 'name' !== $field ) {
|
||||
return null;
|
||||
}
|
||||
if ( empty( $GLOBALS['rss2cpt_test_terms'][ $taxonomy ][ (string) $value ] ) ) {
|
||||
return null;
|
||||
}
|
||||
return (object) array( 'term_id' => (int) $GLOBALS['rss2cpt_test_terms'][ $taxonomy ][ (string) $value ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* In-memory term creator for isolated tests.
|
||||
*
|
||||
* @param string $term Term name.
|
||||
* @param string $taxonomy Taxonomy slug.
|
||||
* @return array<string,int>|\WP_Error
|
||||
*/
|
||||
function wp_insert_term( $term, $taxonomy ) {
|
||||
$taxonomy = (string) $taxonomy;
|
||||
$term = (string) $term;
|
||||
if ( ! isset( $GLOBALS['rss2cpt_test_terms'][ $taxonomy ] ) ) {
|
||||
$GLOBALS['rss2cpt_test_terms'][ $taxonomy ] = array();
|
||||
}
|
||||
if ( isset( $GLOBALS['rss2cpt_test_terms'][ $taxonomy ][ $term ] ) ) {
|
||||
return array( 'term_id' => (int) $GLOBALS['rss2cpt_test_terms'][ $taxonomy ][ $term ] );
|
||||
}
|
||||
$next = ( isset( $GLOBALS['rss2cpt_test_next_term_id'] ) ? (int) $GLOBALS['rss2cpt_test_next_term_id'] : 1 );
|
||||
$GLOBALS['rss2cpt_test_next_term_id'] = $next + 1;
|
||||
$GLOBALS['rss2cpt_test_terms'][ $taxonomy ][ $term ] = $next;
|
||||
return array( 'term_id' => $next );
|
||||
}
|
||||
|
||||
/**
|
||||
* In-memory object-term linker for isolated tests.
|
||||
*
|
||||
* Records (post_id, term_ids[], taxonomy) on $GLOBALS['rss2cpt_test_object_terms'].
|
||||
*
|
||||
* @param int $object_id Object ID.
|
||||
* @param array|string $terms Term IDs or slugs.
|
||||
* @param string $taxonomy Taxonomy slug.
|
||||
*/
|
||||
function wp_set_object_terms( $object_id, $terms, $taxonomy ) {
|
||||
$terms = array_map( 'intval', (array) $terms );
|
||||
$GLOBALS['rss2cpt_test_object_terms'][] = array(
|
||||
'object_id' => (int) $object_id,
|
||||
'term_ids' => $terms,
|
||||
'taxonomy' => (string) $taxonomy,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal is_wp_error stub: tests construct WP_Error objects by hand.
|
||||
*
|
||||
* @param mixed $thing Thing to check.
|
||||
* @return bool
|
||||
*/
|
||||
function is_wp_error( $thing ) {
|
||||
return $thing instanceof \WP_Error;
|
||||
}
|
||||
|
||||
require_once dirname( __DIR__ ) . '/src/Options.php';
|
||||
require_once dirname( __DIR__ ) . '/src/Import/Importer.php';
|
||||
|
||||
Reference in New Issue
Block a user