Importare automaticamente un file XML in WordPress tramite functions.php
Sto sviluppando un tema che utilizza un metodo diverso per aggiungere contenuti e quindi l'installazione predefinita di WordPress non mostrerà alcun contenuto a causa di questo. Mi chiedevo se fosse possibile importare automaticamente un file XML tramite una funzione interna e/o hook dopo che il tema è stato attivato.
L'utente installa il tema > L'utente attiva il tema > Il codice dietro le quinte carica un file XML ed esegue un'importazione silenziosa dei suoi contenuti
Attualmente per importare un file XML devi installare il plugin WordPress Importer e poi importare manualmente il file, selezionare un utente per associare i contenuti importati e decidere se vuoi importare gli allegati multimediali. Trovo questo passaggio troppo confuso per il tipo di clienti a cui mi rivolgo e vorrei eliminare efficacemente la necessità di questo passaggio.
Ho esaminato lo script dell'importatore di WordPress e ci sono molte chiamate di funzione. Cosa dovrei fare per rimuovere le parti in cui è richiesto l'input dell'utente e importare un file utilizzando direttamente la classe e i suoi metodi? Non sono sicuro da dove iniziare.
I miei clienti sono artigiani, quindi anche qualcosa di semplice come importare un file XML li confonde e non hanno tempo per farlo, quindi c'è spazio per errori, soprattutto se provano a importare più volte causando pagine duplicate.
Grazie in anticipo.
Modifica/Chiarimento
Sembra esserci molta confusione qui. Non sto chiedendo come verificare se un tema è stato attivato, ho già risolto quella parte. Sto chiedendo come potrei fare per analizzare un file XML di importazione e importarlo automaticamente senza sforzo da parte dell'utente. Voglio essenzialmente automatizzare il plugin di importazione di WordPress che puoi già usare per importare manualmente il file XML, scegliere l'autore, scegliere di scaricare e importare gli allegati all'interno del mio functions.php.
Invece di aver bisogno di un plugin o di richiedere ai miei clienti, che hanno poca conoscenza informatica né la voglia di imparare come farlo usando il plugin.

La tua domanda è un po' specifica se vuoi "soltanto" importare automaticamente alcuni post/pagine. Ci sono altri modi per farlo oltre a utilizzare un file di esportazione XML.
Se hai post solo di testo, allora dovresti usare LOAD DATA INFILE. Prima di tutto devi esportare i tuoi post.
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(),
// per multisite
'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( 'Impossibile creare la directory di esportazione. Operazione annullata.' );
}
// svuota la directory di esportazione altrimenti MySQL genera errori
$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>Impossibile esportare {$table} in {$outfile}</p>";
}
}
}
Questo creerà una directory nella cartella del tuo tema (assicurati che sia scrivibile!) ed esporterà i post e i commenti (con i relativi meta) in file di dump. Usa l'array export
per definire cosa vuoi esportare. Ho raggruppato le cose più o meno in modo logico (se vuoi esportare i post, dovresti esportare anche i postmeta e così via).
Il vantaggio di questa soluzione è che con l'istruzione SELECT
puoi definire elementi specifici (ad esempio solo post di una particolare categoria o solo pagine o solo post cestinati).
Ora vuoi importare questi elementi in un nuovo 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>Impossibile importare dati dal file {$file} nella tabella {$wpdb->$match[1]}</p>";
}
Questa soluzione è buona se i post non contengono allegati come immagini. Un altro problema è che nessun utente e nessuna categoria verranno importati. Assicurati che entrambi siano creati prima che inizi l'importazione (o includi utenti e categorie nella tua esportazione). È un metodo molto grezzo per importare elementi, sovrascriverà quelli esistenti!
Se vuoi esportare anche gli allegati, devi fare un po' più di lavoro.
(Nota a margine: Per favore leggi la risposta completa e le Ultime Parole alla fine! Questo argomento non è per principianti e non scriverò un avviso per ogni riga di codice rischiosa)
Il plugin WordPress Importer sembra un buon modo per importare tutto e scaricare automaticamente gli allegati. Diamo un'occhiata a cosa fa questo plugin.
Innanzitutto il plugin chiede di caricare un file XML. Poi analizza il file XML e chiede un mapping degli autori e se gli allegati devono essere scaricati o meno.
Per un'esecuzione automatica del plugin dobbiamo cambiare alcune cose. Prima di tutto dobbiamo saltare il processo di upload. Questo è abbastanza facile perché puoi includere il file XML con il tema e sai dove si trova il file XML. Poi dobbiamo saltare le domande che appaiono dopo il caricamento del file XML. Possiamo predefinire i nostri valori e passarli al processo di importazione.
Inizia con una copia del plugin. Crea una directory nel tuo tema come autoimport
e copia i file wordpress-importer.php
e parsers.php
al suo interno. È una buona idea rinominare il file wordpress-importer.php
in qualcosa come autoimporter.php
. Nelle funzioni del tuo tema aggiungi una chiamata di funzione per attivare l'importazione automatica
/**
* Importazione automatica di un file XML
*/
add_action( 'after_setup_theme', 'autoimport' );
function autoimport() {
// ottieni il file
require_once TEMPLATEPATH . '/autoimport/autoimporter.php';
if ( ! class_exists( 'Auto_Importer' ) )
die( 'Auto_Importer non trovato' );
// chiama la funzione
$args = array(
'file' => TEMPLATEPATH . '/autoimport/import.xml',
'map_user_id' => 1
);
auto_import( $args );
}
Prima di tutto impostiamo alcuni argomenti. La prima cosa è il percorso completo del file XML. La seconda è l'ID di un utente esistente. Abbiamo bisogno di questo utente per il mapping degli autori, questo è l'utente a cui verranno mappati tutti i post quando non si vogliono creare nuovi autori.
Ora dobbiamo capire come funziona il plugin. Apri il file del plugin rinominato e scorri fino alla fine. C'è una funzione wordpress_importer_init()
e una chiamata di azione. Rimuovi entrambe, non sono più necessarie. Ora vai all'inizio del file e rimuovi l'intestazione del plugin (il commento all'inizio del file). Dopodiché, rinomina la classe WP_Importer
in qualcosa come Auto_Importer
, non dimenticare di modificare anche la dichiarazione function_exists
e il primo metodo WP_Importer
(questo è il costruttore in stile PHP4).
Più tardi passeremo il file XML direttamente al costruttore della classe, modifica il primo metodo in questo modo
var $xmlfile = '';
var $map_user_id = 0;
function Auto_Importer( $args ) {
if ( file_exists( $args['file'] ) ) {
// per sistemi Windows
$file = str_replace( '\\', '/', $args['file'] );
$this->xmlfile = $file;
}
if ( isset( $args['map_user_id'] ) )
$this->map_user_id = $args['map_user_id'];
}
Ora dobbiamo rimuovere e modificare alcuni metodi all'interno della classe. Il primo metodo è il metodo dispatch()
. Questo metodo ti dice come funziona la classe. Fa tre passi. Prima carica il file XML, poi lo elabora e infine importa i dati.
Il caso zero è il primo passo, è il saluto. Questa è la parte che vedi se chiami l'importazione per la prima volta. Chiederà un file da caricare. Il caso due gestisce il caricamento e mostra un modulo per le opzioni di importazione. Il caso tre finalmente esegue l'importazione. In altre parole: i primi due passi chiedono solo dati che possiamo fornire noi stessi. Abbiamo bisogno solo del passo 3 (caso 2) e dobbiamo fornire i dati richiesti nel passo uno e due.
Nel passo due vedi una chiamata di funzione a wp_import_handle_upload()
. Questa funzione imposta alcune informazioni sul file xml. Non possiamo più usare questa funzione perché non abbiamo caricato un file. Quindi dobbiamo copiare e modificare la funzione. Crea un nuovo metodo all'interno della classe
function import_handle_upload() {
$url = get_template_directory_uri() . str_replace( TEMPLATEPATH, '', $this->xmlfile );
$type = 'application/xml'; // conosciamo il tipo mime del nostro file
$file = $this->xmlfile;
$filename = basename( $this->xmlfile );
// Costruisci l'array dell'oggetto
$object = array( 'post_title' => $filename,
'post_content' => $url,
'post_mime_type' => $type,
'guid' => $url,
'context' => 'import',
'post_status' => 'private'
);
// Salva i dati
$id = wp_insert_attachment( $object, $file );
// programma una pulizia tra un giorno da ora in caso di importazione fallita o mancata chiamata wp_import_cleanup()
wp_schedule_single_event( time() + DAY_IN_SECONDS, 'importer_scheduled_cleanup', array( $id ) );
return array( 'file' => $file, 'id' => $id );
}
E sostituisci la chiamata di funzione $file = wp_import_handle_upload();
nel metodo handle_upload()
con il nostro nuovo metodo $file = $this->import_handle_upload();
Abbiamo ora sostituito il processo di upload con il nostro file (che dovrebbe già esistere). Prosegui e rimuovi altri metodi non necessari. I metodi greet()
, header()
e footer()
non sono più necessari (header e footer stampano solo del testo) e possono essere rimossi dalla classe. Nel metodo dispatch()
rimuovi le chiamate a questi metodi ($this->header()
e $this->footer()
).
Il primo passo è fatto, ora dobbiamo occuparci del secondo passo, le opzioni di importazione. Le opzioni di importazione chiedono se deve essere consentito scaricare gli allegati e mappare gli autori.
La prima parte è facile. Imposta su true se gli allegati devono essere scaricati o false se non devono. Il mapping degli autori è un po' più complicato. Se è consentito creare nuovi utenti (gli autori dal file di importazione), creali. Altrimenti, assegna i post a un utente esistente. Questo viene fatto nel metodo get_author_mapping()
. Dobbiamo sostituire i dati $_POST
con dati esistenti. Qui abbiamo bisogno di una soluzione semplice, quindi mappiamo semplicemente tutti i nuovi autori a uno esistente se non è consentito creare nuovi utenti. Oppure crea semplicemente tutti i nuovi utenti. Nel secondo caso, assicurati che tutti i nuovi utenti siano utenti dummy. Altrimenti, ogni volta che li importi, riceveranno un'email con login e password per il nuovo blog!! Non spiegherò ogni riga di codice, ecco il metodo completamente riscritto
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'];
// Multisite aggiunge strtolower a sanitize_user. Bisogna sanitizzare qui per evitare interruzioni in 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( __( 'Creazione nuovo utente fallita per %s. I loro post saranno attribuiti all\'utente corrente.', '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 />';
}
}
// sicurezza: se l'user_id non era valido, usa l'utente corrente
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();
}
}
}
C'è ancora del lavoro da fare. Aggiungiamo prima una funzione 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();
}
Posiziona questa funzione dopo la classe. Questa funzione manca di alcuni controlli e gestione degli errori (ad esempio per un argomento file vuoto).
Se ora esegui la classe, riceverai molti messaggi di errore. Il primo è che la classe manca. Questo perché c'è un'istruzione if
all'inizio.
if ( ! defined( 'WP_LOAD_IMPORTERS' ) )
return;
Dobbiamo rimuoverla, altrimenti il file non verrebbe analizzato completamente. Poi ci sono alcune funzioni che non sono caricate a questo punto. Dobbiamo includere alcuni file.
$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;
}
Fondamentalmente questo è tutto. Ho testato questo su un'installazione locale con i dati di test XML di WordPress. Funziona per me ma non è una soluzione perfetta per la produzione!
E alcune ultime parole sull'impostazione di alcune opzioni. Ci sono due opzioni che possono essere modificate da un filtro:
add_filter( 'import_allow_create_users', function() { return false; } );
add_filter( 'import_allow_fetch_attachments', '__return_false' );
Penso di non doverlo spiegare. Metti questi filtri nel tuo functions.php e imposta true o false (il primo è in stile PHP5.3, il secondo è in stile WP).
Ultime Parole
Ho messo tutto insieme in questo gist. Usalo a tuo rischio! Non sono responsabile per nulla!. Per favore dai un'occhiata ai file nel gist, non ho spiegato ogni piccolo passo qui.
Cose che non ho fatto: Impostare un valore ad esempio nelle opzioni (del tema) dopo l'importazione. Altrimenti l'importazione parte ogni volta che il tema viene attivato.
Forse ci lavorerò in futuro, pulirò alcune cose e eseguirò più test.

Wow. Semplicemente wow. Questa è una delle risposte più dettagliate e impressionanti che abbia mai visto su questo sito, complimenti davvero. Dopo aver passato mezza giornata a leggerla, sono giunto alla conclusione che questo è il modo giusto di procedere. Mi hai dato moltissimo su cui riflettere, ma questa è sicuramente la strada giusta da seguire. Grazie per aver dedicato il tempo a scrivere questa risposta.

Permettimi di ri-presentare 2 cose qui:
(a) "Non sto chiedendo come... ho già risolto quella parte..."
»» Ho imparato col tempo ad accettare il fatto che l'approccio ai problemi/correzioni non richiede necessariamente una 'associazione visibile' con il problema in sé.
(b) "...dovrei fare per rimuovere le parti..." "...i clienti sono artigiani, quindi anche qualcosa di semplice come..."
»» Perché rendere le cose più facili per il cliente a discapito di complicarle per te stesso? Potrei certamente offrire 'servizi' dopo la consegna e stabilire una connessione remota per farlo al posto loro [a pagamento], invece di "...modificare il plugin di importazione...". Voglio dire, chiediti se ne vale davvero la pena nel tuo attuale schema di lavoro. Tuttavia SE sei disposto a metterci l'impegno, prova a dare un'occhiata al codice qui sotto. Se puoi, allora:
- afferra prima i fondamentali e comprendi meglio il database
- tieni a portata di mano un riferimento per tutte le funzioni - vecchio ma oro
- tieni a portata di mano un riferimento per tutti gli hook - semplifica semplifica
Concordo sia con chrisguitarguy che con amolv sopra.
Come ha sottolineato chris, i modi per ottenere un output sono molti. Questo è solo uno. Sebbene possa diventare laboriosamente lungo, fai riferimento agli ultimi paio di righe prima di tutto il resto.
<?php
/* Di solito inserisco UNA riga in functions.php */
require_once (TEMPLATEPATH . '/includes/whatever.php');
/* e poi in quella posizione CONTROLLA PRIMA*/
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;
// crea tabelle
your_tables();
// inserisci valori predefiniti
your_values();
// inserisci link predefiniti
your_links();
// pagine e template
your_pages();
// crea categoria o categorie
// wp_create_categories $categories, $post_id = ''
// wp_create_category $cat_name, $parent
//flush rewrite
$wp_rewrite->flush_rules();
}
// crea le tabelle del database
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);
// e così via
/* Inserisci dati predefiniti/TUTTI i dati nelle tabelle */
// MA CONTROLLA PRIMA SE I DATI ESISTONO. SE = SI NON INSERIRE NULLA
$sql = "SELECT field_id " . "FROM " . $wpdb->prefix . "table1_name LIMIT 1";
$wpdb->get_results($sql);
if($wpdb->num_rows == 0) {
// altro codice seguirà
// devo andare ora
}
?>
NOTA
Se usi WP da un po' è superfluo dire FARE UN BACKUP DEL DB PRIMA.
phpMyAdmin ha una potenza grezza e rende abbastanza facile incasinare le cose con cura.
Sebbene lo sforzo richiesto possa sembrare inizialmente scoraggiante, se fatto bene potrebbe funzionare come un orologio svizzero...
Infine
Come inserire 2000 righe di dati in 20 secondi in quelle ultime 2 righe tra quelle 2 parentesi graffe?
phpMyAdmin » Seleziona DB a sinistra »» Seleziona Tutte le TABELLE a destra »» Esporta ▼
➝ Personalizzato: mostra tutte le opzioni
➝ Visualizza output come testo = ON
➝ Salva output in un file = OFF
➝ Compressione = NESSUNA
➝ Formato = SQL
➝ Dump Tabella = STRUTTURA & DATI
➝ Aggiungi DROP TABLE... = OFF (Importante!)
➝ Sintassi da usare = "entrambe le precedenti"
»» VAI!
Dalla schermata successiva potrei copiare la parte 'STRUTTURA' in $sql = "...." per
your_tables()
e la porzione 'DATI' in$sql
peryour_data()
Per il resto dei valori predefiniti di WP uso
update_option(...)
&update_post_meta(...)

Non esiste un equivalente per i temi dell'register_activation_hook
utilizzato nei plugin – ci sono alcuni escamotage. Perché? Perché un tema è una skin. Solo le funzionalità specificamente legate alla visualizzazione dei contenuti dovrebbero essere inserite in un tema, non i contenuti stessi.
Per quanto riguarda il come: utilizza l'esempio precedente per eseguire una funzione di callback una volta. L'importatore di WordPress funziona con file XML e ci sono molti diversi modi per analizzare XML in PHP. Scegli quello che preferisci, analizza il file e fai ciò che vuoi con esso.

Grazie Chris. Il problema non è trovare un hook adatto per rilevare l'attivazione di un tema (quella parte l'ho già risolta) ma trovare un modo per importare automaticamente un file XML in WordPress. Ho il file XML in una cartella della mia directory del tema chiamata "importxml". Preferirei utilizzare l'importatore predefinito di WordPress, rimuovere le parti manuali e usarlo per caricare il mio file XML senza che l'utente debba fare nulla.

Nel file functions.php è possibile verificare la condizione
if( isset($_GET['activated']) && 'themes.php' == $GLOBALS['pagenow']) )
{
// verifica duplicati
// chiama la classe di importazione
// codice per l'importazione XML
// esegui qualsiasi operazione desideri
}
Non appena il tuo tema viene attivato, questo codice importerà automaticamente i dati.

Grazie per aver postato amolv. Tuttavia, non sto chiedendo come verificare se un tema è stato attivato (lo sto già facendo). Piuttosto, vorrei sapere come analizzare e importare automaticamente un file XML senza l'intervento dell'utente.
