Custom Database Tables: Importing Data


In the last tutorial we looked at exporting data from a custom table. Of course this is only half the story - we obviously need to provide a way of importing that data. Naturally, WordPress doesn't handle this - so once more we need to roll our own.

In the previous article in this series we noted that if our data contains references to one of the native WordPress tables (a post ID for example) then we rather quickly hit difficulties. The reason is that when importing such data, the post ID being referenced may not exist and if it does, may not be the correct ID. This is because when posts are imported, their ID may change to avoid collisions in the database (IDs must be unique).

Normally this is ok: linked data is imported together and the references are updated during the import routine to ensure that any changes propagate throughout the entire dataset. However, as discussed in the previous article - it's actually very difficult (except in particular cases) to import our custom data alongside the native data. So the caveats mentioned in that article carry forward to this one - and as before, although the example below references WordPress tables, it is used simply for consistency with the rest of the series.

The Mark-Up

We want to allow our users to import data from the export file generated in the last article in this series - so let's start with adding a form that allows the user to upload that file. We'll do this by modifying the class we defined in last tutorial.

class WPTuts_Log_Export_Admin_Page {

	 * The page hook suffix
	static $hook_suffix='';

	static function load() {
		add_action( 'admin_menu', array( __CLASS__, 'add_submenu' ) );
		add_action( 'admin_init', array( __CLASS__, 'maybe_download' ) );
		add_action( 'admin_init', array( __CLASS__, 'maybe_upload' ) );
		add_action( 'admin_notices',array( __CLASS__, 'admin_notices' ) );

	static function add_submenu() {
		/* Defined in previous article */

	static function maybe_download() {
		/* Defined in previous article */

	static function display() {
		/* Defined in previous article - but we'll make some modifications */

	static function maybe_upload() {


	static function admin_notices() {


	static function import() {


	static function parse() {


Above we've added the following methods

  • maybe_upload() - which will act as a listener for a file being submitted for importing.
  • admin_notices() - which will display a success or error notice after attempting to import a file.
  • import() - which will receive an uploaded file and import the data.
  • parse() - a helper function called by import() to parse the uploaded file, and extract the logs it contains.

But first, we'll add a form by which we can upload a file. We'll add this below the export button we created in the previous article. To do this we'll need make some alterations to the display() method, responsible for producing the mark-up of our admin page. Since this second form will submit a file, we need to set the encoding type to 'multipart/form-data'.

static function display() {

	echo '<div class="wrap">';
	echo '<h2>' . __( 'Export Activity Logs', 'wptuts-log' ) . '</h2>';

	<form id="wptuts-export-log-form" method="post" action="">
			<label><?php _e( 'Click to export the activity logs','wptuts-log' ); ?></label>
			<input type="hidden" name="action" value="export-logs" />
		<?php wp_nonce_field( 'wptuts-export-logs', '_wplnonce' ); ?>
		<?php submit_button( __( 'Download Activity Logs', 'wptuts-log' ), 'button' ); ?>

	<form method="post" action="" enctype="multipart/form-data">
			<label for="wptuts_import_logs"><?php _e( 'Import an .xml file.', 'wptuts-log' ); ?></label>
			<input type="file" id="wptuts_import_logs" name="wptuts_import" />
		<input type="hidden" name="action" value="import-logs" />
		<?php wp_nonce_field( 'wptuts-import-logs', '_wplnonce' ); ?>
		<?php submit_button( __( 'Upload Activity Logs', 'wptuts-log' ), 'secondary' ); ?>

Handling the Form Submission

Next, we want to listen for when the above form is submitted and trigger the import routine. Before doing that it's important to run a few checks:

  • Does the user have permission to upload files? Here we've granted only those who can manage_options the ability to upload.
  • Did the user intend to upload the files (we check this by verify the nonce)
  • Was a file actually uploaded? And was it of the correct type
  • Were there any errors in the upload?

Optionally you can place a limit on the size of the uploaded file as a sort of 'sanity check'. In this example I've capped it at 2MB. (A useful function for formatting file sizes in a 'human readable way' is the function size_format).

static function maybe_upload() {
	/* Listen for form submission */
	if ( empty( $_POST['action'] ) || 'import-logs' !== $_POST['action'] )

	/* Check permissions and nonces */
	if ( ! current_user_can( 'manage_options' ) )

	check_admin_referer( 'wptuts-import-logs', '_wplnonce' );

	/* Perform checks on file: */

	// Sanity check
	if ( empty( $_FILES["wptuts_import"] ) )
		wp_die( 'No file found' );

	$file = $_FILES["wptuts_import"];

	// Is it of the expected type?
	if ( $file["type"] != "text/xml" )
		wp_die( sprintf( __( "There was an error importing the logs. File type detected: '%s'. 'text/xml' expected", 'wptuts-log' ), $file['type'] ) );

	// Impose a limit on the size of the uploaded file. Max 2097152 bytes = 2MB
	if ( $file["size"] > 2097152 ) {
		$size = size_format( $file['size'], 2 );
		wp_die( sprintf( __( 'File size too large (%s). Maximum 2MB', 'import-logs' ), $size ) );

	if( $file["error"] > 0 )
		wp_die( sprintf( __( "Error encountered: %d", 'wptuts-import' ), $file["error"] ) );
	/* If we've made it this far then we can import the data */
	$imported = self::import( $file['tmp_name'] );

	/* Everything is complete, now redirect back to the page */
	wp_redirect( add_query_arg( 'imported', $imported ) );

Importing the File

Next we need to import the file. First we'll need to extract the logs from the uploaded file - and we'll delegate that job to the parse() method (we'll get to that method in a bit).

Once we have these logs we'll first check if they already exist (to avoid any accidental duplication), before inserting them. When checking the logs we'll just check the user ID and the activity date. We could, if we wanted, be more strict (checking activity, object ID and type etc.) but we would need to go back and extend our API (specifically wptuts_get_logs()).

Once we've imported the logs we redirect the user back to our admin page. We'll add a query variable to the URL (imported) to store how many logs were imported (if any). This way we can display an appropriate admin message.

function import( $file ) {

	// Parse file
	$logs = self::parse( $file );

	// No logs found ? - then aborted.
	if  ( ! $logs )
		return 0;

	// Initialises a variable storing the number of logs successfully imported.
	$imported = 0;

	// Go through each log
	foreach ( $logs as $log_id => $log ) {
		 * Check if the log already exists:
		 * We'll just check the date and the user ID, but we could check other details
		 * if we extended our wptuts_get_logs() API
		$exists = wptuts_get_logs( array(
			'user_id' => $log['user_id'],
			'since' => mysql2date( 'G', $log['activity_date'], false ),
			'until' => mysql2date( 'G', $log['activity_date'], false ),

		// If it exists, don't import it
		if ( $exists )

		// Insert the log
		$successful = wptuts_insert_log( array(
			'user_id'=> $log['user_id'],
			'date' =>  mysql2date( 'G', $log['activity_date'], false ),
			'object_id' => $log['object_id'],
			'object_type' => $log['object_type'],
			'activity' => $log['activity'],
			'activity_date' => $log['activity_date'],

		if ( $successful )

	return $imported;

Parsing the File

We still need to define the parse() method which, given the uploaded file, should extract the data and return it as an array of logs. Fortunately, with PHP's built-in XML handler this is a fairly simple task.

function parse( $file ) {
	// Load the xml file
	$xml = simplexml_load_file( $file );

	// halt if loading produces an error
	if ( ! $xml )
		return false;

	// Initial logs array
	$logs = array();
	foreach ( $xml->xpath( '/logs/item' ) as $log_obj ) {

		$log = $log_obj->children( );
		$log_id = (int) $log->log_id;

		$logs[$log_id] = array(
			'user_id' => (int) $log->user_id,
			'object_id' => (int) $log->object_id,
			'object_type' => (string) $log->object_type,
			'activty' => (string) $log->activity,
			'activity_date' =>(string)  $log->activity_date,

	return $logs;

Displaying the Admin Notice

Finally, we want to define our admin_notices() method to display an appropriate message after the file has been uploaded. Recall that once the import routine has finished, we redirect the user back to our admin page, with the query variable imported added, storing the number of imported logs. We use this to determine whether we should display an error or success message.

We also check the screen ID so that we only display the notice on our admin page. If you're not sure what the screen ID of your admin page is, see this article.

function admin_notices() {

	// Was an import attempted and are we on the correct admin page?
	if ( ! isset( $_GET['imported'] ) || 'tools_page_wptuts-export' !== get_current_screen()->id )

	$imported = intval( $_GET['imported'] );

	if ( 1 == $imported ) {
		printf( '<div class="updated"><p>%s</p></div>', __( '1 log successfully imported', 'wptuts-import' ) );

	elseif ( intval( $_GET['imported'] ) ) {
		printf( '<div class="updated"><p>%s</p></div>', sprintf( __( '%d logs successfully imported', 'wptuts-import' ), $imported ) );
	else {
		printf( '<div class="error"><p>%s</p></div>', __( ' No logs were imported', 'wptuts-import' ) );
Related Posts
  • Code
    Theme Development
    Custom Controls in the Theme CustomizerTheme customizer custom control 400
    In the last article, we explored the advanced controls available in the Theme Customizer, and how to implement them. We’re going to look at how to create our own custom control, allowing you to choose which Category of Posts are displayed on the home page. To get started, download version 0.6.0 of our Theme Customizer Example.Read More…
  • Code
    Theme Development
    Creating a WordPress Theme From Static HTML: The Footer FileCreating wordpress theme from html 400
    In this series, you've been learning how to create a WordPress theme form static HTML. Up to this point, you have: prepared your markup for WordPress converted your HTML to PHP and split your file into template files edited the stylesheet and uploaded your theme to WordPress added a loop to your index file added meta tags, the wp_head hook and the site title and description to your header file added a navigation menu added widget areas to the header and sidebar. Read More…
  • Code
    Using HighCharts in WP-AdminHighcharts 400
    Charts are a great way to present data. They make data more digestible by making it visually appealing. In WordPress, there is no built-in method for getting posts and pages data in a graphical form. Although, there are certain plugins available which integrate Google Analytics with WordPress, but they are overkill if you want to get only a portion of that data. Also, nothing should keep you from learning new techniques and to dive straight into the subject is the best way to learn.Read More…
  • Code
    Authentication With Laravel 4Laravel 4 auth retina preview
    Authentication is required for virtually any type of web application. In this tutorial, I'd like to show you how you can go about creating a small authentication application using Laravel 4. We'll start from the very beginning by creating our Laravel app using composer, creating the database, loading in the Twitter Bootstrap, creating a main layout, registering users, logging in and out, and protecting routes using filters. We've got a lot of code to cover, so let's get started!Read More…
  • Code
    Creative Coding
    A Look at the WordPress HTTP API: A Practical Example of wp_remote_postDiagram http api
    In the previous article, we reviewed the previous articles regarding GET requests, the native PHP facilities for making requests, and reviewed WordPress wp_remote_post API function along with the arguments that it offers. In this article, we're going to make use of wp_remote_post such that we're actually able to see it in action. Remember that this - like wp_remote_post - is part of the HTTP API of which there are other functions worth reviewing. But, for now, we're going to put wp_remote_post to work. Specifically, we're going to do the following: When the page loads, we're going to submit some information to a custom script The script will examine the information and return it to our page We'll then display the data on the page Sure, it's a bit of a contrived example but it will give us the experience of creating a separate PHP script that can be used for operations triggered by the use of wp_remote_post. Anyway, for the purposes of this example, we are going to use the PHP $_SERVER collection to log when the user has submitted their preference rather than require that they have logged in. Finally, the source code will be made available on GitHub and accessible at the end of this series in the following article. For now however, let's get started with working on the plugin.Read More…
  • Code
    Custom Database Tables: Exporting DataCustomdbtables part5
    As mentioned in the very first article of this series one of the major problems with a custom database tables is the fact that they are not handled by existing import & export handlers. This article aims to address that problem – but it should be noted that currently there is no completely satisfactory solution.Read More…