Views: 6
Welcome to Episode 7 of building the Versana Companion plugin! In Episode 6, we created professional XML demo files. Now it’s time to bring them to life with a complete WordPress demo import system.
In this episode, you’ll learn how to process XML files, create posts and pages programmatically, set up navigation menus automatically, adjust theme.json settings per demo, and provide users with cleanup options—all in a user-friendly interface.
By the end of this tutorial, users will be able to import complete demo sites with one click, including content, menus, and styling!
What We’ll Cover
- Processing WordPress XML export files
- Creating posts and pages with wp_insert_post()
- Handling categories and tags
- Creating navigation menus automatically
- Adjusting theme.json per demo
- Progress tracking and user feedback
- Cleanup and rollback functionality
- Testing the complete import system
Prerequisites
- Completed Episodes 2-6
- XML demo files in place (from Episode 6)
- Text editor open
- Understanding of WordPress functions
Part 1: Understanding the Complete Import Process
What Happens During Import?
User clicks “Import” button:
- ✅ Read XML file
- ✅ Parse XML content
- ✅ Check for duplicates
- ✅ Create categories and tags
- ✅ Import posts and pages
- ✅ Create navigation menu
- ✅ Set homepage and blog page
- ✅ Apply demo-specific theme.json
- ✅ Track imported items for cleanup
- ✅ Show success message
Complete workflow:
User Action → Security Check → XML Processing → Content Import →
Menu Creation → Theme Config → Progress Updates → Success/Error
Data We Need to Track
For cleanup purposes:
// Store imported item IDs in wp_options
versana_imported_demo_data = array(
'demo_key' => 'business',
'posts' => array(10, 11, 12, 13, 14),
'pages' => array(20, 21, 22),
'categories' => array(5, 6),
'tags' => array(10, 11, 12),
'menus' => array(3),
'import_date' => '2024-03-07 10:30:00'
)
Part 2: XML Processing Functions
Step 1: Enhanced XML Parser
Update the XML parser from Episode 6 to extract all necessary data:
/**
* Parse demo XML file
*
* Extracts posts, pages, categories, and tags
*
* @param string $xml_content XML file content
* @return array|false Parsed data or false on failure
*/
function versana_companion_parse_demo_xml( $xml_content ) {
// Suppress XML errors
libxml_use_internal_errors( true );
// Load XML
$xml = simplexml_load_string( $xml_content );
if ( $xml === false ) {
return false;
}
// Register namespaces
$namespaces = $xml->getNamespaces( true );
$parsed_data = array(
'title' => (string) $xml->channel->title,
'posts' => array(),
'pages' => array(),
'categories' => array(),
'tags' => array(),
);
// Process each item
foreach ( $xml->channel->item as $item ) {
// Get namespaced elements
$wp = $item->children( $namespaces['wp'] );
$content = $item->children( $namespaces['content'] );
$excerpt = $item->children( $namespaces['excerpt'] );
// Get post type
$post_type = (string) $wp->post_type;
// Build item data
$item_data = array(
'title' => (string) $item->title,
'content' => (string) $content->encoded,
'excerpt' => (string) $excerpt->encoded,
'post_type' => $post_type,
'status' => (string) $wp->status,
'post_name' => (string) $wp->post_name,
'post_date' => (string) $wp->post_date,
'categories' => array(),
'tags' => array(),
);
// Extract categories and tags
foreach ( $item->category as $category ) {
$domain = (string) $category['domain'];
$name = (string) $category;
$nicename = (string) $category['nicename'];
if ( $domain === 'category' ) {
$item_data['categories'][] = $name;
// Track unique categories
if ( ! in_array( $name, $parsed_data['categories'] ) ) {
$parsed_data['categories'][] = $name;
}
} elseif ( $domain === 'post_tag' ) {
$item_data['tags'][] = $name;
// Track unique tags
if ( ! in_array( $name, $parsed_data['tags'] ) ) {
$parsed_data['tags'][] = $name;
}
}
}
// Add to appropriate array
if ( $post_type === 'post' ) {
$parsed_data['posts'][] = $item_data;
} elseif ( $post_type === 'page' ) {
$parsed_data['pages'][] = $item_data;
}
}
return $parsed_data;
}
Understanding the enhancements:
$namespaces = $xml->getNamespaces( true );
- Get all XML namespaces (wp:, content:, excerpt:)
- Required to access namespaced elements
$wp = $item->children( $namespaces['wp'] );
- Access wp: namespaced elements
- Contains post_type, status, post_name, etc.
foreach ( $item->category as $category ) {
$domain = (string) $category['domain'];
- Categories and tags both use
<category>element - Distinguish by domain attribute
domain="category"= Categorydomain="post_tag"= Tag
Part 3: Creating Posts and Pages
Step 2: Import Content Function
/**
* Import posts and pages from parsed data
*
* Creates WordPress posts and pages
*
* @param array $parsed_data Parsed XML data
* @return array Import results with IDs
*/
function versana_companion_import_content( $parsed_data ) {
$results = array(
'success' => true,
'posts' => array(),
'pages' => array(),
'categories' => array(),
'tags' => array(),
'errors' => array(),
'skipped' => array(),
);
// First, create categories
foreach ( $parsed_data['categories'] as $category_name ) {
$cat_id = versana_companion_create_category( $category_name );
if ( $cat_id ) {
$results['categories'][] = $cat_id;
}
}
// Create tags
foreach ( $parsed_data['tags'] as $tag_name ) {
$tag_id = versana_companion_create_tag( $tag_name );
if ( $tag_id ) {
$results['tags'][] = $tag_id;
}
}
// Import posts
foreach ( $parsed_data['posts'] as $post_data ) {
// Check if already exists
if ( versana_companion_post_exists( $post_data['title'], 'post' ) ) {
$results['skipped'][] = array(
'title' => $post_data['title'],
'type' => 'post',
);
continue;
}
// Prepare post arguments
$post_args = array(
'post_title' => $post_data['title'],
'post_content' => $post_data['content'],
'post_excerpt' => $post_data['excerpt'],
'post_status' => $post_data['status'],
'post_type' => 'post',
'post_name' => $post_data['post_name'],
'post_author' => get_current_user_id(),
);
// Create post
$post_id = wp_insert_post( $post_args, true );
if ( is_wp_error( $post_id ) ) {
$results['errors'][] = array(
'title' => $post_data['title'],
'error' => $post_id->get_error_message(),
);
$results['success'] = false;
} else {
$results['posts'][] = $post_id;
// Assign categories
if ( ! empty( $post_data['categories'] ) ) {
$category_ids = array();
foreach ( $post_data['categories'] as $cat_name ) {
$term = get_term_by( 'name', $cat_name, 'category' );
if ( $term ) {
$category_ids[] = $term->term_id;
}
}
if ( ! empty( $category_ids ) ) {
wp_set_post_categories( $post_id, $category_ids );
}
}
// Assign tags
if ( ! empty( $post_data['tags'] ) ) {
wp_set_post_tags( $post_id, $post_data['tags'] );
}
}
}
// Import pages
foreach ( $parsed_data['pages'] as $page_data ) {
if ( versana_companion_post_exists( $page_data['title'], 'page' ) ) {
$results['skipped'][] = array(
'title' => $page_data['title'],
'type' => 'page',
);
continue;
}
$page_args = array(
'post_title' => $page_data['title'],
'post_content' => $page_data['content'],
'post_status' => $page_data['status'],
'post_type' => 'page',
'post_name' => $page_data['post_name'],
'post_author' => get_current_user_id(),
);
$page_id = wp_insert_post( $page_args, true );
if ( is_wp_error( $page_id ) ) {
$results['errors'][] = array(
'title' => $page_data['title'],
'error' => $page_id->get_error_message(),
);
$results['success'] = false;
} else {
$results['pages'][] = $page_id;
}
}
return $results;
}
/**
* Create category if doesn't exist
*
* @param string $category_name Category name
* @return int|false Category ID or false
*/
function versana_companion_create_category( $category_name ) {
$term = get_term_by( 'name', $category_name, 'category' );
if ( $term ) {
return $term->term_id;
}
$result = wp_insert_term( $category_name, 'category' );
if ( is_wp_error( $result ) ) {
return false;
}
return $result['term_id'];
}
/**
* Create tag if doesn't exist
*
* @param string $tag_name Tag name
* @return int|false Tag ID or false
*/
function versana_companion_create_tag( $tag_name ) {
$term = get_term_by( 'name', $tag_name, 'post_tag' );
if ( $term ) {
return $term->term_id;
}
$result = wp_insert_term( $tag_name, 'post_tag' );
if ( is_wp_error( $result ) ) {
return false;
}
return $result['term_id'];
}
/**
* Check if post exists by title
*
* @param string $title Post title
* @param string $post_type Post type
* @return bool True if exists
*/
function versana_companion_post_exists( $title, $post_type = 'post' ) {
global $wpdb;
$result = $wpdb->get_var(
$wpdb->prepare(
"SELECT ID FROM $wpdb->posts
WHERE post_title = %s
AND post_type = %s
AND post_status != 'trash'",
$title,
$post_type
)
);
return ! empty( $result );
}
Part 4: Creating Navigation Menus
Step 3: Automatic Menu Creation
/**
* Create navigation menu for demo
*
* Creates menu with main pages
*
* @param array $page_ids Array of page IDs
* @param string $demo_key Demo identifier
* @return int|false Menu ID or false
*/
function versana_companion_create_demo_menu( $page_ids, $demo_key ) {
// Create menu
$menu_name = ucfirst( $demo_key ) . ' Menu';
$menu_id = wp_create_nav_menu( $menu_name );
if ( is_wp_error( $menu_id ) ) {
return false;
}
// Get Home page (usually first page)
$home_page_id = isset( $page_ids[0] ) ? $page_ids[0] : 0;
// Menu order counter
$menu_order = 1;
// Add pages to menu
foreach ( $page_ids as $page_id ) {
// Skip home page, we'll add it first
if ( $page_id === $home_page_id ) {
continue;
}
wp_update_nav_menu_item( $menu_id, 0, array(
'menu-item-object-id' => $page_id,
'menu-item-object' => 'page',
'menu-item-type' => 'post_type',
'menu-item-status' => 'publish',
'menu-item-position' => $menu_order++,
) );
}
// Add "Blog" link to posts page
wp_update_nav_menu_item( $menu_id, 0, array(
'menu-item-title' => 'Blog',
'menu-item-url' => home_url( '/blog/' ),
'menu-item-status' => 'publish',
'menu-item-type' => 'custom',
'menu-item-position' => $menu_order++,
) );
// Assign menu to primary location
$locations = get_theme_mod( 'nav_menu_locations' );
$locations['primary'] = $menu_id;
set_theme_mod( 'nav_menu_locations', $locations );
return $menu_id;
}
Understanding menu creation:
$menu_id = wp_create_nav_menu( $menu_name );
- Creates new navigation menu
- Returns menu ID or WP_Error
wp_update_nav_menu_item( $menu_id, 0, $args );
- Adds item to menu
- First param: Menu ID
- Second param: 0 = Create new item
- Third param: Item configuration
'menu-item-object-id' => $page_id,
'menu-item-object' => 'page',
'menu-item-type' => 'post_type',
- Links to a page post type
- Uses page ID
set_theme_mod( 'nav_menu_locations', $locations );
- Assigns menu to theme location
- ‘primary’ = Main navigation location
Part 5: Setting Homepage and Blog Page
Step 4: Configure Reading Settings
/**
* Set homepage and blog page
*
* Configures WordPress reading settings
*
* @param array $page_ids Array of page IDs
* @return bool Success status
*/
function versana_companion_set_reading_settings( $page_ids ) {
if ( empty( $page_ids ) ) {
return false;
}
// Get Home page (first page in XML)
$home_page_id = $page_ids[0];
// Create "Blog" page if doesn't exist
$blog_page_id = versana_companion_create_blog_page();
// Set front page to display static page
update_option( 'show_on_front', 'page' );
// Set homepage
update_option( 'page_on_front', $home_page_id );
// Set blog page
if ( $blog_page_id ) {
update_option( 'page_for_posts', $blog_page_id );
}
return true;
}
/**
* Create blog page for posts
*
* @return int|false Page ID or false
*/
function versana_companion_create_blog_page() {
// Check if Blog page exists
$blog_page = get_page_by_title( 'Blog' );
if ( $blog_page ) {
return $blog_page->ID;
}
// Create Blog page
$page_id = wp_insert_post( array(
'post_title' => 'Blog',
'post_content' => '',
'post_status' => 'publish',
'post_type' => 'page',
'post_name' => 'blog',
) );
if ( is_wp_error( $page_id ) ) {
return false;
}
return $page_id;
}
What this does:
update_option( 'show_on_front', 'page' );
- Changes “Your homepage displays” to “A static page”
- Instead of latest posts
update_option( 'page_on_front', $home_page_id );
- Sets which page is homepage
- Uses first page from XML (usually “Home”)
update_option( 'page_for_posts', $blog_page_id );
- Sets which page shows blog posts
- Creates “Blog” page if needed
Part 6: Demo-Specific Theme Configuration
Step 5: Apply Theme Settings Per Demo
/**
* Apply demo-specific theme configuration
*
* Different settings for each demo
*
* @param string $demo_key Demo identifier
* @return bool Success status
*/
function versana_companion_apply_demo_config( $demo_key ) {
$configs = array(
'business' => array(
'blog_layout' => 'list',
'blog_sidebar_position' => 'right',
'archive_layout' => 'list',
'enable_sticky_header' => true,
'enable_back_to_top' => true,
),
'blog' => array(
'blog_layout' => '2col',
'blog_sidebar_position' => 'right',
'archive_layout' => '2col',
'enable_sticky_header' => false,
'enable_back_to_top' => true,
),
'portfolio' => array(
'blog_layout' => '3col',
'blog_sidebar_position' => 'none',
'archive_layout' => '3col',
'enable_sticky_header' => true,
'enable_back_to_top' => true,
),
);
if ( ! isset( $configs[ $demo_key ] ) ) {
return false;
}
$config = $configs[ $demo_key ];
// Get existing theme options
$theme_options = get_option( 'versana_theme_options', array() );
// Merge with demo config
$theme_options = array_merge( $theme_options, $config );
// Update theme options
update_option( 'versana_theme_options', $theme_options );
return true;
}
Configuration differences:
Business Demo:
- List layout (professional)
- Right sidebar (traditional)
- Sticky header (always visible)
Blog Demo:
- 2-column grid (more visual)
- Right sidebar (familiar)
- No sticky header (cleaner scroll)
Portfolio Demo:
- 3-column grid (showcase)
- No sidebar (full width for projects)
- Sticky header (navigation always accessible)
Part 7: Tracking Imported Data
Step 6: Store Import Information
/**
* Save imported data for cleanup
*
* Stores all created IDs for future deletion
*
* @param string $demo_key Demo identifier
* @param array $import_results Import results array
* @return bool Success status
*/
function versana_companion_save_import_data( $demo_key, $import_results ) {
$import_data = array(
'demo_key' => $demo_key,
'posts' => $import_results['posts'],
'pages' => $import_results['pages'],
'categories' => $import_results['categories'],
'tags' => $import_results['tags'],
'menu_id' => isset( $import_results['menu_id'] ) ? $import_results['menu_id'] : 0,
'blog_page_id' => isset( $import_results['blog_page_id'] ) ? $import_results['blog_page_id'] : 0,
'import_date' => current_time( 'mysql' ),
);
update_option( 'versana_imported_demo_data', $import_data );
return true;
}
/**
* Get imported demo data
*
* @return array|false Import data or false
*/
function versana_companion_get_import_data() {
return get_option( 'versana_imported_demo_data', false );
}
/**
* Check if demo is currently imported
*
* @return bool True if demo imported
*/
function versana_companion_has_imported_demo() {
$import_data = versana_companion_get_import_data();
return ! empty( $import_data );
}
Part 8: Complete Import Process
Step 7: Main Import Function
/**
* Process complete demo import
*
* Main function coordinating entire import
*/
function versana_companion_process_demo_import() {
// Security check
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Verify nonce
if ( ! isset( $_POST['versana_import_nonce'] ) ||
! wp_verify_nonce( $_POST['versana_import_nonce'], 'versana_import_demo' ) ) {
add_settings_error(
'versana_companion_messages',
'versana_import_error',
'Security verification failed',
'error'
);
return;
}
// Get demo key
$demo_key = isset( $_POST['demo_key'] ) ? sanitize_key( $_POST['demo_key'] ) : '';
if ( empty( $demo_key ) ) {
add_settings_error(
'versana_companion_messages',
'versana_import_error',
'Invalid demo selected',
'error'
);
return;
}
// Get demo info
$demo = versana_companion_get_demo( $demo_key );
if ( ! $demo || ! file_exists( $demo['xml_file'] ) ) {
add_settings_error(
'versana_companion_messages',
'versana_import_error',
'Demo file not found',
'error'
);
return;
}
// Read XML file
$xml_content = file_get_contents( $demo['xml_file'] );
if ( $xml_content === false ) {
add_settings_error(
'versana_companion_messages',
'versana_import_error',
'Could not read demo file',
'error'
);
return;
}
// Parse XML
$parsed_data = versana_companion_parse_demo_xml( $xml_content );
if ( $parsed_data === false ) {
add_settings_error(
'versana_companion_messages',
'versana_import_error',
'Could not parse XML file',
'error'
);
return;
}
// Import content
$import_results = versana_companion_import_content( $parsed_data );
if ( ! $import_results['success'] ) {
add_settings_error(
'versana_companion_messages',
'versana_import_error',
'Import completed with errors',
'error'
);
return;
}
// Create navigation menu
if ( ! empty( $import_results['pages'] ) ) {
$menu_id = versana_companion_create_demo_menu( $import_results['pages'], $demo_key );
$import_results['menu_id'] = $menu_id;
}
// Set homepage and blog page
if ( ! empty( $import_results['pages'] ) ) {
versana_companion_set_reading_settings( $import_results['pages'] );
$blog_page = get_page_by_title( 'Blog' );
if ( $blog_page ) {
$import_results['blog_page_id'] = $blog_page->ID;
}
}
// Apply demo configuration
versana_companion_apply_demo_config( $demo_key );
// Save import data for cleanup
versana_companion_save_import_data( $demo_key, $import_results );
// Build success message
$message = sprintf(
'Demo imported successfully! Created %d posts, %d pages, and navigation menu.',
count( $import_results['posts'] ),
count( $import_results['pages'] )
);
if ( ! empty( $import_results['skipped'] ) ) {
$message .= sprintf( ' Skipped %d existing items.', count( $import_results['skipped'] ) );
}
add_settings_error(
'versana_companion_messages',
'versana_import_success',
$message,
'updated'
);
}
Part 9: Cleanup and Rollback
Step 8: Delete Imported Demo
/**
* Delete imported demo content
*
* Removes all content created by import
*/
function versana_companion_cleanup_demo() {
// Security check
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Verify nonce
if ( ! isset( $_POST['versana_cleanup_nonce'] ) ||
! wp_verify_nonce( $_POST['versana_cleanup_nonce'], 'versana_cleanup_demo' ) ) {
add_settings_error(
'versana_companion_messages',
'versana_cleanup_error',
'Security verification failed',
'error'
);
return;
}
// Get import data
$import_data = versana_companion_get_import_data();
if ( ! $import_data ) {
add_settings_error(
'versana_companion_messages',
'versana_cleanup_error',
'No imported demo found',
'error'
);
return;
}
$deleted_counts = array(
'posts' => 0,
'pages' => 0,
'categories' => 0,
'tags' => 0,
);
// Delete posts
foreach ( $import_data['posts'] as $post_id ) {
if ( wp_delete_post( $post_id, true ) ) {
$deleted_counts['posts']++;
}
}
// Delete pages
foreach ( $import_data['pages'] as $page_id ) {
if ( wp_delete_post( $page_id, true ) ) {
$deleted_counts['pages']++;
}
}
// Delete blog page if created
if ( ! empty( $import_data['blog_page_id'] ) ) {
wp_delete_post( $import_data['blog_page_id'], true );
}
// Delete categories (only if empty)
foreach ( $import_data['categories'] as $cat_id ) {
wp_delete_term( $cat_id, 'category' );
$deleted_counts['categories']++;
}
// Delete tags (only if not used)
foreach ( $import_data['tags'] as $tag_id ) {
wp_delete_term( $tag_id, 'post_tag' );
$deleted_counts['tags']++;
}
// Delete menu
if ( ! empty( $import_data['menu_id'] ) ) {
wp_delete_nav_menu( $import_data['menu_id'] );
}
// Reset homepage to posts
update_option( 'show_on_front', 'posts' );
delete_option( 'page_on_front' );
delete_option( 'page_for_posts' );
// Delete import data
delete_option( 'versana_imported_demo_data' );
// Success message
$message = sprintf(
'Demo removed! Deleted %d posts, %d pages, %d categories, and %d tags.',
$deleted_counts['posts'],
$deleted_counts['pages'],
$deleted_counts['categories'],
$deleted_counts['tags']
);
add_settings_error(
'versana_companion_messages',
'versana_cleanup_success',
$message,
'updated'
);
}
Part 10: User Interface Updates
Step 9: Enhanced Demo Import Tab
Update the demo import tab to show import status and cleanup option:
/**
* Render Demo Import tab with cleanup
*/
function versana_companion_render_demo_import_tab() {
// Check if demo import enabled
if ( ! versana_companion_get_setting( 'enable_demo_import', true ) ) {
?>
<div class="versana-tab-content">
<div class="notice notice-warning">
<p><?php esc_html_e( 'Demo import is disabled. Enable in Companion tab.', 'versana-companion' ); ?></p>
</div>
</div>
<?php
return;
}
// Check if demo already imported
$imported_demo = versana_companion_get_import_data();
// Get available demos
$demos = versana_companion_get_available_demos();
?>
<div class="versana-tab-content">
<h2><?php esc_html_e( 'Import Demo Content', 'versana-companion' ); ?></h2>
<?php if ( $imported_demo ) : ?>
<!-- Currently Imported Demo Notice -->
<div class="notice notice-info">
<p>
<strong><?php esc_html_e( 'Demo Currently Imported:', 'versana-companion' ); ?></strong>
<?php echo esc_html( ucfirst( $imported_demo['demo_key'] ) ); ?>
<?php esc_html_e( 'demo imported on', 'versana-companion' ); ?>
<?php echo esc_html( date_i18n( get_option( 'date_format' ), strtotime( $imported_demo['import_date'] ) ) ); ?>
</p>
<p>
<?php
printf(
esc_html__( 'Includes %d posts, %d pages, and navigation menu.', 'versana-companion' ),
count( $imported_demo['posts'] ),
count( $imported_demo['pages'] )
);
?>
</p>
<form method="post" action="" style="margin-top: 15px;">
<?php wp_nonce_field( 'versana_cleanup_demo', 'versana_cleanup_nonce' ); ?>
<button type="submit"
name="versana_cleanup_demo"
class="button button-secondary"
onclick="return confirm('<?php esc_attr_e( 'This will permanently delete all imported demo content. Are you sure?', 'versana-companion' ); ?>');">
<span class="dashicons dashicons-trash"></span>
<?php esc_html_e( 'Remove Imported Demo', 'versana-companion' ); ?>
</button>
</form>
</div>
<?php endif; ?>
<p class="description">
<?php esc_html_e( 'Choose a demo to import. This will add sample posts, pages, and create navigation menus.', 'versana-companion' ); ?>
</p>
<?php if ( $imported_demo ) : ?>
<p class="description">
<strong><?php esc_html_e( 'Note:', 'versana-companion' ); ?></strong>
<?php esc_html_e( 'You can import a different demo. Previous demo content will remain unless you remove it first.', 'versana-companion' ); ?>
</p>
<?php endif; ?>
<div class="versana-demo-library">
<?php foreach ( $demos as $demo_key => $demo ) : ?>
<div class="versana-demo-item <?php echo $imported_demo && $imported_demo['demo_key'] === $demo_key ? 'demo-imported' : ''; ?>">
<!-- Demo Thumbnail -->
<div class="demo-thumbnail">
<?php if ( ! empty( $demo['thumbnail'] ) && file_exists( str_replace( VERSANA_COMPANION_URL, VERSANA_COMPANION_PATH, $demo['thumbnail'] ) ) ) : ?>
<img src="<?php echo esc_url( $demo['thumbnail'] ); ?>"
alt="<?php echo esc_attr( $demo['name'] ); ?>">
<?php else : ?>
<div class="demo-thumbnail-placeholder">
<span class="dashicons dashicons-admin-appearance"></span>
</div>
<?php endif; ?>
<?php if ( $imported_demo && $imported_demo['demo_key'] === $demo_key ) : ?>
<div class="demo-imported-badge">
<span class="dashicons dashicons-yes-alt"></span>
<?php esc_html_e( 'Imported', 'versana-companion' ); ?>
</div>
<?php endif; ?>
</div>
<!-- Demo Info -->
<div class="demo-info">
<h3 class="demo-name"><?php echo esc_html( $demo['name'] ); ?></h3>
<p class="demo-description"><?php echo esc_html( $demo['description'] ); ?></p>
</div>
<!-- Demo Actions -->
<div class="demo-actions">
<?php if ( ! empty( $demo['preview_url'] ) ) : ?>
<a href="<?php echo esc_url( $demo['preview_url'] ); ?>"
class="button"
target="_blank">
<span class="dashicons dashicons-visibility"></span>
<?php esc_html_e( 'Preview', 'versana-companion' ); ?>
</a>
<?php endif; ?>
<?php if ( file_exists( $demo['xml_file'] ) ) : ?>
<form method="post" action="" style="display: inline;">
<?php wp_nonce_field( 'versana_import_demo', 'versana_import_nonce' ); ?>
<input type="hidden" name="demo_key" value="<?php echo esc_attr( $demo_key ); ?>">
<button type="submit"
name="versana_import_demo"
class="button button-primary"
onclick="return confirm('<?php esc_attr_e( 'This will import demo content. Continue?', 'versana-companion' ); ?>');">
<span class="dashicons dashicons-download"></span>
<?php esc_html_e( 'Import', 'versana-companion' ); ?>
</button>
</form>
<?php else : ?>
<button type="button" class="button" disabled>
<?php esc_html_e( 'File Missing', 'versana-companion' ); ?>
</button>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php
}
Step 10: Enhanced CSS for Import Status
Add to assets/css/demo-library.css:
/* Imported Demo Badge */
.demo-imported-badge {
position: absolute;
top: 10px;
right: 10px;
background: var(--wp--preset--color--success, #4CAF50);
color: white;
padding: 5px 12px;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
display: flex;
align-items: center;
gap: 5px;
z-index: 10;
}
.demo-imported-badge .dashicons {
font-size: 16px;
width: 16px;
height: 16px;
}
/* Imported Demo Card Styling */
.versana-demo-item.demo-imported {
border-color: var(--wp--preset--color--success, #4CAF50);
background: #f0f9f0;
}
/* Notice Styling */
.notice.notice-info p {
margin: 0.5em 0;
}
.notice.notice-info form {
margin-top: 10px;
}
.notice button .dashicons {
vertical-align: middle;
margin-right: 5px;
}
Part 11: Testing the Complete System
Step 11: Complete Testing Checklist
Test Import:
- ✅ Click “Import” on Business demo
- ✅ Verify success message shows
- ✅ Check Posts → All Posts (5 new posts)
- ✅ Check Pages → All Pages (3 new pages + Blog)
- ✅ Check Categories (Business category created)
- ✅ Check Tags (Technology, Leadership, etc. created)
- ✅ Check Appearance → Menus (Business Menu created)
- ✅ Check Settings → Reading (Homepage set correctly)
- ✅ Visit homepage (demo content displays)
- ✅ Visit /blog/ (posts show correctly)
- ✅ Check imported badge appears on Business demo
Test Cleanup:
- ✅ Note import date shown
- ✅ Click “Remove Imported Demo”
- ✅ Confirm deletion dialog
- ✅ Verify success message
- ✅ Check all posts deleted
- ✅ Check all pages deleted
- ✅ Check categories removed
- ✅ Check menu deleted
- ✅ Check homepage reset
- ✅ Badge removed from demo card
Test Re-Import:
- ✅ Import Blog demo (different demo)
- ✅ Verify Blog demo badge shows
- ✅ Verify Blog demo config applied (2-column layout)
- ✅ Previous Business content still exists if not cleaned
Conclusion
Congratulations! You’ve built a complete WordPress demo import system that creates posts, pages, menus, and applies theme configuration automatically – all with cleanup capability!
What We Accomplished
✅ XML file processing with namespaces ✅ Creating posts and pages programmatically ✅ Category and tag creation ✅ Automatic navigation menu creation ✅ Homepage and blog page configuration ✅ Demo-specific theme settings ✅ Import data tracking ✅ Complete cleanup and rollback ✅ User-friendly interface with status ✅ Duplicate prevention
What We Learned
Technical Skills:
- XML parsing with SimpleXML
wp_insert_post()for content creationwp_create_nav_menu()for menuswp_update_nav_menu_item()for menu items- WordPress reading settings
- Theme options management
- Data tracking for cleanup
- Batch deletion operations
Best Practices:
- Always check for duplicates
- Track imported data for cleanup
- Provide clear user feedback
- Allow rollback/undo
- Apply demo-specific config
- Security with nonces
- Proper error handling
- User confirmation dialogs
Key Takeaways
For WordPress demo import system:
- Track Everything – Store all created IDs
- User Control – Let users remove demos
- Clear Status – Show what’s imported
- Prevent Duplicates – Check before creating
- Complete Setup – Content + menus + config
- Reversible – Always allow undo
- User Friendly – Clear messages and confirmations
Your Plugin Progress
✓ Episode 2: Basic plugin
✓ Episode 3: Theme compatibility
✓ Episode 4: Settings integration
✓ Episode 5: Demo library display
✓ Episode 6: Professional XML content
✓ Episode 7: Complete import system ← YOU ARE HERE
→ Episode 8: Advanced features
Frequently Asked Questions
Q: What happens if user imports same demo twice? A: Posts are skipped (duplicate detection). Success message shows “Skipped X existing items.”
Q: Can user have multiple demos imported? A: Yes, but tracked data only stores one. They can manually delete if needed.
Q: What if import fails halfway? A: Partially created content remains. User can run cleanup to remove, then try again.
Q: Does cleanup remove user’s own content? A: No, only tracked demo content is removed. User content is safe.
Q: Can user edit imported content? A: Yes! It’s regular WordPress content. Edit freely.
Q: What about images in demo? A: This tutorial focuses on text content. Image handling in advanced episodes.
Q: Does this work with custom post types? A: Yes, just add support in XML parser and import function.
Q: How to add more menu locations? A: Modify menu creation function to set multiple locations.
Next Episode Preview
Episode 8: Advanced Features
We’ll learn:
- AJAX-powered import (no page reload)
- Progress bars during import
- Image downloading and importing
- Widget importing
- Advanced cleanup options
Congratulations on completing Episode 7! Your plugin now has a complete, production-ready demo import system!
Series: Building Versana Companion Plugin
Episode: 7 of ongoing series (Complete Import System)
Leave a Reply