ID, '_aumnexcart_migration_notes', true );
if ( '' === trim( (string) $notes ) ) {
return;
}
add_meta_box(
'aumnexcart_migration_notes',
__( 'AumNexCart Migration Notes', 'aumnexcart' ),
array( $this, 'render_notes_metabox' ),
'product',
'side',
'low'
);
}
/**
* Renders the stored migration notes as a bullet list.
*
* @param WP_Post $post
*/
public function render_notes_metabox( $post ) {
$notes = (string) get_post_meta( $post->ID, '_aumnexcart_migration_notes', true );
echo '
' . esc_html__( 'Recorded when this product was migrated from AumNexCart.', 'aumnexcart' ) . '
';
echo '';
foreach ( explode( "\n", $notes ) as $line ) {
$line = trim( $line );
if ( '' === $line ) {
continue;
}
echo '- ' . esc_html( $line ) . '
';
}
echo '
';
}
/**
* Detects WooCommerce. Returns true only when the main class is
* present, since the plugin file existing on disk is not enough.
*
* @return bool
*/
public static function is_woocommerce_active() {
return class_exists( 'WooCommerce' );
}
/**
* Counts products eligible for migration. Used for the preview
* step in the UI.
*
* @return int
*/
public static function count_products() {
$q = new WP_Query(
array(
'post_type' => AumNexCart_Post_Type::POST_TYPE,
'post_status' => array( 'publish', 'draft' ),
'posts_per_page' => 1,
'fields' => 'ids',
'no_found_rows' => false,
)
);
return (int) $q->found_posts;
}
/**
* Initiates a migration session. Stores a token in a transient
* and redirects back with it so the JS runner can begin.
*/
public function handle_start() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Insufficient permissions.', 'aumnexcart' ) );
}
check_admin_referer( self::NONCE );
if ( ! self::is_woocommerce_active() ) {
self::redirect_migration( array( 'mig_error' => 'no_woo' ) );
}
$token = wp_generate_password( 12, false );
$q = new WP_Query(
array(
'post_type' => AumNexCart_Post_Type::POST_TYPE,
'post_status' => array( 'publish', 'draft' ),
'posts_per_page' => -1,
'fields' => 'ids',
'orderby' => 'ID',
'order' => 'ASC',
)
);
set_transient(
self::TRANSIENT . $token,
array(
'ids' => $q->posts,
'total' => count( $q->posts ),
'processed' => 0,
'success' => 0,
'failed' => array(),
'results' => array(),
),
HOUR_IN_SECONDS
);
self::redirect_migration( array( 'mig_token' => $token ) );
}
/**
* Processes a batch of products. Returns progress JSON.
*/
public function ajax_batch() {
check_ajax_referer( self::NONCE, 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'aumnexcart' ) ) );
}
$token = isset( $_POST['token'] ) ? sanitize_text_field( wp_unslash( $_POST['token'] ) ) : '';
$state = get_transient( self::TRANSIENT . $token );
if ( ! is_array( $state ) ) {
wp_send_json_error( array( 'message' => __( 'Migration session expired.', 'aumnexcart' ) ) );
}
$processed = (int) $state['processed'];
$end = min( $processed + self::BATCH_SIZE, $state['total'] );
for ( $i = $processed; $i < $end; $i++ ) {
$source_id = $state['ids'][ $i ];
$title = get_the_title( $source_id );
$result = $this->migrate_one( $source_id );
if ( is_wp_error( $result ) ) {
$state['failed'][] = array(
'id' => $source_id,
'title' => $title,
'message' => $result->get_error_message(),
);
$state['results'][] = array(
'id' => $source_id,
'title' => $title,
'ok' => false,
'message' => $result->get_error_message(),
);
} else {
$state['success']++;
$state['results'][] = array(
'id' => $source_id,
'title' => $title,
'ok' => true,
'new_id' => $result['new_id'],
'edit_url' => admin_url( 'post.php?post=' . $result['new_id'] . '&action=edit' ),
'featured' => $result['featured'],
'gallery' => $result['gallery'],
'remote_skipped' => $result['remote_skipped'],
'price' => $result['price'],
'warnings' => $result['warnings'],
);
}
}
$state['processed'] = $end;
$done = $end >= $state['total'];
set_transient( self::TRANSIENT . $token, $state, HOUR_IN_SECONDS );
$response = array(
'processed' => $state['processed'],
'total' => $state['total'],
'success' => $state['success'],
'failed' => count( $state['failed'] ),
'done' => $done,
);
if ( $done ) {
$response['failures'] = $state['failed'];
$response['results'] = $state['results'];
delete_transient( self::TRANSIENT . $token );
}
wp_send_json_success( $response );
}
/**
* Migrates a single product. Returns a structured result array on
* success (surfaced in the migration report) or a WP_Error.
*
* @param int $source_id
* @return array|WP_Error {
* @type int $new_id New WooCommerce product ID.
* @type bool $featured Whether a featured image was set.
* @type int $gallery Number of gallery images transferred.
* @type int $remote_skipped Number of remote media URLs skipped.
* @type string $price Numeric price written to Woo, '' if none.
* @type array $warnings Human-readable per-product warnings.
* }
*/
private function migrate_one( $source_id ) {
$source = get_post( $source_id );
if ( ! $source ) {
return new WP_Error( 'not_found', __( 'Source product not found.', 'aumnexcart' ) );
}
$new_id = wp_insert_post(
array(
'post_type' => 'product',
'post_title' => $source->post_title,
'post_content' => $source->post_content,
'post_excerpt' => $source->post_excerpt,
'post_status' => $source->post_status,
'post_author' => $source->post_author,
'post_name' => $source->post_name . '-wc',
),
true
);
if ( is_wp_error( $new_id ) ) {
return $new_id;
}
/* WooCommerce required meta */
update_post_meta( $new_id, '_product_type', 'simple' );
update_post_meta( $new_id, '_manage_stock', 'no' );
update_post_meta( $new_id, '_visibility', 'visible' );
update_post_meta( $new_id, '_stock_status', 'instock' );
/* Per-product result accumulator surfaced in the migration report. */
$result = array(
'new_id' => (int) $new_id,
'featured' => false,
'gallery' => 0,
'remote_skipped' => 0,
'price' => '',
'warnings' => array(),
);
/* Price */
$price = AumNexCart_Meta::get_price( $source_id );
if ( 'range' === $price['type'] ) {
if ( '' !== $price['min'] ) {
update_post_meta( $new_id, '_price', $price['min'] );
update_post_meta( $new_id, '_regular_price', $price['min'] );
$result['price'] = (string) $price['min'];
}
if ( '' !== $price['max'] && $price['max'] !== $price['min'] ) {
/* translators: %s: max price value */
$result['warnings'][] = sprintf( __( 'Price range maximum (%s) not transferred — a Woo simple product carries a single price.', 'aumnexcart' ), $price['max'] );
}
} elseif ( 'text' === $price['type'] && '' !== $price['text'] ) {
/* translators: %s: original price text */
$result['warnings'][] = sprintf( __( 'Text price (“%s”) not transferred — the Woo product has no numeric price and will not be purchasable until you set one.', 'aumnexcart' ), $price['text'] );
}
/* Categories */
$terms = wp_get_object_terms( $source_id, AumNexCart_Post_Type::TAXONOMY, array( 'fields' => 'names' ) );
if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
$wc_term_ids = array();
foreach ( $terms as $name ) {
$existing = term_exists( $name, 'product_cat' );
if ( ! $existing ) {
$created = wp_insert_term( $name, 'product_cat' );
if ( ! is_wp_error( $created ) ) {
$wc_term_ids[] = (int) $created['term_id'];
}
} else {
$wc_term_ids[] = (int) ( is_array( $existing ) ? $existing['term_id'] : $existing );
}
}
if ( ! empty( $wc_term_ids ) ) {
wp_set_object_terms( $new_id, $wc_term_ids, 'product_cat' );
}
}
/* Featured image */
$thumb_id = get_post_thumbnail_id( $source_id );
if ( $thumb_id ) {
set_post_thumbnail( $new_id, $thumb_id );
$result['featured'] = true;
}
/* Media gallery — only attachments transfer cleanly. Exclude the
* featured image by ID (it may sit at any position, or appear more
* than once) and de-duplicate so nothing repeats in the gallery. */
$media = AumNexCart_Meta::get_media( $source_id );
$attachments = array();
$remote_urls = array();
foreach ( $media as $item ) {
if ( 'upload' === $item['source'] && $item['attachment_id'] ) {
$att_id = (int) $item['attachment_id'];
if ( $thumb_id && $att_id === (int) $thumb_id ) {
continue; // already used as the featured image
}
if ( ! in_array( $att_id, $attachments, true ) ) {
$attachments[] = $att_id;
}
} elseif ( ! in_array( $item['url'], $remote_urls, true ) ) {
$remote_urls[] = $item['url'];
}
}
if ( ! empty( $attachments ) ) {
update_post_meta( $new_id, '_product_image_gallery', implode( ',', $attachments ) );
}
$result['gallery'] = count( $attachments );
$result['remote_skipped'] = count( $remote_urls );
if ( ! empty( $remote_urls ) ) {
$result['warnings'][] = sprintf(
/* translators: %d: number of remote media URLs */
_n(
'%d remote media URL not migrated (upload it manually):',
'%d remote media URLs not migrated (upload them manually):',
count( $remote_urls ),
'aumnexcart'
),
count( $remote_urls )
) . ' ' . implode( ', ', $remote_urls );
}
/* Attributes — converted to WooCommerce custom attribute format */
$attrs = AumNexCart_Meta::get_attributes( $source_id );
if ( ! empty( $attrs ) ) {
$wc_attrs = array();
$position = 0;
foreach ( $attrs as $a ) {
$slug = sanitize_title( $a['key'] );
$wc_attrs[ $slug ] = array(
'name' => $a['key'],
'value' => $a['value'],
'position' => $position++,
'is_visible' => 1,
'is_variation' => 0,
'is_taxonomy' => 0,
);
}
update_post_meta( $new_id, '_product_attributes', $wc_attrs );
}
/* Contact actions are NOT applicable to Woo — record as note */
$contact = AumNexCart_Meta::get_contact( $source_id );
if ( ! empty( $contact ) ) {
$labels = array();
foreach ( $contact as $c ) {
$labels[] = $c['type'] . ': ' . $c['value'];
}
$result['warnings'][] = __( 'Contact actions not migrated (WooCommerce has no equivalent):', 'aumnexcart' )
. ' ' . implode( '; ', $labels );
}
if ( ! empty( $result['warnings'] ) ) {
update_post_meta( $new_id, '_aumnexcart_migration_notes', implode( "\n", $result['warnings'] ) );
}
return $result;
}
/**
* Renders the Migration tab content.
*/
public static function render_tab() {
$err = isset( $_GET['mig_error'] ) ? sanitize_text_field( wp_unslash( $_GET['mig_error'] ) ) : '';
if ( 'no_woo' === $err ) {
echo '' . esc_html__( 'WooCommerce is not active.', 'aumnexcart' ) . '
';
}
$token = isset( $_GET['mig_token'] ) ? sanitize_text_field( wp_unslash( $_GET['mig_token'] ) ) : '';
echo '' . esc_html__( 'WooCommerce Migration', 'aumnexcart' ) . '
';
echo '' . esc_html__( 'Copies every NexCart product into a new WooCommerce product. The original products are kept intact, so you can roll back by deactivating WooCommerce. Run this only when you are ready to enable online purchasing.', 'aumnexcart' ) . '
';
if ( ! self::is_woocommerce_active() ) {
echo '' . esc_html__( 'WooCommerce is not installed or activated on this site.', 'aumnexcart' ) . '
';
echo '' . esc_html__( 'Install and activate WooCommerce first, then return to this page.', 'aumnexcart' ) . '
';
return;
}
$count = self::count_products();
echo '' . sprintf(
/* translators: %d: number of products to migrate */
esc_html( _n( '%d product will be migrated.', '%d products will be migrated.', $count, 'aumnexcart' ) ),
esc_html( $count )
) . '
';
echo '' . esc_html__( 'Note:', 'aumnexcart' ) . ' ';
echo esc_html__( 'Price ranges only transfer the minimum value; text-mode prices are stored in a notes meta field. Remote media URLs are not auto-uploaded; their list is preserved as a note for manual handling. Contact actions are recorded as notes since WooCommerce does not use them.', 'aumnexcart' );
echo '
';
if ( $token ) {
?>