Importar archivo XML de WordPress desde functions.php automáticamente
Estoy desarrollando un tema que tiene un método diferente de agregar contenido y, por lo tanto, la instalación predeterminada de WordPress no mostrará ningún contenido debido a esto. Me preguntaba si es posible importar automáticamente un archivo XML mediante una función interna o hooks después de que el tema se haya activado.
Usuario instala el tema > Usuario activa el tema > El código detrás de escena carga un archivo XML y realiza una importación silenciosa de su contenido
Actualmente, para importar un archivo XML hay que instalar el plugin WordPress Importer y luego importar manualmente el archivo, seleccionar un usuario para asociar el contenido importado y decidir si se desea importar archivos adjuntos. Encuentro que este paso es demasiado confuso para el tipo de clientes a los que me dirijo y me gustaría eliminar efectivamente la necesidad de este paso.
Investigué un poco el script del importador de WordPress y hay muchas llamadas a funciones. ¿Qué tendría que hacer para eliminar las partes donde se requiere entrada del usuario e importar un archivo usando la clase y sus métodos directamente? Realmente no estoy seguro por dónde empezar.
Mis clientes son trabajadores manuales, por lo que incluso algo tan simple como importar un archivo XML los confunde y no tienen tiempo para hacerlo, por lo que hay margen para errores, especialmente si intentan importar más de una vez causando páginas duplicadas.
Gracias de antemano.
Editar/Aclaración
Parece haber mucha confusión aquí. No estoy preguntando cómo verificar si un tema ha sido activado, ya tengo esa parte resuelta. Estoy preguntando cómo podría analizar un archivo de importación XML e importarlo automáticamente sin esfuerzo del usuario. Básicamente quiero automatizar el plugin de importación de WordPress que ya se puede usar para importar manualmente el archivo XML, elegir autor, decidir si descargar e importar archivos adjuntos, pero todo desde mi functions.php.
En lugar de necesitar un plugin o requerir que mis clientes, que carecen de conocimientos informáticos y no tienen interés en aprender, lo hagan usando el plugin.

Tu pregunta es un poco específica si "solo" quieres importar automáticamente algunas entradas/páginas. Hay otras formas de hacer esto además de usar un archivo de exportación XML.
Si tienes publicaciones de solo texto, entonces deberías usar LOAD DATA INFILE. Primero tienes que exportar tus publicaciones.
global $wpdb, $wp_filesystem;
$tables = array(
'posts' => array( 'posts', 'postmeta' ),
'comments' => array( 'comments', 'commentmeta' ),
'terms' => array( 'terms', 'term_taxonomy', 'term_relationships' ),
'users' => array( 'user', 'usermeta' ),
'links' => array( 'links' ),
'options' => array( 'options' ),
'other' => array(),
// para multisitio
'multiside' => array( 'blogs', 'signups', 'site', 'sitemeta', 'sitecategories', 'registration_log', 'blog_versions' )
);
$exports = array( 'posts', 'comments', 'users' );
$exportdir = TEMPLATEPATH . '/export';
if ( ! is_dir( $exportdir ) ) {
$mkdir = wp_mkdir_p( $exportdir );
if ( false == $mkdir || ! is_dir( $exportdir ) )
throw new Exception( 'No se puede crear el directorio de exportación. Abortando.' );
}
// vaciar el directorio de exportación o MySQL arrojará errores
$files = glob( $exportdir . '/*' );
if ( ! empty( $files ) ) {
foreach( $files as $file )
unlink( $file );
}
foreach ( $exports as $export ) {
if ( ! isset( $tables[$export] ) )
continue;
if ( ! empty( $tables[$export] ) ) {
foreach ( $tables[$export] as $table ) {
$outfile = sprintf( '%s/%s_dump.sql', $exportdir, $table );
$sql = "SELECT * FROM {$wpdb->$table} INTO OUTFILE '%s'";
$res = $wpdb->query( $wpdb->prepare( $sql, $outfile ) );
if ( is_wp_error( $res ) )
echo "<p>No se puede exportar {$table} en {$outfile}</p>";
}
}
}
Esto creará un directorio en la carpeta de tu tema (¡asegúrate de que tenga permisos de escritura!) y exportará las publicaciones y comentarios (con sus metadatos) a archivos de volcado. Usa el array export
para definir lo que quieres exportar. Agrupé la mayoría de las cosas de manera más o menos lógica (si quieres exportar las publicaciones, también deberías exportar postmeta y así sucesivamente).
El beneficio de esta solución es que con la sentencia SELECT
puedes definir cosas particulares (por ejemplo, solo publicaciones de una categoría especial o solo páginas o solo publicaciones en la papelera).
Ahora quieres importar esto en un nuevo blog
global $wpdb;
$exportdir = TEMPLATEPATH . '/export';
$files = glob( $exportdir . '/*_dump.sql' );
foreach ( $files as $file ) {
preg_match( '#/([^/]+)_dump.sql$#is', $file, $match );
if ( ! isset( $match[1] ) )
continue;
$sql = "LOAD DATA LOCAL INFILE '%s' INTO TABLE {$wpdb->$match[1]};";
$res = $wpdb->query( $wpdb->prepare( $sql, $file ) );
if ( is_wp_error( $res ) )
echo "<p>No se pueden importar datos del archivo {$file} en la tabla {$wpdb->$match[1]}</p>";
}
Esta solución es buena si las publicaciones no contienen ningún adjunto como imágenes. Otro problema es que no se importarán usuarios ni categorías. Asegúrate de que ambos estén creados antes de que comience la importación (o incluye usuarios y categorías en tu exportación). Es un método muy rudimentario para importar cosas, ¡sobrescribirá lo existente!
Si quieres exportar también los adjuntos, tienes que hacer un poco más de trabajo.
(Nota al margen: ¡Por favor lee la respuesta completa y las Últimas Palabras al final! Este tema no es para principiantes y no escribiré una advertencia en cada línea de código riesgosa)
El plugin WordPress Importer parece ser una buena forma de importar todo y descargar automáticamente los adjuntos. Así que echemos un vistazo a lo que hace este plugin.
Primero, el plugin solicita un archivo XML para cargar. Luego analiza el archivo XML y pregunta por un mapeo de autores y si los adjuntos deben descargarse o no.
Para una ejecución automática del plugin necesitamos cambiar algunas cosas. Primero tenemos que omitir el proceso de carga. Eso es bastante fácil porque puedes agrupar el archivo XML con el tema y sabes dónde está el archivo XML. Luego tenemos que omitir las preguntas que aparecen después de cargar el archivo XML. Podemos predefinir nuestros propios valores y pasarlos al proceso de importación.
Comienza con una copia del plugin. Crea un directorio en tu tema como autoimport
y copia los archivos wordpress-importer.php
y parsers.php
en él. Es una buena idea renombrar el archivo wordpress-importer.php
a algo como autoimporter.php
. En las funciones de tu tema agrega una llamada a función para activar la importación automática
/**
* Importación automática de un archivo XML
*/
add_action( 'after_setup_theme', 'autoimport' );
function autoimport() {
// obtener el archivo
require_once TEMPLATEPATH . '/autoimport/autoimporter.php';
if ( ! class_exists( 'Auto_Importer' ) )
die( 'Auto_Importer no encontrado' );
// llamar a la función
$args = array(
'file' => TEMPLATEPATH . '/autoimport/import.xml',
'map_user_id' => 1
);
auto_import( $args );
}
Primero configuramos algunos argumentos. Lo primero es la ruta completa al archivo XML. El segundo es el ID de un usuario existente. Necesitamos este usuario para el mapeo de autores, este es el usuario al que se mapearán todas las publicaciones cuando no se deban crear nuevos autores.
Ahora tenemos que entender cómo funciona el plugin. Abre tu archivo de plugin renombrado y desplázate hasta el final. Hay una función wordpress_importer_init()
y una llamada a acción. Elimina ambas, ya no son necesarias. Ahora ve a la parte superior del archivo y elimina el encabezado del plugin (el comentario al principio del archivo). Después de eso, renombra la clase WP_Importer
a algo como Auto_Importer
, no olvides ajustar la declaración function_exists
y el primer método WP_Importer
(este es el constructor en estilo PHP4).
Más adelante pasaremos el archivo XML directamente al constructor de la clase, modifica el primer método a esto
var $xmlfile = '';
var $map_user_id = 0;
function Auto_Importer( $args ) {
if ( file_exists( $args['file'] ) ) {
// para sistemas windows
$file = str_replace( '\\', '/', $args['file'] );
$this->xmlfile = $file;
}
if ( isset( $args['map_user_id'] ) )
$this->map_user_id = $args['map_user_id'];
}
Ahora tenemos que eliminar y modificar algunos métodos dentro de la clase. El primer método es el método dispatch()
. Este método te dice cómo funciona la clase. Hace tres pasos. Primero carga el archivo XML, luego lo procesa y finalmente importa los datos.
El caso cero es el primer paso, es el saludo. Esta es la parte que ves si llamas a la importación por primera vez. Preguntará por un archivo para cargar. El caso dos maneja la carga y muestra un formulario para las opciones de importación. El caso tres finalmente hace la importación. En otras palabras: los dos primeros pasos solo piden datos que podemos proporcionar nosotros mismos. Solo necesitamos el paso 3 (caso 2) y tenemos que proporcionar los datos solicitados en los pasos uno y dos.
En el paso dos ves una llamada a función a wp_import_handle_upload()
. Esta función configura algunas informaciones sobre el archivo xml. Ya no podemos usar esta función porque no hemos cargado un archivo. Así que tenemos que copiar y modificar la función. Crea un nuevo método dentro de la clase
function import_handle_upload() {
$url = get_template_directory_uri() . str_replace( TEMPLATEPATH, '', $this->xmlfile );
$type = 'application/xml'; // conocemos el tipo mime de nuestro archivo
$file = $this->xmlfile;
$filename = basename( $this->xmlfile );
// Construir el array de objeto
$object = array( 'post_title' => $filename,
'post_content' => $url,
'post_mime_type' => $type,
'guid' => $url,
'context' => 'import',
'post_status' => 'private'
);
// Guardar los datos
$id = wp_insert_attachment( $object, $file );
// programar una limpieza para un día después en caso de importación fallida o falta de llamada wp_import_cleanup()
wp_schedule_single_event( time() + DAY_IN_SECONDS, 'importer_scheduled_cleanup', array( $id ) );
return array( 'file' => $file, 'id' => $id );
}
Y reemplaza la llamada a función $file = wp_import_handle_upload();
en el método handle_upload()
con nuestro nuevo método $file = $this->import_handle_upload();
Hemos reemplazado el proceso de carga con nuestro propio archivo (que ya debería existir). Continúa y elimina más métodos innecesarios. Los métodos gereet()
, header()
y footer()
ya no son necesarios (header y footer solo imprimen texto) y pueden eliminarse de la clase. En el método dispatch()
elimina las llamadas a estos métodos ($this->header()
y $this->footer()
).
El primer paso está hecho, ahora tenemos que ocuparnos del segundo paso, las opciones de importación. Las opciones de importación preguntan si se debe permitir descargar los adjuntos y mapear los autores.
La primera parte es fácil. Establece en verdadero si los adjuntos deben descargarse o falso si no. El mapeo de autores es un poco más complicado. Si se permite crear nuevos usuarios (los autores del archivo de importación), créalos. Si no, asigna las publicaciones a un usuario existente. Esto se hace en el método get_author_mapping()
. Tenemos que reemplazar los datos $_POST
con datos existentes. Aquí necesitamos una solución simple, así que simplemente mapeamos todos los nuevos autores a uno existente si no se permite crear nuevos usuarios. O simplemente creamos todos los nuevos usuarios. En el segundo caso, ¡asegúrate de que todos los nuevos usuarios sean usuarios ficticios. Si no, ¡cada vez que los importes recibirán un correo con usuario y contraseña del nuevo blog!! No explicaré cada línea de código, aquí está el método completamente reescrito
function get_author_mapping( $map_users_id ) {
if ( empty( $this->authors ) )
return;
$create_users = $this->allow_create_users();
foreach ( (array) $this->authors as $i => $data ) {
$old_login = $data['author_login'];
// Multisitio agrega strtolower a sanitize_user. Necesitamos sanitizar aquí para evitar problemas en process_posts.
$santized_old_login = sanitize_user( $old_login, true );
$old_id = isset( $this->authors[$old_login]['author_id'] ) ? intval($this->authors[$old_login]['author_id']) : false;
if ( ! $create_users ) {
$user = get_userdata( intval($map_users_id) );
if ( isset( $user->ID ) ) {
if ( $old_id )
$this->processed_authors[$old_id] = $user->ID;
$this->author_mapping[$santized_old_login] = $user->ID;
}
} else if ( $create_users ) {
if ( ! empty($this->authors[$i]) ) {
$user_id = wp_create_user( $this->authors[$i]['author_login'], wp_generate_password() );
} else if ( $this->version != '1.0' ) {
$user_data = array(
'user_login' => $old_login,
'user_pass' => wp_generate_password(),
'user_email' => isset( $this->authors[$old_login]['author_email'] ) ? $this->authors[$old_login]['author_email'] : '',
'display_name' => $this->authors[$old_login]['author_display_name'],
'first_name' => isset( $this->authors[$old_login]['author_first_name'] ) ? $this->authors[$old_login]['author_first_name'] : '',
'last_name' => isset( $this->authors[$old_login]['author_last_name'] ) ? $this->authors[$old_login]['author_last_name'] : '',
);
$user_id = wp_insert_user( $user_data );
}
if ( ! is_wp_error( $user_id ) ) {
if ( $old_id )
$this->processed_authors[$old_id] = $user_id;
$this->author_mapping[$santized_old_login] = $user_id;
} else {
printf( __( 'Error al crear nuevo usuario para %s. Sus publicaciones se atribuirán al usuario actual.', 'wordpress-importer' ), esc_html($this->authors[$old_login]['author_display_name']) );
if ( defined('IMPORT_DEBUG') && IMPORT_DEBUG )
echo ' ' . $user_id->get_error_message();
echo '<br />';
}
}
// seguro: si el user_id era inválido, usar el usuario actual
if ( ! isset( $this->author_mapping[$santized_old_login] ) ) {
if ( $old_id )
$this->processed_authors[$old_id] = (int) get_current_user_id();
$this->author_mapping[$santized_old_login] = (int) get_current_user_id();
}
}
}
Queda algo de trabajo por hacer. Primero añadir una función auto_import()
function auto_import( $args ) {
$defaults = array( 'file' => '', 'map_user_id' => 0);
$args = wp_parse_args( $args, $defaults );
$autoimport = new Auto_Importer( $args );
$autoimport->do_import();
}
Coloca esta función después de la clase. Esta función carece de manejo de errores y comprobaciones (por ejemplo, para un argumento de archivo vacío).
Si ahora ejecutas la clase, obtendrás muchos mensajes de error. El primero es que falta la clase. Esto se debe a que hay una declaración if
al principio.
if ( ! defined( 'WP_LOAD_IMPORTERS' ) )
return;
Tenemos que eliminarlo, de lo contrario el archivo no se analizará completamente. Luego hay algunas funciones que no se cargan en este punto. Tenemos que incluir algunos archivos.
$required = array(
'post_exists' => ABSPATH . 'wp-admin/includes/post.php',
'wp_generate_attachment_metadata' => ABSPATH . 'wp-admin/includes/image.php',
'comment_exists' => ABSPATH . 'wp-admin/includes/comment.php'
);
foreach ( $required as $func => $req_file ) {
if ( ! function_exists( $func ) )
require_once $req_file;
}
Básicamente eso es todo. Probé esto en una instalación local con los datos de prueba XML de WordPress. Funciona para mí, pero ¡no es una solución perfecta para producción!
Y algunas últimas palabras sobre la configuración de algunas opciones. Hay dos opciones que se pueden modificar mediante un filtro:
add_filter( 'import_allow_create_users', function() { return false; } );
add_filter( 'import_allow_fetch_attachments', '__return_false' );
Creo que no necesito explicarlo. Pon estos filtros en tu functions.php y configura verdadero o falso (el primero es estilo PHP5.3, el segundo es estilo WP).
Últimas Palabras
He reunido todo en este gist. ¡Úsalo bajo tu propio riesgo! ¡No soy responsable de nada!. Por favor mira los archivos en el gist, no he explicado cada pequeño paso aquí.
Cosas que no he hecho: Establecer un valor, por ejemplo, en las opciones (del tema) después de importar. De lo contrario, la importación comenzará cada vez que se active el tema.
Quizás trabaje en ello en el futuro, limpie algunas cosas y haga más pruebas.

Guau. Simplemente guau. Esta es una de las respuestas más detalladas e impresionantes que he visto en este sitio, felicitaciones. Después de pasar medio día leyendo esto, he llegado a la conclusión de que este es el camino correcto para abordar las cosas. Me has dado mucho en qué pensar, pero definitivamente esta es la dirección correcta a seguir. Gracias por tomarte el tiempo de dejar esta respuesta.

Permíteme volver a presentar 2 cosas aquí:
(a) "No estoy preguntando cómo... ya tengo esa parte resuelta..."
»» Con el tiempo he aprendido a aceptar que el enfoque para solucionar problemas no necesariamente requiere una 'asociación visible' con el problema en cuestión.
(b) "...tendría que hacer para eliminar las partes..." "...los clientes son profesionales, así que incluso algo tan simple como..."
»» ¿Por qué hacerlo más fácil para el cliente a costa de hacerlo difícil para ti mismo? Ciertamente podría ofrecer 'servicios' después de la entrega y establecer una conexión remota para hacerlo por ellos [cobrable], en lugar de "...hackear el plugin de importación...". Es decir, pregúntate si realmente vale la pena en tu situación actual. Sin embargo, SI estás dispuesto a poner el esfuerzo, entonces prueba con el código a continuación. Si puedes, entonces:
- domina primero los fundamentos y comprende mejor la base de datos
- ten a mano una referencia para todas las funciones - lo viejo es oro
- ten a mano una referencia para todos los hooks - simplifica simplifica
Estoy de acuerdo con chrisguitarguy y amolv arriba.
Como señaló chris, hay muchas formas de lograr un resultado. Esta es solo una. Aunque tiene el potencial de volverse laboriosamente extensa, consulta las últimas líneas antes que nada.
<?php
/* Normalmente coloco UNA línea en functions.php */
require_once (TEMPLATEPATH . '/includes/whatever.php');
/* y luego en esa ubicación VERIFICA PRIMERO*/
if ((is_admin() && isset($_GET['activated']) && $pagenow == 'themes.php')||(is_admin() && isset($_GET['upgrade']) && $pagenow == 'admin.php' && $_GET['page'] == 'admin-options.php'))
{
global $wpdb, $wp_rewrite, $hey;
// crear tablas
your_tables();
// insertar valores por defecto
your_values();
// insertar enlaces por defecto
your_links();
// páginas y plantillas
your_pages();
// crear categoría o categorías
// wp_create_categories $categories, $post_id = ''
// wp_create_category $cat_name, $parent
//limpiar reglas de reescritura
$wp_rewrite->flush_rules();
}
// crear las tablas de la base de datos
function your_tables() {
global $wpdb, $hey;
$collate = '';
if($wpdb->supports_collation()) {
if(!empty($wpdb->charset)) $collate = "DEFAULT CHARACTER SET $wpdb->charset";
if(!empty($wpdb->collate)) $collate .= " COLLATE $wpdb->collate";
}
$sql = "CREATE TABLE IF NOT EXISTS ". $wpdb->prefix . "table1_name" ." (
`id` INT(10) NOT NULL auto_increment,
`some_name1` VARCHAR(255) NOT NULL,
`some_name2` VARCHAR(255) NOT NULL,
`some_name3` LONGTEXT,
`some_name4` LONGTEXT NOT NULL,
`some_name5` VARCHAR(255) DEFAULT NULL,
`some_name6` VARCHAR(255) DEFAULT NULL,
`some_name7` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
`some_name8` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY id (`id`)) $collate;";
$wpdb->query($sql);
$sql = "CREATE TABLE IF NOT EXISTS ". $wpdb->prefix . "table2_name" ." (
`meta_id` INT(10) NOT NULL AUTO_INCREMENT,
`some_name1` INT(10) NOT NULL,
`some_name2` INT(10) NOT NULL,
`some_name3` VARCHAR(255) NOT NULL,
`some_name4` INT(10) NOT NULL,
PRIMARY KEY id (`meta_id`)) $collate;";
$wpdb->query($sql);
// y así sucesivamente
/* Insertar datos por defecto/TODOS los datos en las tablas */
// PERO VERIFICA PRIMERO SI LOS DATOS EXISTEN. SI = SÍ NO INSERTES NADA
$sql = "SELECT field_id " . "FROM " . $wpdb->prefix . "table1_name LIMIT 1";
$wpdb->get_results($sql);
if($wpdb->num_rows == 0) {
// más código seguirá
// tengo que irme ahora
}
?>
NOTA
Si has estado con WP por un tiempo, es innecesario mencionar HAZ UNA COPIA DE SEGURIDAD DE TU BASE DE DATOS PRIMERO.
phpMyAdmin tiene un poder bruto y facilita mucho el arruinar las cosas con cuidado.
Aunque el esfuerzo requerido puede parecer abrumador inicialmente, si se hace correctamente, podrías hacer que funcione como un reloj ф ...
Finalmente
¿Cómo insertar 2000 líneas de datos en 20 segundos en esas últimas 2 líneas dentro de esas 2 llaves?
phpMyAdmin » Selecciona la base de datos a la izquierda »» Selecciona TODAS las TABLAS a la derecha »» Exportar ▼
➝ Personalizado: mostrar todas las opciones
➝ Ver salida como texto = ACTIVADO
➝ Guardar salida en un archivo = DESACTIVADO
➝ Compresión = NINGUNA
➝ Formato = SQL
➝ Volcado de tabla = ESTRUCTURA & DATOS
➝ Añadir DROP TABLE... = DESACTIVADO (¡Importante!)
➝ Sintaxis a usar = "ambas de las anteriores"
»» ¡VAMOS!
Desde la siguiente pantalla podría copiar la parte de 'ESTRUCTURA' en el $sql = "...." para
your_tables()
y la porción de 'DATOS' en$sql
parayour_data()
Para el resto de los valores por defecto de WP uso
update_option(...)
&update_post_meta(...)

No existe un equivalente en temas para register_activation_hook
de los plugins — hay algunos trucos. ¿Por qué? Porque un tema es una piel. Solo la funcionalidad específicamente relacionada con la visualización del contenido debe ir en un tema, no el contenido en sí.
En cuanto al cómo: usa el ejemplo anterior para ejecutar una función de callback una sola vez. El importador de WordPress funciona con archivos XML; hay muchas formas diferentes de analizar XML en PHP. Elige la que prefieras, analiza el archivo y haz lo que necesites con él.

Gracias Chris. El problema no es encontrar un hook adecuado para detectar cuando se activa un tema (ya tengo esa parte resuelta), sino encontrar una forma de importar automáticamente un archivo XML a WordPress. Tengo el archivo XML en una carpeta dentro de mi directorio de tema llamada "importxml". Preferiría usar el importador existente de WordPress, eliminar la parte manual y usarlo para cargar mi archivo XML sin que el usuario necesite hacer nada.

En functions.php se puede verificar la condición
if( isset($_GET['activated']) && 'themes.php' == $GLOBALS['pagenow']) )
{
// verificar duplicados
// llamar a la clase de importación
// código de importación xml
// hacer lo que necesites
}
Tan pronto como se active tu tema, esto importará los datos automáticamente.

Gracias por publicar amolv. Sin embargo, no estoy preguntando cómo verificar si un tema ha sido activado (ya lo estoy haciendo). Más bien quiero saber cómo analizar e importar automáticamente un archivo XML sin entrada del usuario.
