get_admin_css() );
wp_register_script( 'a1tools-import-export', false, array( 'jquery' ), A1TOOLS_VERSION, true );
wp_enqueue_script( 'a1tools-import-export' );
wp_localize_script( 'a1tools-import-export', 'a1toolsIE', array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'noncePages' => wp_create_nonce( 'a1tools_get_pages' ),
'nonceExport' => wp_create_nonce( 'a1tools_export' ),
'nonceImport' => wp_create_nonce( 'a1tools_import' ),
) );
wp_add_inline_script( 'a1tools-import-export', $this->get_admin_js() );
}
// =========================================================================
// AJAX: Get Pages by Author
// =========================================================================
/**
* AJAX handler — return pages filtered by author.
*/
public function ajax_get_pages_by_author() {
check_ajax_referer( 'a1tools_get_pages', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'a1-tools' ) ) );
}
$author_id = isset( $_POST['author_id'] ) ? absint( $_POST['author_id'] ) : 0;
$args = array(
'post_type' => 'page',
'post_status' => array( 'publish', 'draft', 'private' ),
'numberposts' => -1,
'orderby' => 'title',
'order' => 'ASC',
);
if ( $author_id > 0 ) {
$args['author'] = $author_id;
}
$posts = get_posts( $args );
$pages = array();
foreach ( $posts as $post ) {
$elementor_data = get_post_meta( $post->ID, '_elementor_data', true );
$pages[] = array(
'id' => $post->ID,
'title' => $post->post_title,
'status' => $post->post_status,
'date' => get_the_date( 'Y-m-d', $post ),
'has_elementor' => ! empty( $elementor_data ),
);
}
wp_send_json_success( array( 'pages' => $pages ) );
}
// =========================================================================
// AJAX: Export Pages
// =========================================================================
/**
* AJAX handler — export selected pages as JSON or WXR.
*/
public function ajax_export_pages() {
check_ajax_referer( 'a1tools_export', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'a1-tools' ) ) );
}
$page_ids = isset( $_POST['page_ids'] ) ? array_map( 'absint', (array) $_POST['page_ids'] ) : array();
$format = isset( $_POST['format'] ) ? sanitize_text_field( wp_unslash( $_POST['format'] ) ) : 'json';
if ( empty( $page_ids ) ) {
wp_send_json_error( array( 'message' => __( 'No pages selected.', 'a1-tools' ) ) );
}
if ( ! in_array( $format, array( 'json', 'wxr' ), true ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid format.', 'a1-tools' ) ) );
}
$site_name = sanitize_file_name( get_bloginfo( 'name' ) );
$date = gmdate( 'Y-m-d' );
if ( 'json' === $format ) {
$data = $this->export_json( $page_ids );
$filename = "a1tools-export-{$site_name}-{$date}.json";
$content = wp_json_encode( $data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE );
$mime = 'application/json';
} else {
$content = $this->export_wxr( $page_ids );
$filename = "a1tools-export-{$site_name}-{$date}.xml";
$mime = 'text/xml';
}
wp_send_json_success( array(
'filename' => $filename,
'content' => $content,
'mime' => $mime,
) );
}
// =========================================================================
// AJAX: Import Pages
// =========================================================================
/**
* AJAX handler — import pages from uploaded file.
*/
public function ajax_import_pages() {
check_ajax_referer( 'a1tools_import', 'nonce' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'a1-tools' ) ) );
}
if ( empty( $_FILES['import_file'] ) ) {
wp_send_json_error( array( 'message' => __( 'No file uploaded.', 'a1-tools' ) ) );
}
$file = $_FILES['import_file']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- file array handled by validate_upload()
$validation = $this->validate_upload( $file );
if ( is_wp_error( $validation ) ) {
wp_send_json_error( array( 'message' => $validation->get_error_message() ) );
}
$duplicate_handling = isset( $_POST['duplicate_handling'] )
? sanitize_text_field( wp_unslash( $_POST['duplicate_handling'] ) )
: 'skip';
if ( ! in_array( $duplicate_handling, array( 'skip', 'create_new', 'update_existing' ), true ) ) {
$duplicate_handling = 'skip';
}
$author_id = isset( $_POST['author_id'] ) ? absint( $_POST['author_id'] ) : 0;
$parent_page_id = isset( $_POST['parent_page_id'] ) ? absint( $_POST['parent_page_id'] ) : 0;
$ext = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) );
if ( 'json' === $ext ) {
$results = $this->import_json( $file['tmp_name'], $duplicate_handling, $author_id, $parent_page_id );
} elseif ( 'xml' === $ext ) {
$results = $this->import_wxr( $file['tmp_name'], $duplicate_handling, $author_id, $parent_page_id );
} else {
wp_send_json_error( array( 'message' => __( 'Unsupported file format. Use .json or .xml.', 'a1-tools' ) ) );
return;
}
if ( is_wp_error( $results ) ) {
wp_send_json_error( array( 'message' => $results->get_error_message() ) );
return;
}
wp_send_json_success( $results );
}
// =========================================================================
// Export Helpers
// =========================================================================
/**
* Build export data for a single page.
*
* @param int $post_id The page ID.
* @return array|null Page data array or null on failure.
*/
private function get_post_export_data( $post_id ) {
$post = get_post( $post_id );
if ( ! $post || 'page' !== $post->post_type ) {
return null;
}
$data = array(
'post_title' => $post->post_title,
'post_content' => $post->post_content,
'post_excerpt' => $post->post_excerpt,
'post_status' => $post->post_status,
'post_name' => $post->post_name,
'post_parent' => $post->post_parent,
'menu_order' => $post->menu_order,
'comment_status' => $post->comment_status,
'ping_status' => $post->ping_status,
'post_date' => $post->post_date,
'post_date_gmt' => $post->post_date_gmt,
'meta' => array(),
);
// Capture all post meta (skip internal keys).
$skip_keys = array( '_edit_lock', '_edit_last' );
$all_meta = get_post_meta( $post_id );
foreach ( $all_meta as $key => $values ) {
if ( in_array( $key, $skip_keys, true ) ) {
continue;
}
$data['meta'][ $key ] = maybe_unserialize( $values[0] );
}
return $data;
}
/**
* Generate JSON export data.
*
* @param array $post_ids Array of page IDs.
* @return array Export data structure.
*/
private function export_json( $post_ids ) {
$export = array(
'version' => A1TOOLS_VERSION,
'export_date' => gmdate( 'Y-m-d H:i:s' ),
'site_url' => site_url(),
'site_name' => get_bloginfo( 'name' ),
'format' => 'a1tools-json',
'pages' => array(),
);
foreach ( $post_ids as $post_id ) {
$page_data = $this->get_post_export_data( $post_id );
if ( $page_data ) {
$export['pages'][] = $page_data;
}
}
return $export;
}
/**
* Generate WXR (WordPress eXtended RSS) export string.
*
* @param array $post_ids Array of page IDs.
* @return string WXR XML string.
*/
private function export_wxr( $post_ids ) {
$site_url = site_url();
$site_name = get_bloginfo( 'name' );
$xml = '' . "\n";
$xml .= 'esc_xml( $site_name ) . '' . "\n";
$xml .= ' ' . esc_url( $site_url ) . '' . "\n";
$xml .= ' A1 Tools Page Export' . "\n";
$xml .= ' 1.2' . "\n";
$xml .= ' ' . esc_url( $site_url ) . '' . "\n";
$xml .= ' ' . esc_url( home_url() ) . '' . "\n";
$xml .= ' A1 Tools ' . $this->esc_xml( A1TOOLS_VERSION ) . '' . "\n";
foreach ( $post_ids as $post_id ) {
$page_data = $this->get_post_export_data( $post_id );
if ( ! $page_data ) {
continue;
}
$author_login = get_the_author_meta( 'login', get_post_field( 'post_author', $post_id ) );
$xml .= ' - ' . "\n";
$xml .= ' ' . $this->esc_xml( $page_data['post_title'] ) . '' . "\n";
$xml .= ' ' . "\n";
$xml .= ' ' . "\n";
$xml .= ' ' . "\n";
$xml .= ' ' . $this->esc_xml( $page_data['post_date'] ) . '' . "\n";
$xml .= ' ' . $this->esc_xml( $page_data['post_date_gmt'] ) . '' . "\n";
$xml .= ' ' . $this->esc_xml( $page_data['post_name'] ) . '' . "\n";
$xml .= ' ' . $this->esc_xml( $page_data['post_status'] ) . '' . "\n";
$xml .= ' ' . absint( $page_data['post_parent'] ) . '' . "\n";
$xml .= ' ' . intval( $page_data['menu_order'] ) . '' . "\n";
$xml .= ' page' . "\n";
$xml .= ' ' . $this->esc_xml( $page_data['comment_status'] ) . '' . "\n";
foreach ( $page_data['meta'] as $meta_key => $meta_value ) {
$serialized = maybe_serialize( $meta_value );
$xml .= ' ' . "\n";
$xml .= ' ' . "\n";
$xml .= ' ' . "\n";
$xml .= ' ' . "\n";
}
$xml .= '
' . "\n";
}
$xml .= '' . "\n";
$xml .= '' . "\n";
return $xml;
}
/**
* XML-escape a string. Uses esc_xml() if available (WP 5.5+), else esc_html().
*
* @param string $value The string to escape.
* @return string Escaped string.
*/
private function esc_xml( $value ) {
if ( function_exists( 'esc_xml' ) ) {
return esc_xml( $value );
}
return esc_html( $value );
}
// =========================================================================
// Import Helpers
// =========================================================================
/**
* Validate an uploaded file.
*
* @param array $file The $_FILES entry.
* @return true|WP_Error True on success, WP_Error on failure.
*/
private function validate_upload( $file ) {
if ( $file['error'] !== UPLOAD_ERR_OK ) {
return new WP_Error( 'upload_error', __( 'File upload failed.', 'a1-tools' ) );
}
$ext = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) );
if ( ! in_array( $ext, array( 'json', 'xml' ), true ) ) {
return new WP_Error( 'invalid_type', __( 'Only .json and .xml files are allowed.', 'a1-tools' ) );
}
if ( $file['size'] > 50 * 1024 * 1024 ) {
return new WP_Error( 'file_too_large', __( 'File exceeds maximum size of 50 MB.', 'a1-tools' ) );
}
if ( ! is_readable( $file['tmp_name'] ) ) {
return new WP_Error( 'not_readable', __( 'Uploaded file is not readable.', 'a1-tools' ) );
}
if ( 'json' === $ext ) {
$content = file_get_contents( $file['tmp_name'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$decoded = json_decode( $content, true );
if ( null === $decoded ) {
return new WP_Error( 'invalid_json', __( 'File contains invalid JSON.', 'a1-tools' ) );
}
if ( ! isset( $decoded['format'] ) || 'a1tools-json' !== $decoded['format'] ) {
return new WP_Error( 'invalid_format', __( 'This JSON file was not exported by A1 Tools.', 'a1-tools' ) );
}
}
if ( 'xml' === $ext ) {
$content = file_get_contents( $file['tmp_name'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
if ( strpos( $content, 'xmlns:wp=' ) === false ) {
return new WP_Error( 'invalid_wxr', __( 'This XML file does not appear to be a valid WordPress export.', 'a1-tools' ) );
}
}
return true;
}
/**
* Import pages from a JSON file.
*
* @param string $file_path Path to the JSON file.
* @param string $duplicate_handling How to handle duplicates: skip, create_new, update_existing.
* @param int $author_id Author ID to assign to imported pages (0 = keep default).
* @param int $parent_page_id Parent page ID to assign to all imported pages (0 = keep original).
* @return array|WP_Error Results array or error.
*/
private function import_json( $file_path, $duplicate_handling, $author_id = 0, $parent_page_id = 0 ) {
$content = file_get_contents( $file_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$data = json_decode( $content, true );
if ( ! isset( $data['pages'] ) || ! is_array( $data['pages'] ) ) {
return new WP_Error( 'invalid_data', __( 'No pages found in import file.', 'a1-tools' ) );
}
return $this->process_import( $data['pages'], $duplicate_handling, $author_id, $parent_page_id );
}
/**
* Import pages from a WXR (WordPress XML) file.
*
* @param string $file_path Path to the XML file.
* @param string $duplicate_handling How to handle duplicates.
* @param int $author_id Author ID to assign to imported pages (0 = keep default).
* @param int $parent_page_id Parent page ID to assign to all imported pages (0 = keep original).
* @return array|WP_Error Results array or error.
*/
private function import_wxr( $file_path, $duplicate_handling, $author_id = 0, $parent_page_id = 0 ) {
$xml_content = file_get_contents( $file_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
libxml_use_internal_errors( true );
$xml = simplexml_load_string( $xml_content );
if ( false === $xml ) {
return new WP_Error( 'xml_parse_error', __( 'Failed to parse XML file.', 'a1-tools' ) );
}
$namespaces = $xml->getNamespaces( true );
$wp_ns = isset( $namespaces['wp'] ) ? $namespaces['wp'] : 'http://wordpress.org/export/1.2/';
$content_ns = isset( $namespaces['content'] ) ? $namespaces['content'] : 'http://purl.org/rss/1.0/modules/content/';
$excerpt_ns = isset( $namespaces['excerpt'] ) ? $namespaces['excerpt'] : 'http://wordpress.org/export/1.2/excerpt/';
$pages = array();
foreach ( $xml->channel->item as $item ) {
$wp = $item->children( $wp_ns );
$content = $item->children( $content_ns );
$excerpt = $item->children( $excerpt_ns );
// Only import pages.
if ( isset( $wp->post_type ) && 'page' !== (string) $wp->post_type ) {
continue;
}
$page_data = array(
'post_title' => (string) $item->title,
'post_content' => (string) $content->encoded,
'post_excerpt' => isset( $excerpt->encoded ) ? (string) $excerpt->encoded : '',
'post_status' => (string) $wp->status,
'post_name' => (string) $wp->post_name,
'post_parent' => (int) $wp->post_parent,
'menu_order' => (int) $wp->menu_order,
'comment_status' => (string) $wp->comment_status,
'post_date' => (string) $wp->post_date,
'post_date_gmt' => (string) $wp->post_date_gmt,
'meta' => array(),
);
if ( isset( $wp->postmeta ) ) {
foreach ( $wp->postmeta as $meta ) {
$meta_key = (string) $meta->meta_key;
$meta_value = (string) $meta->meta_value;
$page_data['meta'][ $meta_key ] = maybe_unserialize( $meta_value );
}
}
$pages[] = $page_data;
}
if ( empty( $pages ) ) {
return new WP_Error( 'no_pages', __( 'No pages found in the import file.', 'a1-tools' ) );
}
return $this->process_import( $pages, $duplicate_handling, $author_id, $parent_page_id );
}
/**
* Process an array of page data for import.
*
* @param array $pages Array of page data arrays.
* @param string $duplicate_handling How to handle duplicates.
* @param int $author_id Author ID to assign to imported pages (0 = keep default).
* @param int $parent_page_id Parent page ID to assign to all imported pages (0 = keep original).
* @return array Results summary.
*/
private function process_import( $pages, $duplicate_handling, $author_id = 0, $parent_page_id = 0 ) {
$results = array(
'imported' => 0,
'updated' => 0,
'skipped' => 0,
'errors' => 0,
'details' => array(),
);
foreach ( $pages as $page_data ) {
$result = $this->import_single_page( $page_data, $duplicate_handling, $author_id, $parent_page_id );
$results['details'][] = $result;
switch ( $result['action'] ) {
case 'imported':
$results['imported']++;
break;
case 'updated':
$results['updated']++;
break;
case 'skipped':
$results['skipped']++;
break;
default:
$results['errors']++;
break;
}
}
return $results;
}
/**
* Find an existing page by slug or title.
*
* @param array $page_data The page data array.
* @return int Existing page ID or 0 if not found.
*/
private function find_existing_page( $page_data ) {
// Try by slug first.
if ( ! empty( $page_data['post_name'] ) ) {
$existing = get_page_by_path( $page_data['post_name'], OBJECT, 'page' );
if ( $existing ) {
return $existing->ID;
}
}
// Fallback: query by title.
if ( ! empty( $page_data['post_title'] ) ) {
$query = new WP_Query( array(
'post_type' => 'page',
'title' => $page_data['post_title'],
'posts_per_page' => 1,
'fields' => 'ids',
'post_status' => array( 'publish', 'draft', 'private', 'pending', 'trash' ),
) );
if ( $query->have_posts() ) {
return $query->posts[0];
}
}
return 0;
}
/**
* Import a single page.
*
* @param array $page_data The page data array.
* @param string $duplicate_handling How to handle duplicates.
* @param int $author_id Author ID to assign (0 = keep default).
* @param int $parent_page_id Parent page ID to assign (0 = keep original from export data).
* @return array Result with action, title, and message.
*/
private function import_single_page( $page_data, $duplicate_handling, $author_id = 0, $parent_page_id = 0 ) {
$title = isset( $page_data['post_title'] ) ? $page_data['post_title'] : '';
$existing_id = $this->find_existing_page( $page_data );
// Handle duplicates.
if ( $existing_id > 0 ) {
if ( 'skip' === $duplicate_handling ) {
return array(
'action' => 'skipped',
'title' => $title,
'message' => sprintf(
/* translators: %s: page title */
__( 'Page "%s" already exists. Skipped.', 'a1-tools' ),
$title
),
);
}
if ( 'update_existing' === $duplicate_handling ) {
$update_data = array(
'ID' => $existing_id,
'post_title' => $title,
'post_content' => isset( $page_data['post_content'] ) ? $page_data['post_content'] : '',
'post_excerpt' => isset( $page_data['post_excerpt'] ) ? $page_data['post_excerpt'] : '',
'post_status' => isset( $page_data['post_status'] ) ? $page_data['post_status'] : 'draft',
'post_name' => isset( $page_data['post_name'] ) ? $page_data['post_name'] : '',
'menu_order' => isset( $page_data['menu_order'] ) ? intval( $page_data['menu_order'] ) : 0,
'comment_status' => isset( $page_data['comment_status'] ) ? $page_data['comment_status'] : 'closed',
);
if ( $author_id > 0 ) {
$update_data['post_author'] = $author_id;
}
// Apply parent page override if specified, otherwise keep original from export.
if ( $parent_page_id > 0 ) {
$update_data['post_parent'] = $parent_page_id;
} elseif ( isset( $page_data['post_parent'] ) ) {
$update_data['post_parent'] = intval( $page_data['post_parent'] );
}
$result = wp_update_post( $update_data, true );
if ( is_wp_error( $result ) ) {
return array(
'action' => 'error',
'title' => $title,
'message' => $result->get_error_message(),
);
}
$this->import_page_meta( $existing_id, $page_data );
return array(
'action' => 'updated',
'title' => $title,
'post_id' => $existing_id,
'message' => sprintf(
/* translators: %s: page title */
__( 'Page "%s" updated successfully.', 'a1-tools' ),
$title
),
);
}
// 'create_new' — fall through to insert below.
}
// Insert new page.
$post_data = array(
'post_title' => $title,
'post_content' => isset( $page_data['post_content'] ) ? $page_data['post_content'] : '',
'post_excerpt' => isset( $page_data['post_excerpt'] ) ? $page_data['post_excerpt'] : '',
'post_status' => isset( $page_data['post_status'] ) ? $page_data['post_status'] : 'draft',
'post_type' => 'page',
'post_name' => isset( $page_data['post_name'] ) ? $page_data['post_name'] : '',
'menu_order' => isset( $page_data['menu_order'] ) ? intval( $page_data['menu_order'] ) : 0,
'comment_status' => isset( $page_data['comment_status'] ) ? $page_data['comment_status'] : 'closed',
);
if ( $author_id > 0 ) {
$post_data['post_author'] = $author_id;
}
// Apply parent page override if specified, otherwise keep original from export.
if ( $parent_page_id > 0 ) {
$post_data['post_parent'] = $parent_page_id;
} elseif ( isset( $page_data['post_parent'] ) ) {
$post_data['post_parent'] = intval( $page_data['post_parent'] );
}
// Let WordPress auto-generate slug if creating duplicate.
if ( $existing_id > 0 && 'create_new' === $duplicate_handling ) {
$post_data['post_name'] = '';
}
$new_id = wp_insert_post( $post_data, true );
if ( is_wp_error( $new_id ) ) {
return array(
'action' => 'error',
'title' => $title,
'message' => $new_id->get_error_message(),
);
}
$this->import_page_meta( $new_id, $page_data );
return array(
'action' => 'imported',
'title' => $title,
'post_id' => $new_id,
'message' => sprintf(
/* translators: %s: page title */
__( 'Page "%s" imported successfully.', 'a1-tools' ),
$title
),
);
}
/**
* Import meta data for a page with Elementor double-serialization fix.
*
* @param int $post_id The page ID.
* @param array $page_data The page data array containing a 'meta' key.
*/
private function import_page_meta( $post_id, $page_data ) {
if ( empty( $page_data['meta'] ) || ! is_array( $page_data['meta'] ) ) {
return;
}
$skip_keys = array( '_edit_lock', '_edit_last' );
foreach ( $page_data['meta'] as $meta_key => $meta_value ) {
if ( in_array( $meta_key, $skip_keys, true ) ) {
continue;
}
// CRITICAL: Elementor _elementor_data is stored as a JSON string.
// WordPress's update_post_meta() internally calls wp_unslash() which
// strips backslashes from the JSON. We must use wp_slash() to counteract this.
if ( '_elementor_data' === $meta_key ) {
if ( is_array( $meta_value ) ) {
$meta_value = wp_json_encode( $meta_value );
}
update_post_meta( $post_id, $meta_key, wp_slash( $meta_value ) );
} else {
update_post_meta( $post_id, $meta_key, $meta_value );
}
}
}
// =========================================================================
// Admin Page Render
// =========================================================================
/**
* Build the HTML for the page checkbox list.
*
* @param array $posts Array of WP_Post objects.
* @return string HTML string.
*/
private function build_page_list_html( $posts ) {
if ( empty( $posts ) ) {
return '
' . esc_html__( 'No pages found.', 'a1-tools' ) . '
';
}
$html = '';
foreach ( $posts as $post ) {
$elementor_data = get_post_meta( $post->ID, '_elementor_data', true );
$has_elementor = ! empty( $elementor_data );
$badge = $has_elementor ? ' Elementor' : '';
$status = '';
if ( 'publish' !== $post->post_status ) {
$status = ' ' . esc_html( $post->post_status ) . '';
}
$title = ! empty( $post->post_title ) ? esc_html( $post->post_title ) : esc_html__( '(no title)', 'a1-tools' );
$html .= '';
}
$html .= '
';
return $html;
}
/**
* Render the Import/Export admin page.
*/
public function render_page() {
// Pre-load all pages server-side for instant display.
$all_pages = get_posts( array(
'post_type' => 'page',
'post_status' => array( 'publish', 'draft', 'private' ),
'numberposts' => -1,
'orderby' => 'title',
'order' => 'ASC',
) );
$page_count = count( $all_pages );
?>
No pages found for this author.';
}
var html = '';
$.each(pages, function(i, p) {
var badge = p.has_elementor
? ' Elementor'
: '';
var sBadge = p.status !== 'publish'
? ' ' + p.status + ''
: '';
html += '';
});
html += '
';
return html;
}
/* ── Author filter change → AJAX reload ──────────────────── */
$('#a1tools-export-author').on('change', function() {
var authorId = $(this).val() || 0;
// Show spinner, hide current list.
$('#a1tools-page-list').hide();
$('#a1tools-page-list-spinner').show();
$('#a1tools-page-list-controls').hide();
$.post(s.ajaxUrl, {
action: 'a1tools_get_pages_by_author',
nonce: s.noncePages,
author_id: authorId
}, function(res) {
$('#a1tools-page-list-spinner').hide();
if (res.success && res.data.pages.length > 0) {
$('#a1tools-page-list').html(buildPageListHtml(res.data.pages)).show();
$('#a1tools-page-list-controls').show();
$('#a1tools-page-count').text(res.data.pages.length + (res.data.pages.length === 1 ? ' page' : ' pages'));
} else {
$('#a1tools-page-list').html('No pages found for this author.
').show();
$('#a1tools-page-list-controls').hide();
}
}).fail(function() {
$('#a1tools-page-list-spinner').hide();
$('#a1tools-page-list').html('Failed to load pages. Please refresh and try again.
').show();
});
});
/* ── Select / Deselect All ───────────────────────────────── */
$('#a1tools-select-all').on('click', function() {
$('#a1tools-page-list input[type="checkbox"]').prop('checked', true);
});
$('#a1tools-deselect-all').on('click', function() {
$('#a1tools-page-list input[type="checkbox"]').prop('checked', false);
});
/* ── Export ───────────────────────────────────────────────── */
$('#a1tools-export-btn').on('click', function() {
var ids = [];
$('#a1tools-page-list input[type="checkbox"]:checked').each(function() {
ids.push($(this).val());
});
if (ids.length === 0) {
alert('Please select at least one page to export.');
return;
}
var $btn = $(this),
$st = $('#a1tools-export-status'),
$prog = $('#a1tools-export-progress'),
$fill = $('#a1tools-export-progress-fill'),
$text = $('#a1tools-export-progress-text');
$btn.prop('disabled', true);
$st.text('').removeClass('a1tools-success a1tools-error');
$fill.css('width', '0%').addClass('a1tools-indeterminate');
$text.text('Preparing export for ' + ids.length + (ids.length === 1 ? ' page' : ' pages') + '...');
$prog.show();
$.post(s.ajaxUrl, {
action: 'a1tools_export_pages',
nonce: s.nonceExport,
page_ids: ids,
format: $('#a1tools-export-format').val()
}, function(res) {
$btn.prop('disabled', false);
$fill.removeClass('a1tools-indeterminate');
if (res.success) {
$fill.css('width', '100%');
$text.text('Download starting...');
var blob = new Blob([res.data.content], { type: res.data.mime });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = res.data.filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
$text.text('Exported ' + ids.length + (ids.length === 1 ? ' page' : ' pages') + ' successfully!');
$st.text('').addClass('a1tools-success');
setTimeout(function() { $prog.fadeOut(); }, 4000);
} else {
$fill.css('width', '100%').css('background', '#d63638');
$text.text(res.data.message || 'Export failed.');
$st.text('').addClass('a1tools-error');
setTimeout(function() {
$prog.fadeOut(function() { $fill.css('background', ''); });
}, 5000);
}
}).fail(function() {
$btn.prop('disabled', false);
$fill.removeClass('a1tools-indeterminate').css('width', '100%').css('background', '#d63638');
$text.text('Export failed. Please try again.');
setTimeout(function() {
$prog.fadeOut(function() { $fill.css('background', ''); });
}, 5000);
});
});
/* ── Import ───────────────────────────────────────────────── */
$('#a1tools-import-btn').on('click', function() {
var fileInput = $('#a1tools-import-file')[0];
if (!fileInput.files || !fileInput.files[0]) {
alert('Please select a file to import.');
return;
}
var $btn = $(this),
$st = $('#a1tools-import-status'),
$prog = $('#a1tools-import-progress'),
$fill = $('#a1tools-import-progress-fill'),
$text = $('#a1tools-import-progress-text');
$btn.prop('disabled', true);
$st.text('').removeClass('a1tools-success a1tools-error');
$('#a1tools-import-results').hide();
$fill.css('width', '0%').css('background', '').addClass('a1tools-indeterminate');
$text.text('Uploading and importing pages...');
$prog.show();
var fd = new FormData();
fd.append('action', 'a1tools_import_pages');
fd.append('nonce', s.nonceImport);
fd.append('import_file', fileInput.files[0]);
fd.append('duplicate_handling', $('#a1tools-duplicate-handling').val());
fd.append('author_id', $('#a1tools-import-author').val());
fd.append('parent_page_id', $('#a1tools-import-parent').val() || '0');
$.ajax({
url: s.ajaxUrl,
type: 'POST',
data: fd,
processData: false,
contentType: false,
success: function(res) {
$btn.prop('disabled', false);
$fill.removeClass('a1tools-indeterminate');
if (res.success) {
var d = res.data;
var total = d.imported + d.updated + d.skipped + d.errors;
$fill.css('width', '100%');
$text.text('Processed ' + total + (total === 1 ? ' page' : ' pages') + ' successfully!');
var summary = '';
summary += 'Imported: ' + d.imported + ' | ';
summary += 'Updated: ' + d.updated + ' | ';
summary += 'Skipped: ' + d.skipped + ' | ';
summary += 'Errors: ' + d.errors;
summary += '
';
$('#a1tools-import-summary').html(summary);
var tbl = '| Page | Action | Details |
';
$.each(d.details, function(i, r) {
var cls = r.action === 'error' ? ' style="color:#d63638;"' : '';
tbl += '';
tbl += '| ' + $('').text(r.title).html() + ' | ';
tbl += '' + r.action + ' | ';
tbl += '' + $('').text(r.message).html() + ' | ';
tbl += '
';
});
tbl += '
';
$('#a1tools-import-details').html(tbl);
$('#a1tools-import-results').show();
setTimeout(function() { $prog.fadeOut(); }, 3000);
} else {
$fill.css('width', '100%').css('background', '#d63638');
$text.text(res.data.message || 'Import failed.');
setTimeout(function() {
$prog.fadeOut(function() { $fill.css('background', ''); });
}, 5000);
}
},
error: function() {
$btn.prop('disabled', false);
$fill.removeClass('a1tools-indeterminate').css('width', '100%').css('background', '#d63638');
$text.text('Import failed. Please try again.');
setTimeout(function() {
$prog.fadeOut(function() { $fill.css('background', ''); });
}, 5000);
}
});
});
})(jQuery);
JS;
}
}